stat()、fstat()、lstat() 和 fopen();如何编写 TOCTOU 受保护的系统独立代码
stat(), fstat(), lstat(), and fopen(); how to write TOCTOU protected system independent code
我已经处理了几个星期的问题,现在正在更新需要独立于系统的 20 年代码(在 Linux 和 Windows 上工作)。它涉及Time-of-Check、Time-of-Use (TOCTOU)问题。我发了一个帖子 here,但并没有走多远,在反复思考了一段时间并深入研究了问题之后,我想我对我的问题的理解更好了一些。也许我也可以问得好一点...
据我所知,代码需要检查文件是否存在,如果可以访问,则打开文件,进行一些操作,最后关闭文件。执行此操作的最佳方法似乎是调用 lstat()
、调用 fopen()
、调用 fstat()
(排除 TOCTOU),然后操作并关闭文件。
但是,我一直相信 lstat()
和 fstat()
是 POSIX 定义的, 不是 C 标准定义的,裁定将它们用于与系统无关的程序,就像 open()
不应该用于交叉兼容性一样。你将如何实施?
如果你看at my first post,你会看到 20 年前的开发人员使用 C 预处理器将代码切割成交叉兼容的部分,但即使我这样做了,我也不知道是什么将 lstat()
或 fstat()
替换为(它们的 windows 对应项)。
编辑:为此 post 添加了缩写代码;如果有什么不清楚 please go to the original post
#ifdef WIN32
struct _stat buf;
#else
struct stat buf;
#endif //WIN32
FILE *fp;
char data[2560];
// Make sure file exists and is readable
#ifdef WIN32
if (_access(file.c_str(), R_OK) == -1) {
#else
if (access(file.c_str(), R_OK) == -1) {
#endif //WIN32
char message[2560];
sprintf(message, "File '%s' Not Found or Not Readable", file.c_str());
throw message;
}
// Get the file status information
#ifdef WIN32
if (_stat(file.c_str(), &buf) != 0) {
#else
if (stat(file.c_str(), &buf) != 0) {
#endif //WIN32
char message[2560];
sprintf(message, "File '%s' No Status Available", file.c_str());
throw message;
}
// Open the file for reading
fp = fopen(file.c_str(), "r");
if (fp == NULL) {
char message[2560];
sprintf(message, "File '%s' Cound Not be Opened", file.c_str());
throw message;
}
// Read the file
MvString s, ss;
while (fgets(data, sizeof(data), fp) != (char *)0) {
s = data;
s.trimBoth();
if (s.compare( 0, 5, "GROUP" ) == 0) {
//size_t t = s.find_last_of( ":" );
size_t t = s.find( ":" );
if (t != string::npos) {
ss = s.substr( t+1 ).c_str();
ss.trimBoth();
ss = ss.substr( 1, ss.length() - 3 ).c_str();
group_list.push_back( ss );
}
}
}
// Close the file
fclose(fp);
}
检查文件是否存在并可以打开的可靠方法是尝试打开它。如果打开,一切正常。如果没有打开,可以考虑花时间分析一下哪里出了问题。
access()
函数正式提出了与您所想的不同的问题;它询问 'can the real user ID or the real group ID access the file',但程序将使用有效用户 ID 或有效组 ID 来访问文件。如果您的程序不是 运行 SUID 或 SGID,并且不是从 运行 SUID 或 SGID 程序启动的——这是正常情况——那么就没有区别。但是问题不一样。
使用stat()
或
lstat()
似乎没有帮助。特别是,lstat()
只告诉您是否从符号链接开始,但代码并不关心这一点。
access()
和stat()
调用都为您提供了TOCTOUwindows漏洞;该文件可以在他们报告文件存在后删除,或者在他们报告文件不存在后创建。
你应该直接调用 fopen()
and see whether it works; the code will be simpler and more resistant to TOCTOU problems. You might need to consider whether to use open()
with all its extra controls (O_EXCL
, etc), and then convert the file descriptor to a file pointer (fdopen()
).
所有这些都适用于 Unix 端。
细节会有所不同,但在 Windows 方面,您仍然最好尝试打开文件并对失败做出适当的反应。
在两个系统中,确保提供给 open 函数的选项是合适的。
我已经处理了几个星期的问题,现在正在更新需要独立于系统的 20 年代码(在 Linux 和 Windows 上工作)。它涉及Time-of-Check、Time-of-Use (TOCTOU)问题。我发了一个帖子 here,但并没有走多远,在反复思考了一段时间并深入研究了问题之后,我想我对我的问题的理解更好了一些。也许我也可以问得好一点...
据我所知,代码需要检查文件是否存在,如果可以访问,则打开文件,进行一些操作,最后关闭文件。执行此操作的最佳方法似乎是调用 lstat()
、调用 fopen()
、调用 fstat()
(排除 TOCTOU),然后操作并关闭文件。
但是,我一直相信 lstat()
和 fstat()
是 POSIX 定义的, 不是 C 标准定义的,裁定将它们用于与系统无关的程序,就像 open()
不应该用于交叉兼容性一样。你将如何实施?
如果你看at my first post,你会看到 20 年前的开发人员使用 C 预处理器将代码切割成交叉兼容的部分,但即使我这样做了,我也不知道是什么将 lstat()
或 fstat()
替换为(它们的 windows 对应项)。
编辑:为此 post 添加了缩写代码;如果有什么不清楚 please go to the original post
#ifdef WIN32
struct _stat buf;
#else
struct stat buf;
#endif //WIN32
FILE *fp;
char data[2560];
// Make sure file exists and is readable
#ifdef WIN32
if (_access(file.c_str(), R_OK) == -1) {
#else
if (access(file.c_str(), R_OK) == -1) {
#endif //WIN32
char message[2560];
sprintf(message, "File '%s' Not Found or Not Readable", file.c_str());
throw message;
}
// Get the file status information
#ifdef WIN32
if (_stat(file.c_str(), &buf) != 0) {
#else
if (stat(file.c_str(), &buf) != 0) {
#endif //WIN32
char message[2560];
sprintf(message, "File '%s' No Status Available", file.c_str());
throw message;
}
// Open the file for reading
fp = fopen(file.c_str(), "r");
if (fp == NULL) {
char message[2560];
sprintf(message, "File '%s' Cound Not be Opened", file.c_str());
throw message;
}
// Read the file
MvString s, ss;
while (fgets(data, sizeof(data), fp) != (char *)0) {
s = data;
s.trimBoth();
if (s.compare( 0, 5, "GROUP" ) == 0) {
//size_t t = s.find_last_of( ":" );
size_t t = s.find( ":" );
if (t != string::npos) {
ss = s.substr( t+1 ).c_str();
ss.trimBoth();
ss = ss.substr( 1, ss.length() - 3 ).c_str();
group_list.push_back( ss );
}
}
}
// Close the file
fclose(fp);
}
检查文件是否存在并可以打开的可靠方法是尝试打开它。如果打开,一切正常。如果没有打开,可以考虑花时间分析一下哪里出了问题。
access()
函数正式提出了与您所想的不同的问题;它询问 'can the real user ID or the real group ID access the file',但程序将使用有效用户 ID 或有效组 ID 来访问文件。如果您的程序不是 运行 SUID 或 SGID,并且不是从 运行 SUID 或 SGID 程序启动的——这是正常情况——那么就没有区别。但是问题不一样。
使用stat()
或
lstat()
似乎没有帮助。特别是,lstat()
只告诉您是否从符号链接开始,但代码并不关心这一点。
access()
和stat()
调用都为您提供了TOCTOUwindows漏洞;该文件可以在他们报告文件存在后删除,或者在他们报告文件不存在后创建。
你应该直接调用 fopen()
and see whether it works; the code will be simpler and more resistant to TOCTOU problems. You might need to consider whether to use open()
with all its extra controls (O_EXCL
, etc), and then convert the file descriptor to a file pointer (fdopen()
).
所有这些都适用于 Unix 端。
细节会有所不同,但在 Windows 方面,您仍然最好尝试打开文件并对失败做出适当的反应。
在两个系统中,确保提供给 open 函数的选项是合适的。