【问题标题】:Deep understanding of strcat and strlen functions深入理解strcat和strlen函数
【发布时间】:2019-11-11 08:01:25
【问题描述】:

我们知道strcat() 接收指向目标数组的指针作为参数并将它们与源字符串连接。目标数组应该足够大以存储连接的结果。最近我发现 strcat() 仍然可以按预期执行,对于小程序,即使目标数组不够大,无法添加第二个字符串。我开始浏览 * 并发现 couple - answers 这个问题。我想更深入地了解当我在下面运行这段代码时硬件层到底发生了什么?

#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>

using namespace std;

int main(){
    char p[6] = "Hello";
    cout << "Length of p before = " << strlen(p) << endl;
    cout << "Size of p before = " << sizeof(p) << endl;
    char as[8] = "_World!";
    cout << "Length of as before = " << strlen(as) << endl;
    cout << "Size of as before = " << sizeof(as) << endl;
    cout << strcat(p,as) << endl;
    cout << "After concatenation:" << endl;
    cout << "Length of p after = " << strlen(p) << endl;
    cout << "Size of p after = " << sizeof(p) << endl; 
    cout << "Length of as after = " << strlen(as) << endl;
    cout << "Size of as after = " << sizeof(as) << endl;

    return 0;
}

运行此代码后,数组 p[] 的长度为 12,p[] 的大小为 6。这样的长度如何物理存储在这样的数组大小上?我的意思是这个数组的字节数是有限的,这是否意味着 strlen(p) 函数只查找 NULL 终止符,并一直计数直到找到它并忽略该数组的实际分配大小。并且 sizeof() 函数并不真正关心数组中的最后一个元素(专门为空字符分配)是否存储空字符。

【问题讨论】:

标签: c++ pointers char strcat


【解决方案1】:

数组p 分配在函数堆栈帧上,因此strcat“溢出”缓冲区p 并继续写入堆栈的其他区域——通常它会覆盖其他本地参数、函数返回地址、等(请记住,在 x86 平台上,函数堆栈通常会“向下”增长,即朝向较小的地址)。这是众所周知的“缓冲区溢出”漏洞。

strlen 无法知道缓冲区的实际大小,它只是寻找0-terminator。另一方面,sizeof 是一个编译时函数,它以字节为单位返回数组大小。

【讨论】:

  • 很确定调用堆栈是有意的。但是如果提问者的 C++ 实现不使用堆栈呢?
  • @SidS 可能意味着堆栈区域
  • @Sid S 你说得对,措辞不准确,“finction stack frame”会更好。
【解决方案2】:

您在p 的范围之外编写,因此您的程序的行为是未定义的。

虽然行为完全未定义,但会发生一些常见的行为:

  1. 您覆盖了一些不相关的数据。这可能是其他局部变量、函数返回地址等。如果不检查编译器为该特定程序生成的程序集,就不可能准确猜测会被覆盖的内容。这可能会导致严重的安全漏洞,因为它可能允许攻击者将自己的代码注入程序的内存空间,并让他们覆盖函数的返回地址,从而导致程序执行注入的代码。

  2. 程序崩溃。如果您在数组末尾写入足够远以通过内存页面边界,则可能会发生这种情况。该程序可以尝试写入操作系统尚未映射到应用程序物理内存的虚拟内存地址。这会导致操作系统杀死您的应用程序(例如,在 Linux 上使用 SIGSEGV)。与函数本地数组相比,动态分配的数组通常会更频繁地发生这种情况。

【讨论】: