我已经在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;
}
答案 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" );
}