strcpy栈溢出疑问:已分配足够内存为何仍报错?
我在运行一段模拟mkdir命令的代码时遇到了**stack smashing(栈溢出)**错误,代码如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX_NAME 12 #define MAX_DIR 10 struct Node { char name[MAX_NAME]; struct Node *subdirs[MAX_DIR]; }; int mkdir(struct Node* root, char* path) { char current_path[MAX_NAME+1]; strcpy(current_path, path); char *next_path = strchr(current_path+1, '/'); char *token; if (!next_path) { int i = 0; while(i < MAX_DIR && root->subdirs[i]) ++i; token = strtok(path, "/"); root->subdirs[i] = malloc(sizeof(struct Node)); strcpy(root->subdirs[i]->name, current_path+1); return 1; } token = strtok(path, "/"); while( token != NULL) { for(int i = 0; i < MAX_DIR; i++) { if (root->subdirs[i] && !strcmp(root->subdirs[i]->name, token)) return mkdir(root->subdirs[i], next_path); } token = strtok(NULL, "/"); } printf("not found\n"); return 0; } int main() { char input1[] = "/test"; char input2[] = "/test/bar"; char input3[] = "/lib/bar"; char input4[] = "/foo"; char input5[] = "/test/bar/foaa"; struct Node *root = malloc(sizeof(struct Node)); mkdir(root, input1); mkdir(root, input2); mkdir(root, input5); return 0; }
这段代码用于模拟mkdir命令:比如输入"/test/bar/foaa"时,若/test/和/test/bar/已存在,会在/test/bar/下创建foaa目录。
错误触发在这行代码:
strcpy(root->subdirs[i]->name, current_path+1);
我发现把MAX_NAME增大到15就能解决问题,但"foaa"(包含终止符\0仅5个字符)理论上完全能放进MAX_NAME=12的数组里,想知道我的假设或代码哪里出了问题?
问题根源
current_path数组越界,直接破坏栈结构
代码中current_path的大小是MAX_NAME+1=13,但传入的路径比如input5是"/test/bar/foaa",长度(含\0)是14,strcpy会把超出的字符写入current_path的栈内存之外,直接触发栈溢出检测。这是最核心的问题——你关注的是最终复制的目录名长度,却忽略了整个路径字符串复制到current_path时已经越界。strtok修改原始输入字符串,引发连锁错误strtok会将原始字符串中的分隔符替换为\0,而main里的input数组是栈上的局部变量。第一次调用mkdir修改input1后,后续调用传入的input2、input5可能已经被意外修改(栈内存被破坏后,相邻变量的值会错乱),导致后续字符串操作进一步越界,最终错误在strcpy行暴露出来。未初始化的结构体成员
malloc分配Node结构体后,subdirs数组是未初始化的野指针,while(i < MAX_DIR && root->subdirs[i]) ++i;可能会访问非法内存,加重栈损坏的不可预见性。
修复方案
- 安全复制路径到
current_path:用strncpy替代strcpy,确保不越界,同时手动添加终止符:strncpy(current_path, path, MAX_NAME); current_path[MAX_NAME] = '\0'; - 避免修改原始输入:不要直接对
main中的输入数组使用strtok,可以先复制一份到临时缓冲区再处理,或者使用线程安全的strtok_r。 - 初始化结构体:
malloc后用memset初始化整个结构体,避免野指针:struct Node *new_node = malloc(sizeof(struct Node)); memset(new_node, 0, sizeof(struct Node)); root->subdirs[i] = new_node;
内容的提问来源于stack exchange,提问作者Franks

