日志函数add_log存在哪些安全隐患?线程与高频调用问题分析
add_log的线程/高频场景不安全因素分析 朋友推荐了如下add_log函数代码,我已在多个版本中使用。起初仅用于调试特定函数,后作为常规内部日志记录方式,但发现在线程环境或高频调用场景下会出现问题。请问该函数的实现存在哪些不安全因素?
void add_log(const char* format, ...) { HANDLE filehandle; DWORD dwReadBytes; char buffer[2048]; char writebuffer[2048]; va_list args; va_start(args, format); vsprintf_s(buffer, format, args); filehandle = CreateFile("C:\\Log.txt", GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0); SetFilePointer(filehandle, 0, 0, FILE_END); time_t ltime; ltime = time(NULL); //sprintf_s(writebuffer, 2048, "%s: %s\n", asctime(localtime(<ime)), buffer); sprintf_s(writebuffer, 2048, "%s", buffer); WriteFile(filehandle, writebuffer, strlen(writebuffer), &dwReadBytes, 0); CloseHandle(filehandle); }
核心不安全因素
线程间无同步,日志会被覆盖或乱序
多线程同时调用时,每个线程独立执行「打开文件→定位末尾→写入→关闭」流程,这些操作不是原子的。比如线程A刚定位到文件末尾,线程B就抢先写入了内容,线程A再写入就会直接覆盖B的日志;或者多个线程的写入操作交叉执行,导致日志内容拼接混乱、顺序完全错乱。文件共享模式错误,高频调用会触发打开失败
CreateFile的第三个参数传了0,意味着独占打开文件,不允许任何其他线程/进程访问。如果多个线程同时调用该函数,后续线程的CreateFile会直接失败,连文件都打不开更别说写日志了。正常应该传FILE_SHARE_WRITE | FILE_SHARE_READ,允许其他线程同时读写该文件。定位+写入非原子,存在竞态条件
就算改了共享模式,SetFilePointer和WriteFile是两个独立的系统调用,中间可能被其他线程打断,导致写入位置错误。比如线程A定位到末尾后,线程B写入了内容,线程A的写入位置就不再是最新的末尾,同样会覆盖日志。可以用带OVERLAPPED结构体的WriteFile直接指定写入到末尾,或者用原子化的写入方式避免这个问题。固定缓冲区存在溢出风险
虽然用了sprintf_s这类安全函数,但buffer和writebuffer固定为2048字节。如果格式化后的日志内容超过这个长度,会触发sprintf_s的错误处理(默认会调用无效参数处理程序导致程序崩溃),高频调用下一旦出现超长日志,直接影响程序稳定性。未处理系统调用错误,故障无感知
CreateFile、WriteFile这些系统调用都可能失败(比如磁盘满、权限不足),但函数里完全没做错误检查。就算文件打开失败,后续的写入操作照样执行,日志丢了程序也不会有任何提示,排查问题时根本不知道哪里出了错。
内容的提问来源于stack exchange,提问作者Minseok Kim

