[英]C++ memset / memcpy / strcpy implementation - check for buffer overflows
I have made basic memset / memcpy / strcpy implementations in C++, which work fine. 我已经在C ++中完成了基本的memset / memcpy / strcpy实现,可以正常工作。 However, is there a way of detecting buffer overflows if I was to do something like this:
但是,如果我要执行以下操作,是否可以检测缓冲区溢出:
Example: 例:
int main()
{
char *buf = (char *)calloc(10, sizeof(char));
__strcpy(buf, "Hello World");
// buffer size: 10, copy size: 12 (including '\0') - overflow
}
Implementations ( typedef unsigned int UINT
): 实现(
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;
}
Buffer overflows are not detectable in your program. 在程序中无法检测到缓冲区溢出。 The operating system is detecting them.
操作系统正在检测它们。 You only can check for potential pitfalls in the code (if/else,asserts,exceptions).
您只能在代码中检查潜在的陷阱(如果/否则,则断言,例外)。 Or you use profiling tools like valgrind.
或者您使用诸如valgrind之类的配置工具。
You can detect overflows, but only if you also implement your own memory-management routines. 您可以检测到溢出,但前提是您还必须实现自己的内存管理例程。 We used to do this when we wrote embedded software that ran on devices without a "real" operating system, before there were very many good debugging tools.
在编写非常好的调试工具之前,当我们编写在没有“真实”操作系统的设备上运行的嵌入式软件时,我们常常这样做。
The idea is to build your own wrapper around malloc()
(and calloc()
, in your case), that will allocate a few more bytes than the caller requests. 这个想法是围绕
malloc()
(和您的情况下的calloc()
构建您自己的包装器,该包装器将分配比调用方请求更多的字节。 Then set up a few "guard bytes" before and after the memory that was requested and initialize the entire buffer with recognizable data. 然后在请求的内存前后设置一些“保护字节”,并使用可识别的数据初始化整个缓冲区。 Also build a wrapper around
free()
that checks for the guard bytes before releasing the memory and generates an error if they've changed. 还要在
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.
}
In reality you'd probably want this to be smarter in several ways: 实际上,您可能希望通过以下几种方式使其更智能:
MAX_ALLOCS
. MAX_ALLOCS
的限制。 Allocs[]
on exit. Allocs[]
。 The safer way to detect buffer overflows is by providing your own implementation of calloc
instead. 检测缓冲区溢出的更安全方法是提供您自己的
calloc
实现。 Provide a few bytes padding before and after the returned block, set them to a known value (NOT 0 or 255), and when calling free
check that they're untouched. 在返回的块之前和之后提供一些字节填充,将它们设置为已知值(NOT 0或255),并在调用
free
检查它们是否未被触动。 Also, after calling free
you should overwrite the whole block (including the padding on both sides) to check for double free
calls. 同样,在
free
通话之后,您应该覆盖整个块(包括两侧的填充)以检查是否有双重free
通话。
All your mem* functions are invalid. 您所有的mem *函数均无效。 They copy (or set) objects of type unsigned int while they have to copy (or set) objects of type unsigned char.
他们复制(或设置)unsigned int类型的对象,而他们必须复制(或设置)unsigned char类型的对象。 Take into account that __Size can be an odd number.
考虑到__Size可以是一个奇数。 These functions theirself are unable to check buffer overflow without changing their declarations.
这些函数本身无法在不更改其声明的情况下检查缓冲区溢出。
Also even the third function is invalid 甚至第三个功能也是无效的
char *__strcpy(char *_Dst, const char *_Src)
{
while ((*_Dst++ = *_Src++) != '\0');
return _Dst;
}
inside the function pointer _Dst was changed and points to the terminating zero. 函数指针_Dst内部的值已更改,并指向终止的零。 This address you return from the function while you have to return the address of the first character of the string pointed by _Dst.
从函数返回的地址,而必须返回_Dst指向的字符串的第一个字符的地址。
The same is valid for the first two functions. 前两个功能同样有效。
You may need the size
of buffer you are owing, which can be used to iterate that many locations to copy checking buffer overflow if any as shown below, 您可能需要欠下的缓冲区
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
}
There can be a real C++ implementation, if you use arrays instead of pointers to char. 如果使用数组而不是指向char的指针,则可能存在真正的C ++实现。 You could define your functions as template and make the array size a template argument.
您可以将函数定义为模板,并使数组大小为模板参数。
template < std::size_t D >
char* strcpy( char ( &dest )[ D ], const char* source )
{
assert( D > std::strlen( source ) );
...
}
As you really just need the destination size, I've left the source size out. 由于您确实只需要目标大小,因此我省略了源大小。
int main()
{
char buf[ 10 ];
// would assert, if assertions are enabled.
strcpy( buf, "Hello World" );
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.