Search code examples
ccastingaddress-sanitizer

Prevent global buffer overflow casting a static bool reference to int pointer


I have a static global variable echo which type is boolean and a function declared as:

void add_param(char *name,
               int *valp,
               char *documentation,
               setter_function setter);

when called, echo should go into the second parameter, meaning

add_param(/*Some string*/ ,(int *)&echo, /*Some string*/, /*Some thing*/)`.

Here when address sanitizer is on, gives a global-buffer-overflow error, which I know is a result of that a boolean variable is 1 byte in size, but a integer is 4 byte, and changing the type of echo to integer makes the program works perfectly well.

Here's the error message if its needed:

==4344==ERROR: AddressSanitizer: global-buffer-overflow on address 0x5626b775c400 at pc 0x5626b7745905 bp 0x7ffe6ed6e440 sp
0x7ffe6ed6e430 READ of size 4 at 0x5626b775c400 thread T0
    #0 0x5626b7745904 in do_help_cmd /home/uduru/GitHub-Repos/lab0-c/console.c:307
    #1 0x5626b7745a18 in interpret_cmda /home/uduru/GitHub-Repos/lab0-c/console.c:221
    #2 0x5626b77461fd in interpret_cmd /home/uduru/GitHub-Repos/lab0-c/console.c:244
    #3 0x5626b7747940 in run_console /home/uduru/GitHub-Repos/lab0-c/console.c:660
    #4 0x5626b7744527 in main /home/uduru/GitHub-Repos/lab0-c/qtest.c:788
    #5 0x7fa8cf0100b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #6 0x5626b7741b8d in _start (/home/uduru/GitHub-Repos/lab0-c/qtest+0x8b8d)

0x5626b775c401 is located 0 bytes to the right of global variable
'echo' defined in 'console.c:59:13' (0x5626b775c400) of size 1
SUMMARY: AddressSanitizer: global-buffer-overflow
/home/uduru/GitHub-Repos/lab0-c/console.c:307 in do_help_cmd Shadow
bytes around the buggy address:   
0x0ac556ee3830: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9   
0x0ac556ee3840: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9   
0x0ac556ee3850: f9 f9 f9 f9 00 00 00 00 04 f9 f9 f9 f9 f9 f9 f9   
0x0ac556ee3860: 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9 f9 f9   
0x0ac556ee3870: 01 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9
0x0ac556ee3880:[01]f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9   
0x0ac556ee3890: 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00  
0x0ac556ee38a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
0x0ac556ee38b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
0x0ac556ee38c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
0x0ac556ee38d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
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   
Freed heap region:       fd  
Stack left redzone:      f1   
Stack mid redzone:       f2   
Stack right redzone:     f3   
Stack after return:      f5   
Stack use after scope:   f8   
Global redzone:          f9   
Global init order:       f6   
Poisoned by user:        f7   
Container overflow:      fc   
Array cookie:            ac   
Intra object redzone:    bb   
ASan internal:  fe   
Left alloca redzone:     ca   
Right alloca redzone:    cb  
Shadow gap:              cc
==4344==ABORTING ```

However, I wonder if there exists any way preventing the error while keeping the declarations as they are?


Solution

  • The proper way to prevent such errors is to avoid casts, use the proper types everywhere, and configure the compiler to produce more warnings (-Wall -Wextra) and to consider these warnings errors (-Werror).

    If add_param expects a pointer to int, do not pass a pointer to something that is not compatible with type int.

    If you want add_param to handle different types, you can define the valp argument as a pointer to void and pass the expected type with another argument, such as an appropriate setter function. You would explicitly bypass the compiler type checking mechanisms and be on your own if the program has semantic errors.

    Here is an example:

    #include <error.h>
    #include <limits.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef int (*setter_function)(void *valp, const char *value);
    void add_param(const char *name, void *valp, const char *value, setter_function setter) {
        printf("Setting %s parameter to %s: ", name, value);
        if (setter(valp, value))
            printf("failure\n");
        else
            printf("success\n");
    }
    
    int set_bool(void *valp, const char *value) {
        bool *bp = valp;
        if (!strcmp(value, "true")) {
            *bp = true;
            return 0;
        }
        if (!strcmp(value, "false")) {
            *bp = false;
            return 0;
        }
        return 1;  // invalid value
    }
    
    int set_int(void *valp, const char *value) {
        int *bp = valp;
        char *p;
        long n;
    
        errno = 0;
        n = strtol(value, &p, 0);
    
        if (p != value && *p == '\0' && errno == 0 && n >= INT_MIN && n <= INT_MAX) {
            *bp = value;
            return 0;
        }
        return 1;  // invalid value
    }
    
    bool echo;
    int width;
    
    int main() {
        add_param("echo", &echo, "1", set_bool);
        add_param("echo", &echo, "yes", set_bool);
        add_param("echo", &echo, "true", set_bool);
        add_param("width", &width, "abc", set_int);
        add_param("width", &width, "42", set_int);
        // this will compile, but causes undefined behavior.
        //add_param("echo", &echo, "1", set_int);
        return 0;
    }
    

    Output:

    Setting echo parameter to 1: failure
    Setting echo parameter to yes: failure
    Setting echo parameter to true: success
    Setting width parameter to abc: failure
    Setting width parameter to 42: success