【软件安全】Understanding Null-Termination Errors(字符串未正确以 ‘\0‘ 结束的错误)


代码 1:strncpy(arr, "0123456789", sizeof(arr));

int main(int argc, char *argv[])
{
    char arr[10];
    strncpy(arr, "0123456789", sizeof(arr));
    printf("%s\n", arr);
}

问题是什么?What’s wrong?

EN:
arr has size 10. strncpy copies 10 bytes from "0123456789".
But C strings need one extra byte for '\0'. No null terminator is added.
printf("%s", arr) keeps reading memory past arr until it randomly finds a '\0'.

CN:
arr 长度是 10,而 "0123456789" 正好 10 个字符。strncpy 会复制满 10 个字节,没地方放 '\0'
printf("%s", arr) 就会从 arr 开始一直往后读内存,直到碰到某个巧合出现的 0 为止,可能打印垃圾。

比喻:
你有 10 个格子的名字表,全写满了,但忘了画“结束线”;阅读的人会继续往后把别的内容也当名字读出来。


代码 2:引入第二个数组,演示串联输出

int main(int argc, char *argv[])
{
    char arr[10];
    char arr2[10];
    strncpy(arr, "0123456789", sizeof(arr));
    strncpy(arr2, "987654321\0", sizeof(arr2));
    printf("%s\n", arr);
}

为什么会输出 0123456789987654321

EN:

  • arr is not null-terminated (same bug as before).
  • arr2 is null-terminated.
  • When printing arr, printf starts at arr, prints 0123456789,
    then keeps reading into the memory where arr2 lives, prints 987654321,
    finally stops at arr2’s '\0'.

CN:

  • arr 依然没有 '\0'
  • arr2'\0'
  • printf("%s", arr)arr 开始打印 0123456789,停不下来,就继续读到紧挨着的 arr2,把 987654321 也当成 arr 的一部分输出,直到遇到 arr2'\0' 才停。

比喻:
名单 A 没有写“结束线”,后面紧接名单 B,于是阅读时把两份名单拼成了一份。

这是典型 Null-Termination 错误 + 邻接栈变量被意外曝光


代码 3:理论总结:当源长度 ≥ 目标长度

“Where the length of source string is the same or greater than target string:

  • source gets truncated
  • target may not be properly null-terminated”

EN:
If source length ≥ destination capacity, using strncpy(dest, src, sizeof(dest)) is dangerous:

  • The copied content fills the buffer.
  • No '\0' is guaranteed.
  • Printing / using as C-string is unsafe.

CN:
当源串长度和目标数组一样甚至更长时,如果直接 strncpy(dest, src, sizeof(dest))

  • 内容会被截断;
  • 可能没有 '\0'
  • 后续当作字符串使用非常危险。

核心点:一定要给 '\0' 预留一个位置。


代码 4:修复版本:手动添加 '\0'

int main(int argc, char *argv[])
{
    char arr[10];
    strncpy(arr, "0123456789", sizeof(arr) - 1);
    arr[sizeof(arr) - 1] = '\0';
    printf("%s\n", arr);
}

为什么这个是安全的?

EN:

  • Only 9 characters are copied.
  • The last byte arr[9] is set to '\0'.
  • printf stops correctly at the terminator.

CN:

  • 只复制 9 个字符,留出最后一格;
  • 手动把 arr[9] 设成 '\0'
  • printf 知道在哪停,不会串出边界。

比喻:
名单最后画了“结束线”,后面再有别的内容也不会被误读。


代码 5:fgets 示例一(有坑版本)

void main()
{
    char username[8];
    puts("Please enter your username:");
    fgets(username, 8, stdin);
    printf("Your username is %s\n", username);
}

这里要注意什么?

EN (based on slides idea):

  • fgets(buf, n, ...) reads at most n-1 chars, then adds '\0'.
  • Here username has size 8, n = 8 → OK if你真的分配了8个字节
  • 真正的坑往往是:写错参数(比如 n 大于数组大小,或数组太小,逻辑搞混),就会导致潜在问题。

CN:
一般安全,只要:

  • 第二个参数 ≤ 数组长度;
  • 理解 fgets 会保留 \n(有时要自己去掉)。

有些材料会故意问 “Is this code ok?” 用来提醒你:要保证“数组长度、第二个参数、终止符空间”三者对应正确


代码 6:fgets 改进版(更明确)

void main()
{
    char username[9];
    puts("Please enter your username:");
    fgets(username, 9, stdin);
    printf("Your username is %s\n", username);
}

EN:
Now:

  • Buffer size = 9
  • fgets reads up to 8 characters + 1 for '\0'
    Safer and clearly leaves room.

CN:
缓冲区 9 字节:8 个可见字符 + 1 个 '\0',语义清晰,风险更低。

比喻:
杯子容量和刻度统一写清楚,不会搞混。


🧩 统一例题(基于上述代码 & 概念)

下面这 10 题是围绕 null-termination、strncpyfgets 安全用法,每题都有中英文解释,可直接当题库用。


选择题(5 道)

Q1

char buf[10];
strncpy(buf, "0123456789", sizeof(buf));
printf("%s\n", buf);

What is the main problem?

A. sizeof(buf) is wrong
B. strncpy cannot copy numbers
C. buf may not be null-terminated ✅
D. printf cannot print digits

答案解释:

  • EN: strncpy copies 10 chars, no space for '\0'.
  • CN: 填满数组,没有终止符,printf 会读穿。
  • A/B/D 都与真实错误无关。

Q2

char a[5];
strncpy(a, "hello", sizeof(a)-1);
a[4] = '\0';

Is this safe as a C string?

A. Yes ✅
B. No

解释:
只复制4个字符 hell,再手动加 '\0',长度合法安全。


Q3

Which is the best way to safely copy into char dest[20];?

A. strcpy(dest, src);
B. strncpy(dest, src, sizeof(dest) - 1); dest[19] = '\0'; ✅
C. gets(dest);
D. scanf("%s", dest);

解释:

  • B 同时限制长度 + 手动补 '\0'
  • A/C/D 都可能溢出。

Q4

About fgets(buf, n, stdin) which is true?

A. It may write up to n chars plus '\0'
B. It writes at most n-1 chars and then '\0'
C. It never writes '\0'
D. It is identical to gets()

解释:

  • 正确行为是 n-1 + '\0'
  • C/D 完全错误。

Q5

Why does the program sometimes print 0123456789987654321?

A. printf bug
B. First array not null-terminated, so printf continues into arr2
C. Compiler optimization
D. Because strncpy reverses strings

解释:
核心是:arr 没有 '\0',打印串连下一个数组。


简答题(5 道)

S1

Q: What is a null-terminated string in C?
A (EN): A sequence of characters ending with '\0'.
A (CN): 以空字符 '\0' 作为结束标记的字符数组。


S2

Q: Why is strncpy(dest, src, sizeof(dest)) dangerous when src is long?
A:
EN: It may fill the entire buffer and not append '\0', so dest is not a valid C string.
CN: 会把数组塞满而不补终止符,后续 printf("%s") 等操作就会读穿。


S3

Q: How to correctly use strncpy with char arr[10];?
A:

strncpy(arr, src, sizeof(arr) - 1);
arr[sizeof(arr) - 1] = '\0';

EN: Reserve last byte for '\0'.
CN: 永远留一格给终止符。


S4

Q: What is one advantage of fgets() over gets()?
A:
EN: fgets() limits the number of characters read and ensures space for '\0'.
CN: fgets() 有长度限制,更不容易溢出。


S5

Q: When you see unexpected extra data printed after a string, what should you suspect?
A:
EN: Missing null terminator or buffer overflow.
CN: 优先怀疑:字符串没 '\0' 或越界写把终止符覆盖了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值