fopen함수

dandb3·2023년 5월 30일
0

pwnable

목록 보기
5/14
  • fopen 함수의 소스코드를 보자.

    #   define fopen(fname, mode) _IO_new_fopen (fname, mode)

    typedef로 _IO_new_fopen이 연결되어 있다.

  • _IO_new_fopen

    FILE *
    _IO_new_fopen (const char *filename, const char *mode)
    {
      return __fopen_internal (filename, mode, 1);
    }

    내부적으로 __fopen_internal을 호출한다.

  • __fopen_internal

    FILE *
    __fopen_internal (const char *filename, const char *mode, int is32)
    {
      struct locked_FILE
      {
        struct _IO_FILE_plus fp;
    #ifdef _IO_MTSAFE_IO
        _IO_lock_t lock;
    #endif
        struct _IO_wide_data wd;
      } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
    
      if (new_f == NULL)
        return NULL;
    #ifdef _IO_MTSAFE_IO
      new_f->fp.file._lock = &new_f->lock;
    #endif
      _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
      _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
      _IO_new_file_init_internal (&new_f->fp);
      if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL)
        return __fopen_maybe_mmap (&new_f->fp.file);
    
      _IO_un_link (&new_f->fp);
      free (new_f);
      return NULL;
    }

    사실상 여기부터 제대로 된 함수가 시작된다.
    우선, struct locked_FILE 구조체를 할당하는데, 멤버 변수로 struct _IO_FILE_plus, _IO_lock_t, struct _IO_wide_data를 가진다. _IO_lock_tstruct _IO_wide_data는 일단 넘기도록 하자.

  • struct _IO_FILE_plus

    struct _IO_FILE_plus
    {
      FILE file;
      const struct _IO_jump_t *vtable;
    };

    FILEstruct _IO_jump_t로 이루어져 있다.
    여기서 뭔가 눈에 익은 변수 이름이 보이는데, vtable이라는 녀석이다.
    객체지향을 배우면 알게되는 class에서의 다형성을 구현하기 위한 vtable과 이름이 똑같다.
    물론 이름만 같은 것이 아니라 그 기능을 구현하기 때문에 vtable이라는 이름이 붙게 되었다. 그러면 struct _IO_jump_t에 대해서 알아보자.

  • struct _IO_jump_t

    #define JUMP_FIELD(TYPE, NAME) TYPE NAME
    
    struct _IO_jump_t
    {
        JUMP_FIELD(size_t, __dummy);
        JUMP_FIELD(size_t, __dummy2);
        JUMP_FIELD(_IO_finish_t, __finish);
        JUMP_FIELD(_IO_overflow_t, __overflow);
        JUMP_FIELD(_IO_underflow_t, __underflow);
        JUMP_FIELD(_IO_underflow_t, __uflow);
        JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
        /* showmany */
        JUMP_FIELD(_IO_xsputn_t, __xsputn);
        JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
        JUMP_FIELD(_IO_seekoff_t, __seekoff);
        JUMP_FIELD(_IO_seekpos_t, __seekpos);
        JUMP_FIELD(_IO_setbuf_t, __setbuf);
        JUMP_FIELD(_IO_sync_t, __sync);
        JUMP_FIELD(_IO_doallocate_t, __doallocate);
        JUMP_FIELD(_IO_read_t, __read);
        JUMP_FIELD(_IO_write_t, __write);
        JUMP_FIELD(_IO_seek_t, __seek);
        JUMP_FIELD(_IO_close_t, __close);
        JUMP_FIELD(_IO_stat_t, __stat);
        JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
        JUMP_FIELD(_IO_imbue_t, __imbue);
    };

    보면 알겠지만, 함수 포인터들로 구성되어 있다. glibc 파일을 뒤적이다 보면 정말로 많은 매크로들이 즐비한데, 이 매크로를 통해서 각 구조체마다 해당하는 함수를 호출할 수 있게 된다.
    그 중에 우리가 이번에 살펴볼 _IO_file_jumps는 다음과 같이 생겼다.

    #define JUMP_INIT(NAME, VALUE) VALUE
    
    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)
    };
    libc_hidden_data_def (_IO_file_jumps)

    이 테이블을 바탕으로 내부적으로 어떤 함수가 호출되는지 알 수 있다.

  • 다시 __fopen_internal로 돌아가면,,
    struct locked_FILE은 내부적으로 FILE 구조체 뿐만 아니라 해당되는 vtable까지 포함한다는 것을 알 수 있다.

  • 그 다음에 _IO_file_fopen 앞에 있는 나머지 함수/매크로들은 전부 init에 쓰이는 함수들이다. 이제 _IO_file_fopen 함수에 대해 알아보자.

  • _IO_new_file_fopen

    • _IO_file_fopen을 호출한댔는데 갑자기 _IO_new_file_fopen이 왜 나옴??
      -> 함수 아래에 보면 libc_hidden_ver(_IO_new_file_fopen, _IO_file_fopen)이라고 적혀있는데, _IO_new_file_fopen은 외부로부터 숨긴다는 뜻이고, _IO_file_fopen을 호출할 때에 대신 _IO_new_file_fopen을 호출한다는 뜻이다.
    FILE *
    _IO_new_file_fopen (FILE *fp, const char *filename, const char *mode,
                int is32not64)
    {
      int oflags = 0, omode;
      int read_write;
      int oprot = 0666;
      int i;
      FILE *result;
      const char *cs;
      const char *last_recognized;
      if (_IO_file_is_open (fp))
        return 0;
      switch (*mode)
        {
        case 'r':
          omode = O_RDONLY;
          read_write = _IO_NO_WRITES;
          break;
        case 'w':
          omode = O_WRONLY;
          oflags = O_CREAT|O_TRUNC;
          read_write = _IO_NO_READS;
          break;
        case 'a':
          omode = O_WRONLY;
          oflags = O_CREAT|O_APPEND;
          read_write = _IO_NO_READS|_IO_IS_APPENDING;
          break;
        default:
          __set_errno (EINVAL);
          return NULL;
        }
      last_recognized = mode;
      for (i = 1; i < 7; ++i)
        {
          switch (*++mode)
          {
          case '\0':
            break;
          case '+':
            omode = O_RDWR;
            read_write &= _IO_IS_APPENDING;
            last_recognized = mode;
            continue;
          case 'x':
            oflags |= O_EXCL;
            last_recognized = mode;
            continue;
          case 'b':
            last_recognized = mode;
            continue;
          case 'm':
            fp->_flags2 |= _IO_FLAGS2_MMAP;
            continue;
          case 'c':
            fp->_flags2 |= _IO_FLAGS2_NOTCANCEL;
            continue;
          case 'e':
            oflags |= O_CLOEXEC;
            fp->_flags2 |= _IO_FLAGS2_CLOEXEC;
            continue;
          default:
            /* Ignore.  */
            continue;
          }
          break;
        }
      result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
                  is32not64);
    
      ... (생략) ...
    
      return result;
    }
    libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)

    앞부분은 전부다 open mode와 관련된 부분이다. open 함수를 호출할 때 flag, mode값에 들어갈 값들을 세팅해 준다.
    mode 세팅이 끝나고 나면 _IO_file_open 함수를 호출하여 내부적으로 open 시스템 콜을 호출하게 된다.

  • _IO_file_open

    FILE *
    _IO_file_open (FILE *fp, const char *filename, int posix_mode, int prot,
               int read_write, int is32not64)
    {
      int fdesc;
      if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))
        fdesc = __open_nocancel (filename,
                     posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
      else
        fdesc = __open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
      if (fdesc < 0)
        return NULL;
      fp->_fileno = fdesc;
      _IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
      /* For append mode, send the file offset to the end of the file.  Don't
         update the offset cache though, since the file handle is not active.  */
      if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
          == (_IO_IS_APPENDING | _IO_NO_READS))
        {
          off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
          if (new_pos == _IO_pos_BAD && errno != ESPIPE)
          {
            __close_nocancel (fdesc);
            return NULL;
          }
        }
      _IO_link_in ((struct _IO_FILE_plus *) fp);
      return fp;
    }
    libc_hidden_def (_IO_file_open)

    순서 : open systemcall을 호출 -> (appending mode라면 파일 offset을 끝으로 옮겨줌, 내부적으로 커널 버퍼를 이용할 것으로 추측) -> _IO_link_in을 호출하여 linked list에 연결해 준다. (_IO_link_in 코드를 보면 어렵지 않게 알 수 있음)

  • 이러고 나서 다시 __fopen_internal로 돌아가서, open이 성공 시 리턴, 실패 시 unlink, free 후 종료하게 된다.

  • 너무 복잡하긴 한듯.. 여러 아키텍쳐 및 세팅을 고려해야 하기 때문에 매크로가 굉장히 많고, 함수 자체도 중간에 #ifdef가 굉장히 많이 있다.

  • 큰 흐름에 필요가 없는 thread부분(lock-unlock), mmap, wide는 생략하였다.

  • 참고 자료

profile
공부 내용 저장소

0개의 댓글