我有以下代码段。
#include<stdio.h>
#include<stdlib.h>
int main()
{
char *c = malloc(1);
printf("%p\n", c);
c = c + 20;
printf("%p\n", c);
printf("%d\n", *c);
free(c - 20);
return 0;
}
在这段代码中,我将1个字节的内存分配给指针。然后我访问分配内存后20个单元的内存位置。当我取消引用该指针时,我预计会出现内存访问冲突错误或分段错误或类似的内容。我没有收到任何此类错误。
让我们假设这是未定义行为的情况。所以我尝试使用CBMC验证这个程序,这是一个众所周知的模型检查器,使用以下命令。
cbmc test01.c --pointer-check
CBMC报道该计划是安全的。这是CBMC的问题还是我错过了什么?
答案 0 :(得分:1)
正如您在问题中所说,语句printf("%d\n", *c);
暴露了未定义的行为。由于未定义,您对此可能产生的任何期望都是错误的。这包括获取特定错误或任何错误。
C运行时库不检查程序访问内存的方式。如果它会这样做,程序会运行得多,慢得多。关于堆,C运行时库执行一些基本检查;例如,它在程序启动期间在地址0
处放置一定值,并在程序结束时检查该值是否仍然存在。如果值已更改,则空指针将被取消引用以进行写入,并且它可以向您发出警告。
在c = c + 20;
之后,c
最有可能指向属于您的程序的内存块。它可以是堆上的空闲区域,它可以在堆管理器用来处理堆的数据结构内部,但很可能它仍然在同一个内存页面上。
如果您有(坏)运气和c + 20
落在存储c
的内存页面之外,则会发生由操作系统处理的异常情况。它终止程序并显示与您在问题中列出的错误消息类似的错误消息(想法是相同的,每个操作系统上的单词和表示都不同)。
<强>更新强>
分配内存不是某种魔力。该程序以一块内存(称为&#34; heap&#34; )开始,由操作系统为此目的分配给程序。
C运行时库包含管理堆的代码。此代码使用此内存的一小部分进行簿记。一个常见的实现使用双链表,列表中每个节点的有效负载是一块内存&#34;已分配&#34;由程序使用<memory.h>
(malloc()
,calloc()
等中声明的函数。当调用malloc()
时,此代码运行,在列表中创建一个新节点,并返回节点有效负载的地址(堆内的地址)。
程序根据需要使用此指针。例如,您的程序可以在c-1
免费撰写。事实上,在malloc()
里面,它实际上在那里写了信息。在malloc()
返回c
后,您的代码也可以在c-1
处撰写。从OS
的角度来看,这两个写操作之间没有区别。并且由于C
不是托管或解释语言,因此程序中不包含任何代码,用于查看您编写的代码所执行的代码,或者保留其手写不在错误的位置。
如果您在c-1
写信,那么您很可能会破坏堆管理器使用的数据结构。没有错误立即发生。没有显示错误消息,您的程序继续运行显然正常。但是在下一次调用处理堆的函数(无论是内存分配还是释放)时,程序将开始造成严重破坏。堆数据结构损坏可能发生任何事情。
关于CBMC,我不知道它是如何运作的。也许它无法检测到这种情况。或者它可能会报告您的程序是安全的,因为它在c
增加后不会写入。
答案 1 :(得分:0)
顺便说一句。 gcc -fsanitize=address
会在没有任何警告的情况下编译此文件,但是当您运行代码时,您将收到来自地址sanitzer的消息
=================================================================
==24198==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000f004 at pc 0x400921 bp 0x7ffe1e66b900 sp 0x7ffe1e66b8f0
READ of size 1 at 0x60200000f004 thread T0
#0 0x400920 in main (/home/ingo/test/c/sanitize_address+0x400920)
#1 0x7fdd9ddca7af in __libc_start_main (/lib64/libc.so.6+0x207af)
#2 0x4007d8 in _start (/home/ingo/test/c/sanitize_address+0x4007d8)
0x60200000f004 is located 19 bytes to the right of 1-byte region [0x60200000eff0,0x60200000eff1)
allocated by thread T0 here:
#0 0x7fdd9e19c7b7 in malloc (/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libasan.so.1+0x577b7)
#1 0x4008b7 in main (/home/ingo/test/c/sanitize_address+0x4008b7)
#2 0x7fdd9ddca7af in __libc_start_main (/lib64/libc.so.6+0x207af)
SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 main
Shadow bytes around the buggy address:
0x0c047fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa 01 fa
=>0x0c047fff9e00:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff9e50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Contiguous container OOB:fc
ASan internal: fe
==24198==ABORTING
这样的输出可以非常有助于发现这种泄漏和超支。
但编译器找到运行时发生的错误并不是一项简单的任务。