IO FILE 之fclose 详解
字数 960 2025-08-05 08:19:22
IO FILE 之 fclose 详解
概述
本文详细分析 glibc 中 fclose 函数的实现原理,重点关注文件关闭流程和堆内存管理机制。fclose 主要完成三个任务:将 FILE 结构体从 _IO_list_all 链表中移除、关闭文件并释放缓冲区、释放 FILE 结构体内存。
前置知识
fopen会创建 FILE 结构体并将其链接到_IO_list_all链表fread/fwrite会建立输入/输出缓冲区- FILE 结构体涉及两个堆块:结构体本身和缓冲区
核心函数 _IO_new_fclose
int _IO_new_fclose (_IO_FILE *fp) {
int status;
// 1. 从链表中移除
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
// 2. 关闭文件和释放缓冲区
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
// 3. 最终确认
_IO_FINISH (fp);
// 4. 释放结构体
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr) {
fp->_IO_file_flags = 0;
free(fp);
}
return status;
}
详细流程分析
1. 从 _IO_list_all 链表中移除
_IO_un_link 函数实现:
void _IO_un_link (struct _IO_FILE_plus *fp) {
if (fp->file._flags & _IO_LINKED) { // 检查标志位
if (_IO_list_all == NULL) {
; // 空链表不做处理
} else if (fp == _IO_list_all) { // 链表头节点
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
++_IO_list_all_stamp;
} else { // 链表中间节点
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (_IO_FILE *) fp) {
*f = fp->file._chain;
++_IO_list_all_stamp;
break;
}
}
fp->file._flags &= ~_IO_LINKED; // 清除链接标志
}
}
关键点:
- 检查
_IO_LINKED标志位(0x80) - 处理三种情况:空链表、头节点、中间节点
- 更新
_IO_list_all_stamp计数器 - 清除
_IO_LINKED标志
2. 关闭文件和释放缓冲区
_IO_file_close_it 函数实现:
int _IO_new_file_close_it (_IO_FILE *fp) {
// 检查文件是否打开
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);
// 关闭文件描述符
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
// 释放缓冲区
_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;
}
关键子函数:
缓冲区刷新 _IO_do_flush
#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)))
文件关闭 _IO_SYSCLOSE
int _IO_file_close (_IO_FILE *fp) {
return close_not_cancel (fp->_fileno); // 最终调用系统调用 close
}
缓冲区释放 _IO_setb
void _IO_setb (_IO_FILE *f, char *b, char *eb, int a) {
if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF))
free (f->_IO_buf_base); // 释放缓冲区
f->_IO_buf_base = b;
f->_IO_buf_end = eb;
if (a)
f->_flags &= ~_IO_USER_BUF;
else
f->_flags |= _IO_USER_BUF;
}
3. 最终确认 _IO_FINISH
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);
}
_IO_default_finish 实现:
void _IO_default_finish (_IO_FILE *fp, int dummy) {
// 再次释放缓冲区
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) {
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 (fp->_IO_save_base);
fp->_IO_save_base = NULL;
}
// 确保从链表中移除
_IO_un_link ((struct _IO_FILE_plus *) fp);
}
关键点总结
-
vtable 调用:
_IO_do_write- 刷新输出缓冲区_IO_SYSCLOSE(__close) - 关闭文件描述符_IO_FINISH(__finish) - 最终确认
-
内存释放时机:
- 缓冲区在
_IO_setb中释放 - FILE 结构体在
_IO_new_fclose最后释放
- 缓冲区在
-
标志位变化:
_IO_LINKED被清除_fileno设为 -1_flags设为_IO_MAGIC|CLOSED_FILEBUF_FLAGS
-
安全机制:
- 多次确认缓冲区释放
- 多次确认链表移除
- 标准流(stdin/stdout/stderr)不会被释放
利用相关
后续利用可能关注:
- 虚表劫持控制程序流
- vtable 检查与绕过
- 通过结构体指针实现内存读写
理解 fclose 的完整流程为后续漏洞利用打下基础。