函数返回时检测到栈溢出,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

