0

我有以下代码片段。

#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是一个使用以下命令的著名模型检查器。

cbmc test01.c --pointer-check

CBMC 报告该程序是安全的。是 CBMC 的问题还是我遗漏了什么?

4

2 回答 2

1

正如您在问题中所说,该声明printf("%d\n", *c);暴露了未定义的行为。由于undefined,您对它的任何期望都是错误的。这包括获得特定错误或任何错误。

C 运行时库不会检查您的程序访问内存的方式。如果它这样做,程序将运行得非常非常慢。关于堆,C 运行时库做了一些基本的检查;例如,它在程序启动期间将某个值放在地址0上,并在程序结束时检查该值是否仍然存在。如果值发生了变化,那么一个空指针被取消引用以进行写入,它可以警告你这一点。

之后c = c + 20;c很可能指向属于您的程序的一块内存。它可以是堆上的一个空闲区域,它可以在堆管理器用来处理堆的数据结构中,但很有可能它仍然在同一个内存页面上。

如果您运气不好并且c + 20落在存储的内存页面之外,c那么就会发生由操作系统处理的异常情况。它终止程序并显示与您在问题中列出的错误消息类似的错误消息(想法相同,每个操作系统上的单词和演示文稿都不同)。


更新

分配内存不是某种魔法。该程序从一个由操作系统为此目的分配给程序的内存块(称为“堆” )开始。

C 运行时库包含管理堆的代码。此代码使用此内存的一小部分进行簿记。一个常见的实现使用双链表,列表中每个节点的有效负载是程序使用<memory.h>(malloc()calloc()) 中声明的函数“分配”的内存块。当调用malloc()发生时,此代码运行,在列表中创建一个新节点并返回节点有效负载的地址(堆内的地址)。

程序根据需要使用此指针。例如,您的程序可以在 上自由编写c-1。事实上,它里面malloc()居然写了信息那里。malloc()返回后c,你的代码也可以写在c-1。从这个OS角度来看,这两种写操作没有区别。而且由于C不是托管或解释语言,因此您的程序中没有包含任何代码来监视您编写的代码所做的事情或握住它的手不要写在错误的地方。

如果您在此处写入,c-1则很有可能会破坏堆管理器使用的数据结构。不会立即发生任何错误。没有显示错误消息,并且您的程序继续运行,显然是正常的。但是在下一次调用处理堆的函数(无论是内存分配还是释放)时,程序将开始造成严重破坏。堆数据结构被破坏任何事情都可能发生。


关于CBMC,我不知道它是如何工作的。也许它无法检测到这种情况。c或者它可能会报告您的程序是安全的,因为它在递增后没有写入。

于 2016-03-31T09:51:50.900 回答
0

顺便提一句。gcc -fsanitize=address确实会在没有警告的情况下编译此文件,但是当您运行代码时,您会收到来自地址清理程序的消息

=================================================================
==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

这样的输出对于发现这样的泄漏和溢出非常有帮助。

但是对于编译器来说,要找到运行时发生的此类错误并不是一件容易的事。

于 2016-03-31T11:12:45.800 回答