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

Time:01-01

I wrote a small program to explore out-of-bounds reads vulnerabilities in C to better understand them; this program is intentionally buggy and has vulnerabilities:

#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;
}

When run with input: heap\n100\n, I get output:

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!

My question is: Why does this program print !secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo! when secretinfo! only appears on the heap once:

(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:   "\021\004"
0x4052fb:   ""

I do not understand why it repeats secretinfo! so many times.

CPU info (from 1 core):

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

Operating System: Fedora 31

Kernel: Linux 5.8.18-100.fc31.x86_64

Compiler: gcc (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)

Linker: GNU ld version 2.32-33.fc31

glibc: 2.30-13.fc31

Compiler command: gcc ./oob_read.c -o ./oob_read -g3

Additional info could be provided if necessary.

CodePudding user response:

Since stdout is line buffered, putchar doesn't write to the terminal directly; it puts the character into a buffer, which is flushed when a newline is encountered. And the buffer for stdout happens to be located on the heap following your heap_book allocation.

So at some point in your copy, you putchar all the characters of your secretinfo method. They are now in the output buffer. A little later, heap_book[i] is within the stdout buffer itself, so you encounter the copy of secretinfo that is there. When you putchar it, you effectively create another copy a little further along in the buffer, and the process repeats.

You can verify this in your debugger. The address of the stdout buffer, on glibc, can be found with p stdout->_IO_buf_base. In my test it's exactly 160 bytes past heap_book.

  • Related