C ++ memset / memcpy / strcpy实现 - 检查缓冲区溢出

时间:2014-02-20 13:11:33

标签: c++

我已经在C ++中进行了基本的memset / memcpy / strcpy实现,它们工作正常。但是,如果我要做这样的事情,有没有办法检测缓冲区溢出:

示例:

int main()
{
    char *buf = (char *)calloc(10, sizeof(char));
    __strcpy(buf, "Hello World"); 
    // buffer size: 10, copy size: 12 (including '\0') - overflow
}

实施(typedef unsigned int UINT):

void *__memset(void *_Dst, int _Val, UINT _Size)
{
    UINT *buf = (UINT *)_Dst;
    while (_Size--)
    {
        *buf++ = (UINT)_Val;
    }
    return _Dst;
}

void *__memcpy(void *_Dst, const void *_Src, UINT _Size)
{
    UINT *buf = (UINT *)_Dst;
    UINT *src = (UINT *)_Src;
    while (_Size--)
    {
        *buf++ = *src++;
    }
    return _Dst;
}

char *__strcpy(char *_Dst, const char *_Src)
{
    while ((*_Dst++ = *_Src++) != '\0');
    return _Dst;
}

6 个答案:

答案 0 :(得分:2)

程序中无法检测到缓冲区溢出。操作系统正在检测它们。您只能检查代码中的潜在缺陷(if / else,asserts,exceptions)。或者您使用像valgrind这样的分析工具。

答案 1 :(得分:1)

您可以检测溢出,但前提是实现自己的内存管理例程。在我们编写在没有“真正”操作系统的设备上运行的嵌入式软件之前,我们曾经这样做过,而且还有很多很好的调试工具。

我们的想法是围绕malloc()(以及calloc()构建自己的包装器,在你的情况下),它将分配比调用者请求更多的字节。然后在请求的内存之前和之后设置一些“保护字节”,并使用可识别的数据初始化整个缓冲区。还要构建一个围绕free()的包装器,它在释放内存之前检查保护字节,如果它们发生了更改则会生成错误。

#define GUARD_LEN = 4   // Arbitrary number of guard bytes.
#define GUARD_BYTE = 0xA5  // Arbitrary but recognizable: 10100101b
#define UNUSED_BYTE = 0x96 // Arbitrary but recognizable: 10010110b
#define FREED_BYTE = 0xC3  // Arbitrary but recognizable: 11000011b
#define MAX_ALLOCS = 1024  // Max # of malloc'ed buffers.
struct {
  void *addr;  // Address of malloc'ed buffer
  size_t len;  // Number of requested bytes
} Allocs[MAX_ALLOCS];

// Allocates and initializes memory.
void *chk_malloc(size_t length) {
  // Allocate memory for buffer + guard bytes.
  void *mem = malloc(length + 2*GUARD_LEN);
  if (mem == NULL) {
    return NULL;
  }

  // Initialize: [GUARD][UNUSED_BUFFER][GUARD]
  // Caller's usable memory starts after GUARD.
  void *buffer = mem + GUARD_LEN;
  memset(mem, GUARD_BYTE, GUARD_LEN);
  memset(buffer, UNUSED_BYTE, length);
  memset(buffer + length, GUARD_BYTE, GUARD_LEN);

  // Remember the address and length.
  // Simplified for demonstration; you may want this to be smarter.
  for (int i = 0; i < MAX_ALLOCS; ++i) {
    if (Allocs[i].addr == NULL) {
      Allocs[i].addr = buffer;
      Allocs[i].len = length;
      return buffer;
  }
  return NULL;  // Should also indicate MAX_ALLOCS is too small.
}

// Checks that buffer is filled with val.
bool chk_filled(void *buffer, char val, size_t len) {
  for (int i = 0; i < len; ++i) {
    if (buffer[i] != val) {
      return false;
    }
  }
  return true;
}

// Checks for over/underrun and releases memory.
void chk_free(void *buffer) {
  // Find the buffer in the array of alloc'ed buffers.
  for (int i = 0; i < MAX_ALLOCS; ++i) {
    if (Allocs[i].addr == buffer) {
      void *guard = buffer - GUARD_LEN;  // Initial guard bytes.
      if (!chk_filled(guard, GUARD_BYTE, GUARD_LEN)) {
        // Underrun
      }
      end_guard = buffer + Allocs[i].len;    // Terminal guard bytes.
      if (!chk_filled(end_guard, GUARD_BYTE, GUARD_LEN)) {
        // Overrun
      }

      // Mark the buffer as free and release it.
      memset(guard, FREED_BYTE, Allocs[i].len + 2*GUARD_LEN);
      Allocs[i].addr = -Allocs[i].addr;  // See text below.
      free(guard);
      return;
    }
  }
  // Error: attempt to free unalloc'ed memory.
}

实际上你可能希望这在几个方面变得更聪明:

  • 您可能不希望限制为MAX_ALLOCS
  • 检查程序退出时未释放的已分配内存。
  • 退出时打印Allocs[]
  • 在检测到错误时立即打印更多信息和/或退出。

答案 2 :(得分:0)

检测缓冲区溢出的更安全方法是提供您自己的calloc实现。在返回的块之前和之后提供几个字节填充,将它们设置为已知值(NOT 0或255),并在调用free时检查它们是否未被触及。此外,在调用free之后,您应该覆盖整个块(包括两侧的填充)以检查双free次呼叫。

答案 3 :(得分:0)

您的所有mem *功能都无效。它们复制(或设置)unsigned int类型的对象,同时它们必须复制(或设置)unsigned char类型的对象。考虑到__Size可以是奇数。 这些函数无法在不更改声明的情况下检查缓冲区溢出。

即便是第三个功能也无效

char *__strcpy(char *_Dst, const char *_Src)
{
    while ((*_Dst++ = *_Src++) != '\0');
    return _Dst;
}
函数指针_Dst内的

已更改并指向终止零。当你必须返回_Dst指向的字符串的第一个字符的地址时,从函数返回的这个地址。

同样适用于前两个功能。

答案 4 :(得分:0)

您可能需要size缓冲区,可以用来迭代那么多位置来复制检查缓冲区溢出(如果有的话),如下所示,

char *__strcpy(char *_Dst, const char *_Src, int size)
{
   while ((*_Dst++ = *_Src++) != '\0' && size--); //Iterate/copy to allocated Bytes
   return _Dst;
}

int main()
{
    int size;
    char *buf = (char *)calloc(size, sizeof(char)); // Here you know the size of buf
    __strcpy(buf, "Hello World", size);  // Send size as parameter
   // buffer size: 10, copy size: 12 (including '\0') - overflow
}

答案 5 :(得分:0)

如果你使用数组而不是指向char的指针,那么可以有一个真正的C ++实现。您可以将函数定义为模板,并使数组大小成为模板参数。

template < std::size_t D >
char* strcpy( char ( &dest )[ D ], const char* source )
{
    assert( D > std::strlen( source ) );
    ...
}

由于你真的只需要目的地大小,我已经把源尺寸保留了。

int main()
{
    char buf[ 10 ];
    // would assert, if assertions are enabled.
    strcpy( buf, "Hello World" ); 
}