tests/include/rw_test_overrun.h
tests/src/rw_test_overrun.cpp
tests/strings/rwmatch_test.cpp

rw_test_overrun.h contains declaration of the class test_string and #defines used for testing for the memory overrun. rw_test_overrun.cpp contains definition of the class test_string and static utility functions.

  rwmatch_test.cpp - example of using test_string in tests.

Main idea of the test is create string buffer, protected on the end, and pass this buffer to the function which is tested to the correctness memory addressing. Also test_string class contains method protect which can used for check that the any function fun (const char * str) really doen't modify the content of the string (i.e fun accesses the str only for read; detected even non-modifying writes like str[0] = str[0]).

Farid.
#ifndef RW_TEST_OVERRUN_H_INCLUDED
#define RW_TEST_OVERRUN_H_INCLUDED

#include <cstddef>      // for size_t
#include <csetjmp>      // for setjmp
#include <csignal>      // for signal

#include <testdefs.h>

extern std::jmp_buf _rw_overrun_mark;

extern void (* _rw_old_handler) (int);

extern void _rw_overrun_handler (int);

#define BEGIN_TEST_OVERRUN()                                      \
    _rw_old_handler = std::signal (SIGSEGV, _rw_overrun_handler), \
    0 == setjmp (_rw_overrun_mark)

class test_string
{
public:

    enum protection { READ_ONLY, READ_WRITE };

    // default ctor
    _TEST_EXPORT
    test_string ();

    // copy ctor
    // may throw std::bad_alloc
    _TEST_EXPORT
    test_string (const test_string&);

    // construct object and initialize with given string
    // if size == size_t (-1) size of the string object
    // will be strlen (str) + 1 (including terminating zero)
    // may throw std::bad_alloc
    _TEST_EXPORT
    test_string (const char*, size_t = size_t (-1));

    // construct object and initialize with sequence of the
    // given char with length == size
    // may throw std::bad_alloc
    _TEST_EXPORT
    test_string (size_t, char = char ());

    // dtor
    _TEST_EXPORT
    ~test_string ();

    // op_set
    // may throw std::bad_alloc
    _TEST_EXPORT
    test_string& operator= (const test_string&);

    // initialize object with given string
    // if size == size_t (-1) size of the string object
    // will be strlen (str) + 1 (including terminating zero)
    // returns the self reference
    // may throw std::bad_alloc
    _TEST_EXPORT
    test_string& assign (const char*, size_t = size_t (-1));

    // initialize object with sequence of the
    // given char with length == size
    // returns the self reference
    // may throw std::bad_alloc
    _TEST_EXPORT
    test_string& assign (size_t, char = char ());

    // set read-only or read-write access to the controlling string
_TEST_EXPORT
    bool protect (protection prot);

    // returns pointer to the controlling string
    _TEST_EXPORT
    char* data ()
    {
        return str_;
    }

    // returns const pointer to the controlling string
    _TEST_EXPORT
    const char* data () const
    {
        return str_;
    }

    // returns the size of controlling string
    // (including terminating zero if present)
    _TEST_EXPORT
    size_t size () const
    {
        return size_;
    }

private:
    // allocates the memory with guard page 
    // may throw std::bad_alloc
    void allocate (size_t);
    // frees the allocated memory
    void free ();

private:
    // pointer to the allocated memory
    char * buf_;

    // pointer to begin of controlling string
    char * str_;
    // size of the controlling string
    // (including terminating zero if present)
    size_t size_;
};

#endif  // #ifndef RW_TEST_OVERRUN_H_INCLUDED
// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC

#include <rw/_config.h>

#include <cstring>      // for strlen
#include <new>          // for bad_alloc

#include <rw_test_overrun.h>

// temporary
// I don't know why my config.h doesn't contains
// defined _RWSTD_POSIX_THREADS on Linux
#if !defined (_WIN32) && !defined (_WIN64)
#define _RWSTD_POSIX_THREADS
#endif

#if defined (_RWSTD_POSIX_THREADS)

#include <sys/mman.h>   // for mmap
#include <unistd.h>     // for getpagesize

#elif defined (_WIN32) || defined (_WIN64)

#include <Windows.h>    // for VirtualAlloc etc
#include <sys/types.h>  // for off_t

const int PROT_NONE  = 0;
const int PROT_READ  = 1;
const int PROT_WRITE = 2;
const int PROT_EXEC  = 4;

#define MAP_PRIVATE   0
#define MAP_ANONYMOUS 0

#define MAP_FAILED -1

static DWORD translate_prot(int prot)
{
    static DWORD prots[8] = {
        PAGE_NOACCESS,
        PAGE_READONLY,
        PAGE_READWRITE,
        PAGE_READWRITE,
        PAGE_EXECUTE,
        PAGE_EXECUTE_READ,
        PAGE_EXECUTE_READWRITE,
        PAGE_EXECUTE_READWRITE
    };

    if ((0 <= prot) && (sizeof(prots) / sizeof(prots[0]) > prot))
        return prots[prot];

    return PAGE_NOACCESS;
}

static inline void* mmap (void* start, size_t length, int prot,
                          int/* flags*/, int/* fd*/, off_t/* offset*/)
{
    void* buf = ::VirtualAlloc (start, length,
        MEM_COMMIT | MEM_RESERVE, translate_prot (prot));

    return buf ? buf : reinterpret_cast<void*> (MAP_FAILED);
}

static inline int munmap (void* start, size_t/* length*/)
{
    return ::VirtualFree (start, 0, MEM_RELEASE) ? 0 : -1;
}

static inline int mprotect (const void* addr, size_t len, int prot)
{
    DWORD fProtect = translate_prot (prot);
    void* address = const_cast<void*> (addr);

    if (PAGE_NOACCESS == fProtect)
        return ::VirtualFree (address, len, MEM_DECOMMIT) ? 0 : -1;
    else
        return ::VirtualAlloc (address, len, MEM_COMMIT, fProtect) ? 0 : -1;
        // return ::VirtualProtect (address, len, fProtect, NULL) ? 0 : -1;
}

static int getpagesize ()
{
    static int page_size_ = 0;

    if (0 == page_size_) {
        SYSTEM_INFO info;
        ::GetSystemInfo (&info);
        page_size_ = info.dwPageSize;
    }

    return page_size_;
}

#else   // unknown/missing threads environment

#error unknown/missing threads environment

#endif

std::jmp_buf _rw_overrun_mark;
void (* _rw_old_handler) (int);

_TEST_EXPORT
void _rw_overrun_handler (int)
{
    // restore previous handler
    std::signal (SIGSEGV, _rw_old_handler);

    std::longjmp (_rw_overrun_mark, -1);
}

// allocates the memory with specified size and additional guard page
// returns pointer to the allocated buffer (aligned to page boundary)
// and offset to the begin of the buffer for using
// returns 0 if not success
static void* _rw_alloc_buf (size_t, size_t&);

// deallocates the memory
static void _rw_free_buf (void*, size_t, size_t);

/******************************************************************************/

// default ctor
_TEST_EXPORT
test_string::test_string () : buf_ (0), str_ (0), size_ (0)
{
}

// copy ctor
// may throw std::bad_alloc
_TEST_EXPORT
test_string::test_string (const test_string& ts) : buf_ (0), str_ (0), size_ (0)
{
    assign (ts.data (), ts.size ());
}

// construct object and initialize with given string
// if size == size_t (-1) size of the string object
// will be strlen (str) + 1 (including terminating zero)
// may throw std::bad_alloc
_TEST_EXPORT
test_string::test_string (const char * str, size_t sz) :
    buf_ (0), str_ (0), size_ (0)
{
    assign (str, sz);
}

// construct object and initialize with sequence of the
// given char with length == size
// may throw std::bad_alloc
_TEST_EXPORT
test_string::test_string (size_t sz, char c) : buf_ (0), str_ (0), size_ (0)
{
    assign (sz, c);
}

// dtor
_TEST_EXPORT
test_string::~test_string ()
{
    free ();
}

// op_set
// may throw std::bad_alloc
_TEST_EXPORT
test_string& test_string::operator= (const test_string& ts)
{
    if (this != &ts)
        assign (ts.data (), ts.size ());

    return *this;
}

// initialize object with given string
// if size == size_t (-1) size of the string object
// will be strlen (str) + 1 (including terminating zero)
// returns the self reference
// may throw std::bad_alloc
_TEST_EXPORT
test_string& test_string::assign (const char * str, size_t sz)
{
    if (size_t(-1) == sz)
        sz = std::strlen (str) + 1;

    allocate (sz);

    std::memcpy (str_, str, sz);

    return *this;
}

// initialize object with sequence of the
// given char with length == size
// returns the self reference
// may throw std::bad_alloc
_TEST_EXPORT
test_string& test_string::assign (size_t sz, char c)
{
    allocate (sz);

    std::memset (str_, c, sz);

    return *this;
}

// set read-only or read-write access to the controlling string
// returns true if success, otherwise returns false
_TEST_EXPORT
bool test_string::protect (protection p)
{
    int prot = READ_ONLY == p ? PROT_READ : PROT_READ | PROT_WRITE;
    return -1 != mprotect (buf_, size_ + (str_ - buf_), prot);
}

// allocates the memory with guard page 
// may throw std::bad_alloc
void test_string::allocate (size_t sz)
{
    free ();

    if (sz) {

        size_t offset = 0;

        buf_ = static_cast<char*> (_rw_alloc_buf (sz, offset));

        if (!buf_)
            throw std::bad_alloc ();

        str_ = buf_ + offset;

        size_ = sz;
    }
}

// frees the allocated memory
void test_string::free ()
{
    if (buf_) {

        _rw_free_buf (buf_, size_, str_ - buf_);

        buf_ = 0;
        str_ = 0;
        size_ = 0;
    }
}

/******************************************************************************/

// allocates the memory with specified size and additional guard page
// returns pointer to the allocated buffer (aligned to page boundary)
// and offset to the begin of the buffer for using
// returns 0 if not success
static void* _rw_alloc_buf (size_t size, size_t& offset)
{
    size_t page_size = getpagesize ();

    offset = page_size - (size <= page_size ? size : size % page_size);

    size_t buf_size = size + offset;

    // reserve the memory for the buffer
    // plus one memory page with r/w access rights
    void * buf = mmap (NULL, buf_size + page_size,
        PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (buf)
        // deny access to the guard page
        if (-1 == mprotect (static_cast<char*> (buf) + buf_size,
            page_size, PROT_NONE))
        {
            // mprotect failed, free buf
            munmap (buf, buf_size + page_size);
            buf = 0;
        }

    return buf;
}

// deallocates the memory
static void _rw_free_buf (void* buf, size_t size, size_t offset)
{
    munmap (buf, size + offset + getpagesize ());
}
#include <rw_char.h>      // for rw_expand
#include <driver.h>       // for rw_assert
#include <exception>      // for exception

#include <rw_test_overrun.h>

int test (int, char*[])
{
    test_string ts ("abcd");

    if (BEGIN_TEST_OVERRUN()) {
        const char * str = ts.data ();
        const size_t size = ts.size ();
        rw_match (str, str, size);
    } else 
        rw_assert (false, 0, 0, "rw_match addressed after string end");

    if (BEGIN_TEST_OVERRUN()) {
        char * str = ts.data ();
        str [0] = str [1];
        rw_assert (false, 0, 0, "string is read-write");
    } else 
        rw_assert (false, 0, 0, "string is read-only");

    bool b = ts.protect (test_string::READ_ONLY);
    rw_assert (b, 0, 0, "ts.protect failed");

    if (BEGIN_TEST_OVERRUN()) {
        char * str = ts.data ();
        str [0] = str [1];
        rw_assert (false, 0, 0, "string is read-write");
    } else 
        rw_assert (false, 0, 0, "string is read-only");

    return 0;
}

int main(int argc, char* argv[])
{
    try {
        rw_test (argc, argv, __FILE__, "", 0, test, 0);
    } catch (std::exception & ex) {
        rw_assert (false, 0, 0, "exception caught: %s", ex.what ());
    }

    return 0;
}

Reply via email to