从文件读取时如何停止堆栈缓冲区溢出?

时间:2019-10-25 19:07:26

标签: c

我正在从.txt文件中读取文件,并以与文件本身相同的大小将其保存到char数组中。这足以阻止发生不受控制的堆栈缓冲区溢出吗?

我已经尝试使用固定大小的缓冲区,但是现在我知道这就是发生溢出的原因。

FILE *inputFP = NULL;

inputFP = fopen(input_file, "r");
if (inputFP == NULL)
    return 1;
fseek(inputFP, 0, SEEK_END);
long fileSize = ftell(inputFP);
fseek(inputFP, 0, SEEK_SET);
char buffer[fileSize+20];

while ((ch = fgetc(inputFP)) != EOF) 
{
     buffer[i] = ch;
     i++;
}

fprintf(outputFP, buffer, "%s");

一切正常,但我担心输入文件太大,以至于发生不好的事情。

3 个答案:

答案 0 :(得分:1)

  

我正在读取.txt文件,以将其保存到与文件本身相同大小的char数组中。这足以阻止发生不受控制的堆栈缓冲区溢出吗?

通过避免在数组外进行写操作来防止 buffer 溢出。他们是一件很不好的事情 TM

当您耗尽线程/进程/程序中为堆栈分配的可用页面时,会发生

堆栈溢出。通常,堆栈的大小非常小(将其视为1 MiB的量级)。这些也是错误的,但是它们只会使您的程序崩溃。

long fileSize = ftell(inputFP);
...
char buffer[fileSize+20];

这是一个可变长度数组(VLA)。它分配动态(在编译时未知)堆栈空间。如果使用正确,则不会出现缓冲区溢出,但是会出现 stack 溢出,因为文件大小不受限制。


您应该做的是使用固定大小的缓冲区而不是使用VLA,并读取文件的块而不是整个文件。如果确实需要将整个文件存储在内存中,则可以尝试为其分配堆内存(malloc),或者可以为它分配内存映射(mmap)。

答案 1 :(得分:0)

如评论中所述,malloc()可以防止缓冲区溢出。

请注意,请始终尝试逐步读取文件,并且不要将其完全加载到内存中。对于大文件,您会遇到麻烦,因为您的进程将无法分配该数量的内存。例如,几乎不可能将10GB的视频文件完全加载到内存中。此外,通常每个大数据文件都是结构化的,因此您可以逐步读取小块数据。

答案 2 :(得分:0)

限制缓冲区溢出的方法是仔细控制写入任何缓冲区的内存量。

如果您说(用伪代码):

filesize = compute_file_size(filename);
buffer = malloc(filesize);
read_entire_file_into(buffer, filename);

那么您就遇到了一个巨大的,潜在的缓冲区溢出问题。根本的问题不是您分配的缓冲区恰好与文件的大小完全匹配(尽管这可能是一个问题)。问题不在于您事先计算了文件的大小(尽管这可能是一个问题)。不,根本的问题是在假想呼叫中

read_entire_file_into(buffer, filename);

没有告诉read_entire_file_into函数缓冲区有多大。这可能是read_entire_file_into函数的问题,不是您的问题,但最重要的是,将任意数量的数据写入固定大小的缓冲区而不允许指定该缓冲区的大小的函数是灾难等待发生。这就是为什么臭名昭著的gets()函数已从C标准中删除的原因。这就是不建议使用strcpy函数的原因,并且只能在经过仔细控制的情况下使用(如果有的话)。因此,不推荐使用%s的{​​{1}}和%[...]格式说明符。

另一方面,如果您的代码看起来像这样:

scanf

-关键是要再次告知filesize = compute_file_size(filename); buffer = malloc(some_random_number); read_entire_file_into_with_limit(buffer, some_random_number, filename); 函数(假设),在这种情况下,即使read_entire_file_into_with_limit函数得到错误的答案,即使您为compute_file_size使用了完全不同的大小,也已确保不会溢出缓冲区。

从假设的伪代码转换为真实的实际代码:您没有显示代码中实际从文件中读取内容的部分。如果您要调用bufferfread来读取文件,并且要正确地将fgets变量作为fileSize的大小传递给这些函数,则您已充分保护自己免受缓冲区溢出的侵害。但是,另一方面,如果您要调用buffer或循环调用gets并向getc中写入字符,直到到达buffer(但不检查(EOF中读取的字符数),那么您确实有一个很大的潜在缓冲区溢出问题,您需要重新考虑策略并重写代码。


您的代码存在第二个问题,即您正在将缓冲区作为可变长度数组(VLA)分配到堆栈上(可以这么说)。但是真正的大型堆栈分配数组将失败-不是因为缓冲区溢出,而是因为它们实际上太大了。因此,如果您实际上想将整个文件读入内存,则肯定要使用fileSize,而不是VLA。 (而且,如果您不介意依赖于操作系统的解决方案,则可能需要研究内存映射文件技术,例如malloc调用。)


您已经更新了代码,因此现在我可以更新此答案。您发布的文件读取循环很危险-实际上,这正是我撰写本文时所想到的

  

循环调用mmap并将字符写入缓冲区直到到达getc(但不检查根据EOF读取的字符数)

您应该用任一代码替换该代码

fileSize

while ((ch = getc(inputFP)) != EOF) 
{
     if(i >= fileSize) {
         fprintf(stderr, "buffer overflow!\n");
         break;
     }

     buffer[i] = ch;
     i++;
}

或者,您可以采用完全不同的方法。大多数时候,不需要一次将整个文件读入内存。在大多数情况下,一次读取一行,一次读取一个块,甚至一次读取一个字符,处理并写出每个片段然后再进行下一个片段就足够了。这样,您可以处理任何大小的文件,而无需事先弄清楚文件的大小,也不需要分配大的缓冲区,也不需要担心缓冲区溢出。

我今天没有时间向您展示如何做,但是在其他一些答案中有一些提示和建议。