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

函数返回时检测到栈溢出,TCP未知大小JSON读取函数故障排查

栈溢出问题排查:读取未知大小TCP JSON报文函数崩溃分析

问题描述

我编写了一个读取未知大小TCP报文的函数,逻辑是用MSG_PEEK获取JSON消息(通过循环检测"}"判断是否完整)所需的大小,之后再不带MSG_PEEK执行一次读取清理缓冲区。函数末尾的两次打印正常,但调用后程序返回时立即崩溃,提示*** stack smashing detected ***: terminated。我已经分配了content_size + 1的空间,recv传入content_size,理论上content_buffer不会溢出,请求排查原因。

代码片段

char *read_socket_raw(int socket)
{
    if (socket == -1)
    {
        fprintf(stderr, "Reading socket failed.\n");
        return NULL;
    }

    char *content_buffer = NULL;
    unsigned short content_size;

    why2_bool empty_buffer = 0; //WHETHER MSG_PEEK SHOULD BE USER OR NOT

    do
    {
        //FIND THE SENT SIZE
        content_size = 0;
        if (ioctl(socket, FIONREAD, &content_size) < 0 || content_size <= 0) continue;

        //ALLOCATE
        content_buffer = realloc(content_buffer, content_size + 1);

        read_section:

        //READ JSON MESSAGE
        if (recv(socket, content_buffer, content_size, !empty_buffer ? MSG_PEEK : 0) != content_size) //READ THE MESSAGE BY CHARACTERS
        {
            fprintf(stderr, "Socket probably read wrongly!\n");
        }

        if (empty_buffer) goto return_section; //STOP LOOPING
    } while (content_buffer == NULL || strncmp(content_buffer + (content_size - 2), "\"}", 2) != 0);

    //REMOVE JUNK FROM BUFFER (CUZ THE MSG_PEEK FLAG)
    empty_buffer = 1;
    goto read_section;

    return_section:

    content_buffer[content_size] = '\0';

    printf("'%s'\n", content_buffer);
    printf("'%p'\n\n", content_buffer);

    return content_buffer;
}

问题根源分析

1. content_size类型溢出导致缓冲区过小

content_size被定义为unsigned short(最大取值65535),但ioctl(socket, FIONREAD, &content_size)中,FIONREAD返回的套接字接收缓冲区字节数是4字节整数类型(通常是int或unsigned int)。当缓冲区中的数据量超过65535时,content_size会发生溢出,变成一个远小于实际数据量的数值。此时你用这个溢出后的小值调用realloc分配空间,后续recv读取实际大得多的数据时,直接写爆缓冲区,触发栈溢出检测。

2. 越界内存访问

循环条件中的strncmp(content_buffer + (content_size - 2), "\"}", 2) != 0存在严重风险:如果content_size小于2(比如初始数据量不足),content_size - 2会因为unsigned short的无符号特性溢出成一个超大正数,导致content_buffer + 超大正数访问到非法内存区域,这也是触发内存错误的常见原因。

3. goto逻辑复用旧值引发的问题

当empty_buffer = 1后通过goto read_section执行清理读取时,你没有重新调用ioctl获取当前套接字缓冲区的最新数据量,而是直接复用了循环中最后一次的content_size。如果此时缓冲区数据量已经变化(比如有新数据写入),recv读取的字节数可能和content_size不匹配,进而导致缓冲区越界。

4. realloc错误处理缺失

content_buffer = realloc(content_buffer, content_size + 1);没有检查返回值,一旦realloc失败返回NULL,后续对content_buffer的操作会引发空指针访问,虽然这不是当前崩溃的直接原因,但属于严重内存安全隐患。

修复方案

1. 修正content_size类型

将unsigned short content_size;改为int content_size;或size_t content_size;,匹配FIONREAD的返回类型,避免溢出:

int content_size;

2. 增加越界判断

在执行strncmp前先检查content_size是否足够,避免非法内存访问:

do
{
    // ... 原有代码
    if (content_size >= 2 && strncmp(content_buffer + content_size - 2, "\"}", 2) == 0) {
        break;
    }
} while (1);

3. 重构清理读取逻辑

去掉goto,重新获取最新数据量后再执行清理读取:

// 替代原来的goto清理逻辑
empty_buffer = 1;
content_size = 0;
if (ioctl(socket, FIONREAD, &content_size) >= 0 && content_size > 0) {
    if (recv(socket, content_buffer, content_size, 0) != content_size) {
        fprintf(stderr, "Socket read error when clearing buffer!\n");
    }
}

4. 增加realloc错误处理

用临时变量接收realloc返回值,避免内存泄漏:

char *temp_buf = realloc(content_buffer, content_size + 1);
if (temp_buf == NULL) {
    fprintf(stderr, "Memory allocation failed!\n");
    free(content_buffer);
    return NULL;
}
content_buffer = temp_buf;

内容的提问来源于stack exchange,提问作者ENGO_150

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:05:15