种传龙
种传龙

Reputation: 19

How to detect Out-of-Bounds Access within Placement New Boundaries in C++?

#include <iostream>

struct mix {
  int64_t x[10];
};

int main() {
  int64_t* p = new int64_t[100];
  mix* px = new (p) mix;
  mix* py = new (p + 10) mix;
  px->x[12] = 104;
  std::cout << py->x[2] << std::endl;
  delete[] p;
}

In this code, px and py are created using placement new within the same allocated memory block. When accessing px->x[12], it exceeds the boundary of the mix object, but it is still within the bounds of the allocated memory block p.

Using -fsanitize=address, -fsanitize=undefined, and Valgrind does not report any errors for this out-of-bounds access within the placement new boundaries. I need a method to strictly detect such out-of-bounds access beyond the object boundaries set by placement new, even if it does not exceed the overall allocated memory block.

The solution must strictly check every out-of-bounds access. It is acceptable if the solution is slower, as long as it provides strict checking. No modifications to the source code should be required.

Upvotes: 1

Views: 157

Answers (2)

Shelton Liu
Shelton Liu

Reputation: 601

Using -fsanitize=address, -fsanitize=undefined, and Valgrind does not report any errors for this out-of-bounds access within the placement new boundaries. I need a method to strictly detect such out-of-bounds access beyond the object boundaries set by placement new, even if it does not exceed the overall allocated memory block.

To directly quote the requirement, you could write a custom checker on your own side. Here is a simple example to enforce logical object boundaries and verify that accesses stay within them.:

struct mix {
  int64_t x[10];
};

void* operator new(std::size_t size, void* ptr) {
    // Custom placement new (same as default)
    return ptr;
}

void* operator new(std::size_t size) {
  // Regular allocation
  return ::operator new(size);
}

int main() {
  int64_t* p = new int64_t[100];
  mix* px = new (p) mix;
  mix* py = new (p + 10) mix;

  // manually enforce bounds
  if (p + 22 >= p + 100) {
    std::cout << "out-of-bounds access!" << std::endl;
    return 1;
  }

  px->x[12] = 104;
  std::cout << py->x[2] << std::endl;

  delete[] p;
}

Upvotes: 0

Paul Floyd
Paul Floyd

Reputation: 6946

All of the tools that I'm aware of (of which memcheck I know best) cannot do this.

The problem is that placement new is non-allocating. Well, there are two problems really. The second is that your out-of-boundedness is bigger than your chunk size. As a rule tools don't handle that well, having redzones smaller than the chunks (otherwise the memory use overhead gets to be prohibitive).

If you change your code to be something like

#include <iostream>
#include "memcheck.h"

struct mix {
    int64_t x[10];
};

int main()
{
    int64_t* p = new int64_t[1000];
    // second argument is size of redzones
    // third argument means memory is uninitialized
    VALGRIND_CREATE_MEMPOOL(p, 10*sizeof(int64_t), false);
    // allow space for redzone before px
    mix* px = new (p+10) mix;
    VALGRIND_MEMPOOL_ALLOC(p, px, 10*sizeof(int64_t));
    // allow space for redzone after px and before py
    mix* py = new (p + 30) mix;
    VALGRIND_MEMPOOL_ALLOC(p, py, 10*sizeof(int64_t));
    px->x[12] = 104;
    std::cout << py->x[2] << std::endl;
    delete[] p;
    VALGRIND_DESTROY_MEMPOOL(p);
}

Then you will get something like

==712880== Invalid write of size 8
==712880==    at 0x40151B: main (so_placement_new.cpp:16)
==712880==  Address 0x53ea130 is 64 bytes before a block of size 80 client-defined
==712880==    at 0x40150C: main (so_placement_new.cpp:15)
==712880== 
==712880== Conditional jump or move depends on uninitialised value(s)
==712880==    at 0x4B655AC: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:892)
==712880==    by 0x4B72A39: put (locale_facets.h:2400)
==712880==    by 0x4B72A39: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:78)
==712880==    by 0x401537: main (so_placement_new.cpp:17)
==712880== 
==712880== Use of uninitialised value of size 8
==712880==    at 0x4B654BB: int std::__int_to_char<char, unsigned long>(char*, unsigned long, char const*, std::_Ios_Fmtflags, bool) (locale_facets.tcc:821)
==712880==    by 0x4B655D6: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:894)
==712880==    by 0x4B72A39: put (locale_facets.h:2400)
==712880==    by 0x4B72A39: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:78)
==712880==    by 0x401537: main (so_placement_new.cpp:17)
==712880== 
==712880== Conditional jump or move depends on uninitialised value(s)
==712880==    at 0x4B654CD: int std::__int_to_char<char, unsigned long>(char*, unsigned long, char const*, std::_Ios_Fmtflags, bool) (locale_facets.tcc:824)
==712880==    by 0x4B655D6: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:894)
==712880==    by 0x4B72A39: put (locale_facets.h:2400)
==712880==    by 0x4B72A39: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:78)
==712880==    by 0x401537: main (so_placement_new.cpp:17)
==712880== 
==712880== Conditional jump or move depends on uninitialised value(s)
==712880==    at 0x4B6560B: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:914)
==712880==    by 0x4B72A39: put (locale_facets.h:2400)
==712880==    by 0x4B72A39: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:78)
==712880==    by 0x401537: main (so_placement_new.cpp:17)

I don't know ASAN so well. I'm not sure if it has any user memory pool mechanisms. You should be able to use ASAN_POISON_MEMORY_REGION (for p) and ASAN_UNPOISON_MEMORY_REGION (for px and py).

Upvotes: 3

Related Questions