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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:12:04