堆缓冲区溢出与 fprintf

heap-buffer-overflow with fprintf

我正在更新我的问题,非常抱歉以错误的方式提问。

现在我可以将我的问题提炼成一段独立的代码:

#include <stdio.h>
#include <stdlib.h>

static __inline__ char* fileRead(char* file){
     FILE* fp;
     long fileSize;
     char* fileContents;

     fp = fopen ( file , "rb" );
     if(!fp){
          perror(file);
          exit(1);}

     /* this block writes the size of the file in fileSize */
     fseek( fp , 0L , SEEK_END);
     fileSize = ftell( fp );
     rewind( fp );

     /* allocate memory for entire content */
     fileContents = malloc(fileSize+1);
     if(!fileContents){
          fclose(fp);
          fputs("memory alloc fails",stderr);
          exit(1);}

     /* copy the file into the buffer */
     if(fread(fileContents, fileSize, 1, fp) != 1){
          fclose(fp);
          free(fileContents);
          fputs("entire read fails",stderr);
          exit(1);}

     /* close the file */
     fclose(fp);
     return fileContents;}

int main (){
     char* head10 = "";
     char* fileName = "testhtml.html";
     FILE* out = fopen(fileName, "w");

     head10 = fileRead("head10.html");
          printf("%s\n", head10);

     out = fopen(fileName, "wb");
          fprintf(out, "%s\n", head10);
          fclose(out);

     free(head10);
return 0;}

这里是 head10.html 文件。

我正在用 -fsanitize=address 编译它,但出现堆缓冲区溢出。 错误似乎是在 fprintf(out, "%s\n", head10); 行引起的。 head10 是唯一的 malloc 变量,所以这是有道理的。

我可以使用 printf 毫无问题地打印它,但是当我尝试使用 fprintf 将它写入文件时,会生成堆缓冲区溢出。

===编辑=== 看起来问题出在将 fprintf 与 malloc'd var 一起使用,因为 fprintf 本身在后台使用 malloc,因此原始分配丢失,内存泄漏。

所以我在没有使用 malloc 的情况下重写了我的函数:

#define _POSIX_C_SOURCE 200809L /* for getline() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static __inline__ void fileReset(char* fileName){
     FILE* out = fopen(fileName, "w");
     fwrite("" , sizeof(char) , strlen("") , out );
     fclose(out);}

static __inline__ void fileAppend(char* fileName, char* string){
     FILE* out = fopen(fileName, "a"); /* using "a" to APPEND */
     if(fwrite(string , sizeof(char) , strlen(string) , out ) != strlen(string)){
               printf("==file write error\n");
               exit(EXIT_FAILURE);}
     fclose(out);}

static __inline__ void fileAppendFile(char* source, char* dest){
     FILE* in = fopen(source, "r");
     char *line = NULL;
    size_t len = 0;
    size_t read;

     while ((read = getline(&line, &len, in)) != -1) {
          fileAppend(dest, line);}

        free(line);
        fclose(in);}

int main (){
     char* fileName = "testhtml.html";
     char* theme = "dark";

     fileReset(fileName);
     fileAppendFile("head10.html", fileName);
     fileAppend(fileName, theme);
return 0;}

非常感谢所有的帮助,在这里非常菜鸟,不知道 -lasan 是什么,现在我知道这是一个多么宝贵的工具!

==编辑-2== 正如 EmployedRussian 指出的那样,原始代码中的问题不是 fprintf,而是缺少终止符 '\0',请查看下面的答案,它确实修复了我的原始代码:)

Looks like the problem came from using fprintf with a malloc'd var, as fprintf itself uses malloc under the hood, so the original alloc gets lost, and memory leaks.

恐怕你在这里吸取了错误的教训。

虽然 fprintf 确实可以在幕后使用 malloc,但您的问题与此无关

我创建了一个包含 abc\n(4 个字符)的 head10.html 文件。 运行 生成该输入文件的程序:

==10173==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000015 at pc 0x7fb5db2c7054 bp 0x7ffd44e74de0 sp 0x7ffd44e74590
READ of size 6 at 0x602000000015 thread T0
    #0 0x7fb5db2c7053  (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x4d053)
    #1 0x5654101dd435 in main /tmp/foo.c:43
    #2 0x7fb5db0dde0a in __libc_start_main ../csu/libc-start.c:308
    #3 0x5654101dd199 in _start (/tmp/a.out+0x1199)

0x602000000015 is located 0 bytes to the right of 5-byte region [0x602000000010,0x602000000015)
allocated by thread T0 here:
    #0 0x7fb5db381628 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x107628)
    #1 0x5654101dd2db in fileRead /tmp/foo.c:20
    #2 0x5654101dd425 in main /tmp/foo.c:42
    #3 0x7fb5db0dde0a in __libc_start_main ../csu/libc-start.c:308

所以问题是您分配了 5 个字节(如预期的那样),但是 fprintf 试图从该缓冲区读取第 6 个字符。

为什么要这么做?因为您使用的格式:%s 期望找到一个终止 NUL 字符(即它期望一个正确终止的 C 字符串),并且您给它一个指向 non[=43 的指针=] 结尾的字符串,包含以下字节:

a b c \n X

第五个字节包含什么值?它是未定义的(它来自 malloc,并且没有写入任何值)。由于该值是 not NULfprintf 尝试读取 next (第 6 个)字节,这就是地址Sanitizer 发出错误信号并中止您的程序。

正确的解决方法是 NUL 终止字符串,如下所示:

 if (fread(fileContents, fileSize, 1, fp) != 1){ ... handle error
 fileContents[fileSize] = '[=12=]';  // NUL-terminate the string.