繁体   English   中英

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

[英]C++ memset / memcpy / strcpy implementation - check for buffer overflows

我已经在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;
}

在程序中无法检测到缓冲区溢出。 操作系统正在检测它们。 您只能在代码中检查潜在的陷阱(如果/否则,则断言,例外)。 或者您使用诸如valgrind之类的配置工具。

您可以检测到溢出,但前提是您还必须实现自己的内存管理例程。 在编写非常好的调试工具之前,当我们编写在没有“真实”操作系统的设备上运行的嵌入式软件时,我们常常这样做。

这个想法是围绕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[]
  • 检测到错误时,打印更多信息和/或立即退出。

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

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

甚至第三个功能也是无效的

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

函数指针_Dst内部的值已更改,并指向终止的零。 从函数返回的地址,而必须返回_Dst指向的字符串的第一个字符的地址。

前两个功能同样有效。

您可能需要欠下的缓冲区size ,该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
}

如果使用数组而不是指向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" ); 
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM