Server/C++

메모리 오염과 Stomp Allocator

Juzdalua 2024. 8. 5. 02:47

메모리에 쓰레기값이 할당된 데이터를 조작하는 행위

Use-After-Free

 

new 명령어로 메모리를 할당한 후, delete 명령어로 메모리를 해제한다.

delete 명령어 이후 해당 메모리를 가르키는 객체는 임의의 쓰레기값을 가르키게 된다.

즉 객체는 아직 살아있는 상태이고, 객체 데이터를 수정이 가능한 상태가 된다.

-> 운영체제는 가상메모리를 활용하고 있기 때문에, 임의의 메모리에 접근하여 문제가 발생한다.

Knight* knight = new Knight();
delete knight;
knight->_hp = 10; // 쓰레기 데이터 참조
vector<int32> v{ 1,2,3,4,5 };
for (int32 i = 0; i < v.size(); i++) {
	int32 value = v[i];

	if (value == 3) {
		v.clear();
		// break;
	}
}
class Player {

};

class Knight : public Player {
public:
	Knight() { cout << "생성자" << endl; }
	~Knight() { cout << "소멸자" << endl; }

	int32 _hp = 100;
};

int main()
{
	Player* p = new Player();
	Knight* k = static_cast<Knight>(p);
	k->_hp = 10;
}

 

Virtual Alloc

 

malloc-free, new-delete 대신 사용할 수 있는 메모리 할당 방법이다.

-> VirtualAlloc - VirtualFree

 

메모리 해제시 해당 메모리를 가르키는 데이터 또한 더 이상 조작할 수 없는 값이 된다.

 

가상메모리 할당에 있어 메모리는 페이지 단위로 관리된다.

운영체제에서 적용되는 페이지의 크기와 허용범위를 입력해야한다.

페이지크기는 윈도우에서 보통 4kb이다.

허용범위는 Read, Write, R&W, 접근금지 등 메모리에서 수행할 동작을 명시한다.

SYSTEM_INFO info;
GetSystemInfo(&info);

info.dwPageSize; // 메모리를 관리하는 페이지의 단위. 4096 = 4B = 0x1000
info.dwAllocationGranularity; // 메모리 주소의 배수 단위. 65536 = 64B = 0x10000

 

int* test = (int*)VirtualAlloc(NULL, 4, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
*test = 1;
VirtualFree(test, 0, MEM_RELEASE);
*test = 2; // ERROR

 

https://learn.microsoft.com/ko-kr/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

 

VirtualAlloc 함수(memoryapi.h) - Win32 apps

호출 프로세스의 가상 주소 공간에서 페이지 영역의 상태를 예약, 커밋 또는 변경합니다. (VirtualAlloc)

learn.microsoft.com

 


Stomp Allocator

메모리를 페이지 단위로 관리하기 때문에 작은 메모리를 사용하려해도 큰 메모리가 할당되는 단점이 있다.

메모리 오염 문제를 해결하기 위한 메모리 할당법. => 개발단계에서 메모리 버그를 찾기에 용이하다.

// Allocator.h

class StompAllocator {
	enum { PAGE_SIZE = 0x1000 }; // 4096byte = 4kb
public:
	static void* Alloc(int32 size);
	static void Release(void* ptr);
};
// Allocator.cpp

void* StompAllocator::Alloc(int32 size)
{
	// 메모리 관리 페이지 할당
	const int64 pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;

	// 오버플로우 막기 위해 주소 위치 변경
	// [[data]      ] -> [      [data]]
	const int64 dataOffset = pageCount * PAGE_SIZE - size;
	void* baseAddress = VirtualAlloc(NULL, pageCount * PAGE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

	return static_cast<void*>(static_cast<int8*>(baseAddress) + dataOffset);
}

void StompAllocator::Release(void* ptr)
{
	const int64 address = reinterpret_cast<int64>(ptr);
	const int64 baseAddress = address - (address % PAGE_SIZE);

	VirtualFree(reinterpret_cast<void*>(baseAddress), 0, MEM_RELEASE);
}
// main.cpp

#include "pch.h"
#include "CorePch.h"
#include "CoreMacro.h"
#include <iostream>
#include "Memory.h"

class Player{};

class Knight : public Player{
public:
	Knight() { cout << "생성자" << endl; }
	~Knight() { cout << "소멸자" << endl; }

	int32 _hp = 100;
};

int main()
{
	Knight* k = xnew<Knight>();
	xdelete(k);
	k->_hp = 1; // Error

	Knight* p = (Knight*)xnew<Player>();
	p->_hp = 2;
	xdelete(p); // Error - Memory Overflow
}