Why doesn't Valgrind memcheck catch this UB?

770 views Asked by At

Like the title say I really need help of understanding, why this code is treated on my system ( linux mint 19, GCC-8.0.1, valgrind-3.13.0, c17 ) as NOT valid code:

#include <stdio.h>
#include <string.h>

void printThis( const char *const ptr );

int main( void) {

    char a[10] = "asds";
    char b[10] = "1234567890";

    strcpy ( a, b );
    printThis( a );
}

void printThis( const char *const ptr ){
    printf("Copy completed! : %s\n", ptr );
}

Valgrind reports the problem here:

==6973== Memcheck, a memory error detector
==6973== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6973== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==6973== Command: /home/michi/Templates/Cprogram/bin/Debug/Cprogram
==6973== 
==6973== Source and destination overlap in strcpy(0x1ffefffd14, 0x1ffefffd1e)
==6973==    at 0x4C32E97: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6973==    by 0x108724: main (main.c:12)
==6973== 
Copy completed! : 1234567890
==6973== 
==6973== HEAP SUMMARY:
==6973==     in use at exit: 0 bytes in 0 blocks
==6973==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==6973== 
==6973== All heap blocks were freed -- no leaks are possible
==6973== 
==6973== For counts of detected and suppressed errors, rerun with: -v
==6973== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

and this one as Valid code:

#include <stdio.h>

void strcpy2(char *s, char *t);
void printThis( const char *const ptr );

int main( void) {

    char a[10] = "asds";
    char b[10] = "1234567890";

    strcpy2( a, b );
    printThis( a );
}

void strcpy2(char *s, char *t) {
    while ( ( *(s++) = *(t++) ) );
}

void printThis( const char *const ptr ){
    printf("Copy completed! : %s\n", ptr );
}

Valgrind output:

==7025== Memcheck, a memory error detector
==7025== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7025== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==7025== Command: /home/michi/Templates/Cprogram/bin/Debug/Cprogram
==7025== 
Copy completed! : 1234567890
==7025== 
==7025== HEAP SUMMARY:
==7025==     in use at exit: 0 bytes in 0 blocks
==7025==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==7025== 
==7025== All heap blocks were freed -- no leaks are possible
==7025== 
==7025== For counts of detected and suppressed errors, rerun with: -v
==7025== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Compiled with O0, O1, O2 and O3 and GCC flags:

-Wpedantic -std=c17 -Wall -Wextra -Werror -Wstrict-prototypes -Wmissing-prototypes -Wmisleading-indentation -Wduplicated-cond -Wold-style-definition -Wconversion -Wshadow -Winit-self -Wfloat-equal -Wwrite-strings -O0 -g

2

There are 2 answers

7
Antti Haapala -- Слава Україні On BEST ANSWER

Valgrind can catch only certain kinds of errors. It cannot instrument the stack, hence it would not see the error with your strcpy2. OTOH the strcpy is replaced by a version that does check if the source and destination overlap - it could catch this only because a + 10 == b in your compiled program!

To catch this kind of error use GCC's -fsanitize=address:

% ./a.out 
=================================================================
==3368==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff13832a2a at pc 0x557f05344da8 bp 0x7fff13832990 sp 0x7fff13832980
READ of size 1 at 0x7fff13832a2a thread T0
    #0 0x557f05344da7 in strcpy2 (/a.out+0xda7)
    #1 0x557f05344cca in main (/a.out+0xcca)
    #2 0x7f2d400e5b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #3 0x557f05344a49 in _start (/a.out+0xa49)

Address 0x7fff13832a2a is located in stack of thread T0 at offset 106 in frame
    #0 0x557f05344b39 in main (/a.out+0xb39)

  This frame has 2 object(s):
    [32, 42) 'a'
    [96, 106) 'b' <== Memory access at offset 106 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
[ ... many more lines follow ... ]
0
Paul Floyd On

I've updated the title to be more accurate concerning the Valgrind tool used.

Valgrind can detect this sort of error, but as already noted, not with memcheck.

If I run this example code with Valgrind exp-sgcheck (exp = experimental, sgcheck = stack and globals check)

valgrind --tool=exp-sgcheck ./so19

Then I get

==25056== Invalid read of size 1
==25056== at 0x40059D: strcpy2 (so19.c:23)
==25056== by 0x400564: main (so19.c:18)
==25056== Address 0x1ffeffed1a expected vs actual:
==25056== Expected: stack array "b" of size 10 in frame 1 back from here

==25056== Actual: unknown
==25056== Actual: is 0 after Expected
==25056==
==25056== Invalid write of size 1
==25056== at 0x4005A0: strcpy2 (so19.c:23)
==25056== by 0x400564: main (so19.c:18)
==25056== Address 0x1ffeffed2a expected vs actual:
==25056== Expected: stack array "a" of size 10 in frame 1 back from here

==25056== Actual: unknown
==25056== Actual: is 0 after Expected
==25056==
==25056== Invalid read of size 1
==25056== at 0x4005A2: strcpy2 (so19.c:23)
==25056== by 0x400564: main (so19.c:18)
==25056== Address 0x1ffeffed2a expected vs actual:
==25056== Expected: stack array "a" of size 10 in frame 1 back from here

==25056== Actual: unknown
==25056== Actual: is 0 after Expected
==25056==
==25056== Invalid read of size 1
==25056== at 0x4E74C9C: vfprintf (in /lib64/libc-2.12.so)
==25056== by 0x4E7BFF9: printf (in /lib64/libc-2.12.so)
==25056== by 0x4005CD: printThis (so19.c:27)
==25056== by 0x400570: main (so19.c:19)
==25056== Address 0x1ffeffed2a expected vs actual:
==25056== Expected: stack array "a" of size 10 in frame 3 back from here

==25056== Actual: unknown
==25056== Actual: is 0 after Expected
==25056==
==25056== Invalid read of size 1
==25056== at 0x4E9E7C0: _IO_file_xsputn@@GLIBC_2.2.5 (in /lib64/libc-2.12.so)

==25056== by 0x4E74FFF: vfprintf (in /lib64/libc-2.12.so)
==25056== by 0x4E7BFF9: printf (in /lib64/libc-2.12.so)
==25056== by 0x4005CD: printThis (so19.c:27)
==25056== by 0x400570: main (so19.c:19)
==25056== Address 0x1ffeffed2a expected vs actual:
==25056== Expected: stack array "a" of size 10 in frame 4 back from here

==25056== Actual: unknown
==25056== Actual: is 0 after Expected

This is when compiled with debug information (-g). With an optimized build no errors are detected.