C语言服务器配置解析:getline()处堆缓冲区溢出问题排查
解决-fsanitize检测到getline堆缓冲区溢出的问题
问题场景
编写本地服务器配置解析函数时,正常编译运行无异常,但使用-fsanitize编译后,getline()所在的while循环触发堆缓冲区溢出检测。相关代码如下:
struct container *configParse(FILE *file) { char *line = NULL; size_t n; char *token = NULL; char *saveptr = NULL; struct container *head = NULL; struct container *container = NULL; int key = 0; int first = 1; while ((getline(&line, &n, file)) != -1) { token = strtok_r(saveptr, " =\n\r", &line); while (token != NULL) { if (token[0] == '[') { if (first) { container = container_init(); container->title = token; head = container; first = 0; } else { container = container_add_back(container); container = container->next; container->item = NULL; container->title = token; } key = 0; } else { if (key == 0) { if (container->item == NULL) { container->item = items_init(); container->item->key = token; } else { struct item *itemcpy = container->item; while (itemcpy->next != NULL) { itemcpy = itemcpy->next; } itemcpy->next = items_init(); itemcpy->next->key = token; } key = 1; } else { struct item *itemcpy = container->item; while (itemcpy->next != NULL) { itemcpy = itemcpy->next; } itemcpy->value = token; key = 0; } } token = strtok_r(NULL, " =\n\r", &line); } } container_print(head); printf("\n*****Parsing du .conf*****\n\n"); if (isvalid(head)) printf("Parsing OK\n"); else { printf("Parsing KO\n"); return NULL; } return head; }
错误分析
核心问题是**strtok_r的参数使用完全错误**,直接破坏了getline分配的堆缓冲区指针:
- 第一次调用
strtok_r时,第一个参数应传入待分割的字符串line,而非状态变量saveptr; strtok_r的第三个参数是用于保存分割状态的指针,必须传入&saveptr,但代码中错误传入&line——这会导致strtok_r修改line的值(getline的缓冲区指针),当下一次getline尝试操作这个被篡改的指针时,就会触发堆缓冲区溢出。
正常编译未触发崩溃只是巧合,内存错误实际存在,-fsanitize的内存检测机制精准捕捉到了这个非法操作。
修正后的代码
struct container *configParse(FILE *file) { char *line = NULL; size_t n; char *token = NULL; char *saveptr = NULL; struct container *head = NULL; struct container *container = NULL; int key = 0; int first = 1; while ((getline(&line, &n, file)) != -1) { // 修正1:第一次调用strtok_r,传入待分割的line,状态指针用&saveptr token = strtok_r(line, " =\n\r", &saveptr); while (token != NULL) { if (token[0] == '[') { if (first) { container = container_init(); container->title = token; head = container; first = 0; } else { container = container_add_back(container); container = container->next; container->item = NULL; container->title = token; } key = 0; } else { if (key == 0) { if (container->item == NULL) { container->item = items_init(); container->item->key = token; } else { struct item *itemcpy = container->item; while (itemcpy->next != NULL) { itemcpy = itemcpy->next; } itemcpy->next = items_init(); itemcpy->next->key = token; } key = 1; } else { struct item *itemcpy = container->item; while (itemcpy->next != NULL) { itemcpy = itemcpy->next; } itemcpy->value = token; key = 0; } } // 修正2:后续调用strtok_r,状态指针仍用&saveptr token = strtok_r(NULL, " =\n\r", &saveptr); } // 显式释放line,避免循环累积内存泄漏 free(line); line = NULL; n = 0; } container_print(head); printf("\n*****Parsing du .conf*****\n\n"); if (isvalid(head)) printf("Parsing OK\n"); else { printf("Parsing KO\n"); return NULL; } return head; }
额外说明
- 修正代码添加了
free(line)操作,避免循环中累积内存泄漏; strtok_r会直接修改传入的字符串(用'\0'替换分隔符),所以container->title、item->key、item->value保存的是指向line缓冲区的指针。如果需要长期保存这些值,需手动复制字符串(比如用strdup),否则后续getline覆盖缓冲区或free(line)后,这些指针会变成野指针。
内容的提问来源于stack exchange,提问作者n_marc
相关产品推荐
相关产品推荐

