[英]Using memset and memcpy correctly to initialize a character array in C++
[英]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.