AddressSanitizer检测栈缓冲区溢出,print_list函数越界访问排查
栈缓冲区溢出问题分析:命令行字符串大小写转换程序的ASan报错
问题描述
编写了一个将命令行输入字符串按首字母大小写统一转换的C程序,程序可正常运行,但使用AddressSanitizer检测时,提示print_list函数存在栈缓冲区越界访问。误以为while (data[count] != NULL)的循环条件能防止越界,因为数组元素数量与输入字符串数一致,遍历结束时data[count]应为NULL,实际却触发了报错。
源代码
#include <stdlib.h> #include <stdio.h> #include <string.h> #define TRUE 1 #define FALSE 0 void print_list(char **data); int main(int argc, char *argv[]) { if(argv[1] == NULL) { fprintf(stderr, "Error: No strings were entered\n"); return EXIT_FAILURE; } int arg_num = 1; char *output[argc - 1]; while (argv[arg_num] != NULL) { int count = 0; char *line; line = malloc(sizeof(char)); int size = sizeof(char); int word_flag = FALSE; int upper_flag = FALSE; int lower_flag = FALSE; while (argv[arg_num][count] != '\0') { size = size + sizeof(char); line = realloc(line, size); if (argv[arg_num][count] >= 65 && argv[arg_num][count] <= 90) { if (word_flag == FALSE && upper_flag == FALSE) { word_flag = TRUE; upper_flag = TRUE; line[count] = argv[arg_num][count]; } else if (word_flag == TRUE && upper_flag == FALSE) { line[count] = (argv[arg_num][count] + 32); } else { line[count] = argv[arg_num][count]; } } else if (argv[arg_num][count] >= 97 && argv[arg_num][count] <= 122) { if (word_flag == FALSE && lower_flag == FALSE) { word_flag = TRUE; lower_flag = TRUE; line[count] = argv[arg_num][count]; } else if (word_flag == TRUE && lower_flag == FALSE) { line[count] = (argv[arg_num][count] - 32); } else { line[count] = argv[arg_num][count]; } } else if (argv[arg_num][count] == ' ' || argv[arg_num][count] == '\t') { word_flag = FALSE; upper_flag = FALSE; lower_flag = FALSE; line[count] = argv[arg_num][count]; } else { line[count] = argv[arg_num][count]; } count++; } line[count] = '\0'; output[arg_num - 1] = line; arg_num++; } print_list(output); return EXIT_SUCCESS; } void print_list(char **data) { int count = 0; while (data[count] != NULL) { printf("%s\n", data[count]); free(data[count]); count++; } }
AddressSanitizer 报错信息
==222362==ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address 0x7ffc487cc7a8 at pc 0x000000401f8f bp 0x7ffc487cc740 sp 0x7ffc487cc738 READ of size 8 at 0x7ffc487cc7a8 thread T0 #0 0x401f8e in print_list (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401f8e) #1 0x401e87 in main (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401e87) #2 0x7f0e7364a50f in __libc_start_call_main (/lib64/libc.so.6+0x2750f) #3 0x7f0e7364a5c8 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x275c8) #4 0x401144 in _start (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401144) Address 0x7ffc487cc7a8 is located in stack of thread T0 SUMMARY: AddressSanitizer: dynamic-stack-buffer-overflow (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401f8e) in print_list Shadow bytes around the buggy address: 0x1000090f18a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f18b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f18c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f18d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f18e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x1000090f18f0: ca ca ca ca 00[cb]cb cb cb cb cb cb 00 00 00 00 0x1000090f1900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f1910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f1920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f1930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000090f1940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==222362==ABORTING
问题根源
你对output数组的NULL终止逻辑存在误解:
output是栈上分配的数组,大小为argc-1,仅填充了前argc-1个元素(对应命令行输入的argv[1]到argv[argc-1]),数组末尾没有手动设置NULL。- 栈上未初始化的内存区域是随机值,不是NULL。当
print_list里的循环执行到count == argc-1时,data[count]访问的是数组的越界位置,这个位置的栈内存不是NULL,循环会继续往后读,直到触及栈的红区(报错中的cb标记,对应栈右红区),触发ASan的缓冲区溢出检测。
修复方案
方案1:让数组以NULL结尾
修改output数组的定义,要么扩大容量并手动添加NULL,要么初始化时将所有元素设为NULL:
// 方式1:扩大数组容量,最后添加NULL char *output[argc]; // 容量为argc,比输入数多1 // ... 填充完所有元素后 output[arg_num - 1] = NULL; // 循环结束后设置末尾为NULL // 方式2:初始化数组时将所有元素设为NULL char *output[argc - 1] = {0}; // 栈数组用{0}初始化,所有元素默认设为NULL
这样当循环到count == argc-1时,data[count]为NULL,循环终止,不会越界。
方案2:传递数组元素数量给print_list
修改print_list函数,增加元素个数参数,避免依赖NULL判断:
void print_list(char **data, int num_elements) { int count = 0; while (count < num_elements) { printf("%s\n", data[count]); free(data[count]); count++; } }
在main中调用时传入元素数量:
print_list(output, argc - 1);
额外优化建议
- 内存分配优化:原代码中每次循环都调用
realloc效率较低,可以先获取原字符串长度,一次性分配足够内存:int len = strlen(argv[arg_num]); char *line = malloc(len + 1); // +1用于存储字符串终止符'\0' - 大小写转换可读性:使用
<ctype.h>中的toupper/tolower函数替代直接操作ASCII码值,代码更清晰且兼容多字符集:#include <ctype.h> // 示例:小写转大写 line[count] = toupper(argv[arg_num][count]);
内容的提问来源于stack exchange,提问作者Evan Lewis
相关产品推荐
相关产品推荐

