You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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分配的堆缓冲区指针:

  1. 第一次调用strtok_r时,第一个参数应传入待分割的字符串line,而非状态变量saveptr;
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 18:45:47