当字符只出现在堆内存中一次时,为什么这个程序会重复打印字符?

Why does this program print characters repeatedly, when they only appear in heap memory once?

我写了一个小程序来探索 C 中的越界读取漏洞,以便更好地理解它们;这个程序是故意的,有漏洞:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PAGE_SIZE 5
void print_welcome(void);
unsigned int get_pages(void);
char* string(char *str);

int main(void)
{
    char stack_book[] = "This is the stack book.\n";
    char *heap_book = malloc(50);
    if(!heap_book)
    {
        fprintf(stderr, "malloc failed so we have to exit.\n");
        return EXIT_FAILURE;
    }
    char *secret = string("secretinfo");
    if(!secret)
    {
        fprintf(stderr, "Failed to allocate memory for secret\n");
        return EXIT_FAILURE;
    }
    char answer[5] = { 0 };
    unsigned int num_pages = 0;
    strcpy(heap_book, "These are the contents of the heap book!\n");
    print_welcome();

    printf("First, do you prefer the stack or heap book (stack/heap)?\n");
    fscanf(stdin, "%5s", answer);
    printf("Now, how many pages do you want to print?\n");
    fscanf(stdin, "%u", &num_pages);
    if(strstr(answer, "heap") != NULL)
    {
        printf("Good choice! The heap book is a fantastic read. Printing...:\n\n");
        size_t i;
        for( i = 0; i < num_pages*PAGE_SIZE; ++i)
        {
            putchar(heap_book[i]);
        }

    }
    else if(strstr(answer, "stack") != NULL)
    {
        printf("Excelente! The stack book is a splendid read. Printing...:\n\n");
        size_t i;
        for( i = 0; i < num_pages*PAGE_SIZE; ++i)
        {
            putchar(stack_book[i]);
        }

    }
    else
    {
        printf("Come back when you're ready to read, childrens....\n");
    }
    putchar('\n');

    free(heap_book);
    free(secret);
    return EXIT_SUCCESS;
}

void print_welcome(void)
{
    printf("Welcome to speedreader 1.0!\nWe've loaded the books into memory, read at your own speed!\n");
}

char* string(char *str)
{
    if(!str)
    {
        fprintf(stderr,"Runtime error: Bad pointer provided to string create function.\n");
        return NULL;
    }
    size_t len = 0;
    char *ret_ptr = NULL;

    len = strlen(str);

    ret_ptr = calloc(len+1, 1); //Account for null terminator
    if(!ret_ptr)
    {
        fprintf(stderr, "Runtime error(string): calloc failed.\n");
        return NULL;
    }

    strcpy(ret_ptr, str);
    return ret_ptr;
}

当 运行 输入:heap\n100\n,我得到输出:

Welcome to speedreader 1.0!
We've loaded the books into memory, read at your own speed!
First, do you prefer the stack or heap book (stack/heap)?
heap
Now, how many pages do you want to print?
100
Good choice! The heap book is a fantastic read. Printing...:

These are the contents of the heap book!
!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!

我的问题是:当 secretinfo! 只出现在堆上一次时,为什么这个程序打印 !secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!:

(gdb)
0x4052a0:   "These are the contents of the heap book!\n"
0x4052ca:   ""
0x4052cb:   ""
0x4052cc:   ""
0x4052cd:   ""
0x4052ce:   ""
0x4052cf:   ""
0x4052d0:   ""
0x4052d1:   ""
0x4052d2:   ""
0x4052d3:   ""
0x4052d4:   ""
0x4052d5:   ""
0x4052d6:   ""
0x4052d7:   ""
0x4052d8:   "!"
0x4052da:   ""
0x4052db:   ""
0x4052dc:   ""
0x4052dd:   ""
0x4052de:   ""
0x4052df:   ""
0x4052e0:   "secretinfo"
0x4052f1:   ""
0x4052f2:   ""
0x4052f3:   ""
0x4052f4:   ""
0x4052f5:   ""
0x4052f6:   ""
0x4052f7:   ""
0x4052f8:   "1[=12=]4"
0x4052fb:   ""

我不明白为什么它重复 secretinfo! 这么多次。

CPU 信息(来自 1 个核心):

vendor_id   : AuthenticAMD
cpu family  : 23
model       : 113
model name  : AMD Ryzen 9 3900X 12-Core Processor
stepping    : 0
microcode   : 0x8701013
cpu MHz     : 3792.869
cache size  : 512 KB
physical id : 14
siblings    : 1
core id     : 0
cpu cores   : 1
apicid      : 14
initial apicid  : 14
fpu     : yes
fpu_exception   : yes
cpuid level : 16
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xsaves clzero arat overflow_recov succor
bogomips    : 7585.73
TLB size    : 3072 4K pages
clflush size    : 64
cache_alignment : 64
address sizes   : 43 bits physical, 48 bits virtual

操作系统: Fedora 31

内核: Linux 5.8.18-100.fc31.x86_64

编译器:gcc (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)

链接器:GNU ld 版本 2.32-33.fc31

glibc: 2.30-13.fc31

编译命令gcc ./oob_read.c -o ./oob_read -g3

如有必要,可以提供其他信息。

由于 stdout 是行缓冲的,putchar 不会直接写入终端;它将字符放入缓冲区,遇到换行符时刷新缓冲区。 stdout 的缓冲区恰好位于 heap_book 分配后的堆上。

因此在您的副本中的某个时刻,您 putchar 您的 secretinfo 方法的所有字符。它们现在位于输出缓冲区中。稍后,heap_book[i] 位于 stdout 缓冲区本身内,因此您会遇到 secretinfo 的副本。当你 putchar 它时,你有效地在缓冲区中稍远一点的地方创建了另一个副本,然后重复该过程。

您可以在调试器中验证这一点。在 glibc 上,stdout 缓冲区的地址可以通过 p stdout->_IO_buf_base 找到。在我的测试中,它刚好超过 heap_book.

160 个字节