c++gccaddress-sanitizerubsan

How can I detect off-by-one errors (OBOEs) in C++ code?


Consider this simple program:

#include <array>
#include <iostream>
#include <cstdlib>

int main(int argc, char* argv[]) {
    std::array<int, 5> arr = {0, 1, 2, 3, 4};
    int idx = std::atoi(argv[1]);
    int val = std::atoi(argv[2]);

    arr[idx] = val;
    for (auto i=0u; i <= idx; i++) {
        std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
    }
}

If I compile and link it with GCC 6.3.1 like this:

g++ -O0 -std=gnu++14 -fsanitize=undefined example.cpp

and run it like this:

a.out 5 98

I don't get any warnings, even though I am writing and reading one element past the end of 'arr' (the array ends at index 4).

If I run:

a.out 6 98

I receive a warning that 'index 6 out of bounds for type 'int [5]'. In this case I am writing and reading two elements past the end of 'arr'.

Why doesn't the off-by-one case throw an error? I guess because one element past the end of an array is a valid memory address (i.e. iterators can point to it?). Can you suggest any other tool that can reliably detect out of bounds access by one element?

EDIT

I can also run:

g++ -O0 -std=gnu++14 -fsanitize=bounds-strict example.cpp

And I see the same behaviour, i.e. out of bounds access by one element does not trigger a warning, but, out of bounds by two elements does. This is the part I'm trying to understand.


Solution

  • Why doesn't the off-by-one case throw an error?

    Because -fsanitize=undefined doesn't detect out of bounds accesses.

    Can you suggest any other tool that can reliably detect out of bounds access by one element?

    I suggest gcc with -fsanitize=address.

    $ g++ -O0 -std=gnu++14 -fsanitize=address 1.cpp
    $ ./a.out  6 98
    =================================================================
    ==119887==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd3b1aa4b8 at pc 0x560be273c4e9 bp 0x7ffd3b1aa450 sp 0x7ffd3b1aa440
    WRITE of size 4 at 0x7ffd3b1aa4b8 thread T0
        #0 0x560be273c4e8 in main (/tmp/a.out+0x14e8)
        #1 0x7f9dafafe001 in __libc_start_main (/usr/lib/libc.so.6+0x27001)
        #2 0x560be273c17d in _start (/tmp/a.out+0x117d)
    ...
    

    The options are documented in gcc instrumentations options.

    Side note: In one of my projects, I use -fsanitize=address -fsanitize=undefined -fsanitize=leak -fsanitize=pointer-subtract -fsanitize=pointer-compare -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection.