Profile

youngsouk

youngsouk

fclose 분석

이제 마지막으로 fclose를 분석함으로서 파일 관련 분석을 끝낼 것입니다. 이번에도 마찬가지로

#include <stdio.h>

int main(){
        FILE* f = fopen("tmp", "w");

        fclose(f);
}

이런식으로 간단한 프로그램을 짜주시고 디버깅을 해보시면서 함수를 보시면 됩니다.  먼저 fclose도 fwrite와 fread와 비슷하게 fclose는 _IO_new_fclose의 약어입니다.

int
_IO_new_fclose (FILE *fp)
{
  int status;

  CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
  /* 우리는 필사적으로 이상한 방법으로 스트림을 사용하고, 예전과 새로운 함수를 섞은 프로그램들을
  	 도우려고 시도한다. 여기서 오래된 스트림들을 탐지한다.
  */
  if (_IO_vtable_offset (fp) != 0)
    return _IO_old_fclose (fp);
#endif

  /* 스트림을 연결 해제 시킨다.  */
  if (fp->_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);

  _IO_acquire_lock (fp);
  if (fp->_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp);
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp);
  if (fp->_mode > 0)
    {
      /* 이 스트림은 넓은 방향을 가지고 있다. 이것은 우리가 전환 함수를 free해야 된다는 것을 의미한다. */
      struct _IO_codecvt *cc = fp->_codecvt;

      __libc_lock_lock (__gconv_lock);
      __gconv_release_step (cc->__cd_in.__cd.__steps);
      __gconv_release_step (cc->__cd_out.__cd.__steps);
      __libc_lock_unlock (__gconv_lock);
    }
  else
    {
      if (_IO_have_backup (fp))
	_IO_free_backup_area (fp);
    }
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_flags = 0;
      free(fp);
    }

  return status;
}

versioned_symbol (libc, _IO_new_fclose, _IO_fclose, GLIBC_2_1);
strong_alias (_IO_new_fclose, __new_fclose)
versioned_symbol (libc, __new_fclose, fclose, GLIBC_2_1);

fclose를 할 때 먼저 스트림을 unlink를 시킨 뒤에 파일 디스크립터를 free시키게 됩니다. _IO_un_link 함수에 대한 분석은

2019/09/12 - [힙(heap)/house_of_orange] - house_of_orange 번외 - fopen함수 분석(glibc 2.23)

이 글의 마지막 부분에 있기 때문에 참고해주시기 바랍니다.

그 다음으로 볼 함수는 _IO_file_close_it입니다. 그런데 이 함수는 _IO_new_file_close_it의 약어이므로 _IO_new_file_close_it을 분석해야 합니다.

#define _IO_do_flush(_f) \
  ((_f)->_mode <= 0                                                              \
   ? _IO_do_write(_f, (_f)->_IO_write_base,                                      \
                  (_f)->_IO_write_ptr-(_f)->_IO_write_base)                      \
   : _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base,                      \
                   ((_f)->_wide_data->_IO_write_ptr                              \
                    - (_f)->_wide_data->_IO_write_base)))

int
_IO_new_file_close_it (FILE *fp)
{
  int write_status;
  if (!_IO_file_is_open (fp))
    return EOF;
  if ((fp->_flags & _IO_NO_WRITES) == 0
      && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
    write_status = _IO_do_flush (fp);
  else
    write_status = 0;
  _IO_unsave_markers (fp);
  int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
                      ? _IO_SYSCLOSE (fp) : 0);
  /* 버퍼를 free시킨다. */
  if (fp->_mode > 0)
    {
      if (_IO_have_wbackup (fp))
        _IO_free_wbackup_area (fp);
      _IO_wsetb (fp, NULL, NULL, 0);
      _IO_wsetg (fp, NULL, NULL, NULL);
      _IO_wsetp (fp, NULL, NULL);
    }
  _IO_setb (fp, NULL, NULL, 0);
  _IO_setg (fp, NULL, NULL, NULL);
  _IO_setp (fp, NULL, NULL);
  _IO_un_link ((struct _IO_FILE_plus *) fp);
  fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
  fp->_fileno = -1;
  fp->_offset = _IO_pos_BAD;
  return close_status ? close_status : write_status;
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)

이 함수에서는 _IO_do_flush를 통해 버퍼를 비우는 것과 더불어 파일을 닫고 각종 포인터들을 초기화 시키고 있습니다. 여기에서 특이한 점이 _IO_SYSCLOSE부분인데 이것은 vtable의 close부분을 호출하게 됩니다. 하지만 이 close는 _IO_file_close을 가리키고 있습니다. 즉 자기 자신을 한번더 호출하는 것입니다.

다음으로 봐야할 것은 _IO_FINSH함수인데 이 함수는 vtable의 finish를 호출하게 됩니다. 즉 이 상황에서는 

2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 번외 fread 분석 (glibc 2.23)

2019/09/12 - [힙(heap)/house_of_orange] - house_of_orange 번외 - fopen함수 분석(glibc 2.23)

이 두 글에서 보셨다시피 _IO_new_file_finish가 실행이 됩니다.

const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
#define _IO_do_flush(_f) \
  ((_f)->_mode <= 0                                                              \
   ? _IO_do_write(_f, (_f)->_IO_write_base,                                      \
                  (_f)->_IO_write_ptr-(_f)->_IO_write_base)                      \
   : _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base,                      \
                   ((_f)->_wide_data->_IO_write_ptr                              \
                    - (_f)->_wide_data->_IO_write_base)))

void
_IO_new_file_finish (_IO_FILE *fp, int dummy)
{
  if (_IO_file_is_open (fp))
    {
      _IO_do_flush (fp);
      if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
	_IO_SYSCLOSE (fp);
    }
  _IO_default_finish (fp, 0);
}
libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)

여기에서는 _IO_do_flush를 통해 버퍼를 비우고, _IO_default_finish를 호출하고 있습니다.

void
_IO_default_finish (FILE *fp, int dummy)
{
  struct _IO_marker *mark;
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) //할당했던 버퍼 free
    {
      free (fp->_IO_buf_base);
      fp->_IO_buf_base = fp->_IO_buf_end = NULL;
    }
  for (mark = fp->_markers; mark != NULL; mark = mark->_next)
    mark->_sbuf = NULL;
  if (fp->_IO_save_base) //백업이 있다면 백업을 free시킨다.
    {
      free (fp->_IO_save_base);
      fp->_IO_save_base = NULL;
    }
  _IO_un_link ((struct _IO_FILE_plus *) fp); //_IO_list_all과의 link를 해제한다.
#ifdef _IO_MTSAFE_IO
  if (fp->_lock != NULL)
    _IO_lock_fini (*fp->_lock);
#endif
}
libc_hidden_def (_IO_default_finish)

이렇게 할당했던 버퍼들을 free시키고 있는 모습을 볼 수 있습니다. 이렇게 fclose에 쓰이는 함수 분석이 끝났습니다. 마지막으로 fclose의 호출과정을 그림으로 나타내면 이렇게 됩니다.

 

'FSOP' 카테고리의 다른 글

setvbuf 함수 분석  (0) 2019.09.25
FSOP - seethefile(pwnable.tw)  (0) 2019.09.20
fwrite 분석  (0) 2019.09.13
fread 분석  (0) 2019.09.13
fopen함수 분석  (0) 2019.09.12