1. Introduction
파일을 읽는 과정에서 파일 구조체를 조작해 임의 메모리에 값을 쓰는 실습을 해보자.
예제 코드
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char account_buf[1024];
int overwrite_me;
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int read_account() {
FILE *fp;
fp = fopen("/etc/passwd", "r");
fread(account_buf, sizeof(char), sizeof(account_buf), fp);
write(1, account_buf, sizeof(account_buf));
fclose(fp);
}
int main() {
FILE *fp;
char file_buf[1024];
init();
fp = fopen("/etc/issue", "r");
printf("Data: ");
read(0, fp, 300);
fread(file_buf, 1, sizeof(file_buf)-1, fp);
printf("%s", file_buf);
if( overwrite_me == 0xDEADBEEF)
read_account();
fclose(fp);
}
2. 파일 읽기 함수 분석
2.1 파일 읽기 과정
_IO_new_file_xsputn 함수
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
_IO_size_t want, have;
_IO_ssize_t count;
_char *s = data;
want = n;
...
if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF)
break;
continue;
}
...
}
파일의 내용을 읽기 위한 함수는 대표적으로 fread, fgets가 있다.
해당 함수는 라이브러리 내부에서 _IO_file_xsgetn 함수를 호출한다.
위 코드를 살펴보면, 해당 함수는 파일 함수의 인자로 전달된 n이 _IO_buf_end - _IO_buf_base 값보다 작은지를 검사하고
__underflow 즉, _IO_new_file_underflow 함수를 호출한다.
실제로 파일의 내용을 읽는 과정은 _IO_new_file_underflow를 시작으로 다양한 함수가 호출되면서 이뤄진다.
그렇다면 해당 함수 내부에서 어떻게 파일의 내용을 읽는지 자세하게 알아보자.
2.2 _IO_new_file_underflow
_IO_new_file_underflow 함수
int _IO_new_file_underflow (FILE *fp)
{
ssize_t count;
if (fp->_flags & _IO_NO_READS)
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
...
count = _IO_SYSREAD (fp, fp->_IO_buf_base,
fp->_IO_buf_end - fp->_IO_buf_base);
}
위 코드는 _IO_new_file_underflow 함수로,
코드를 살펴보면, 해당 함수 내부에서는 파일 포인터의 _flags 변수에 읽기 권한이 부여되어 있는지를 확인한다.
이후 _IO_SYSREAD 함수의 인자로 파일 포인터와 파일 구조체의 멤버 변수를 연산한 값이 전달된다.
_IO_SYSREAD 함수는 vtable의 _IO_file_read 함수로, 아래 매크로에서 확인할 수 있다.
#define _IO_SYSREAD(FP, DATA, LEN) JUMP2 (__read, FP, DATA, LEN)
2.3 _IO_file_read
_IO_ssize_t
_IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size)
{
return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0)
? __read_nocancel (fp->_fileno, buf, size)
: __read (fp->_fileno, buf, size));
}
_IO_file_read 함수 내부에서는 read 시스템 콜을 사용해 파일의 데이터를 읽는다.
시스템 콜의 인자로 파일 구조체에서 파일 디스크립터를 나타내는 _fileno, _IO_buf_base인 buf, 그리고 _IO_buf_end - _IO_buf_base로 연산된 size 변수가 전달된다.
전달되는 인자를 파일 구조체로 표현하면 다음과 같다.
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
Reference