| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- 그래프 탐색
- JavaScript
- Express.js
- SCC
- DP
- Behavior Design Pattern
- Strongly Connected Component
- 비트필드를 이용한 dp
- 벨만-포드
- Spin Lock
- PROJECT
- 게임 서버 아키텍처
- Delete
- Prisma
- 이분 탐색
- 2-SAT
- 최소 공통 조상
- Lock-free Stack
- 자바스크립트
- Github
- 강한 연결 요소
- trie
- map
- ccw 알고리즘
- 비트마스킹
- localstorage
- reference counting
- 트라이
- Binary Lifting
- R 그래프
Archives
- Today
- Total
dh_0e
[C++/Game Server] Stomp Allocator (Use-after-free & Overflow 오류 검증) 본문
C++/Game Server
[C++/Game Server] Stomp Allocator (Use-after-free & Overflow 오류 검증)
dh_0e 2026. 3. 10. 23:56Use-After-Free 오류용 Stomp Allocator
new & delete
int main() {
SYSTEM_INFO info;
::GetSystemInfo(&info);
cout << info.dwPageSize << endl; // 페이지 크기
cout << info.dwAllocationGranularity << endl; // 할당 단위
Knight* test = new Knight;
test->_hp = 100;
delete test;
test->_hp = 200;
return 0;
}
- test->_hp=200; 에서 CRASH가 나지 않음
- new, delete 같은 힙 할당이 유동적으로 메모리를 관리하기 때문에 다음과 같이 에러를 잡지 못하는 경우가 발생
VirtualAlloc & VirtualFree
int main() {
SYSTEM_INFO info;
::GetSystemInfo(&info);
cout << info.dwPageSize << endl; // 페이지 크기
cout << info.dwAllocationGranularity << endl; // 할당 단위
int* test = (int*)::VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
*test = 100;
::VirtualFree(test, 0, MEM_RELEASE);
*test = 200;
return 0;
}
- Window api(VirtualAlloc, VirtualFree)를 이용해서 직접 메모리를 사용하고 해제하는 방법
- OS 레벨에서 돌아가기 때문에 기본적으로 페이지 단위(최소 4KB)로 메모리 할당이 가능함
- *test = 200;에서 무조건 충돌이 나며 메모리 침범 이슈를 잡아줌
OS가 제공하는 가장 강력한 보안관(페이지 보호 기능)을 고용하기 위해, 그 보안관의 최소 근무 단위인 4KB를 맞춰주는 것
StompAllocator
- Window api를 통한 메모리 할당으로 StompAllocator을 구성
- 언리얼 엔진에서도 마련이 되어있으며, 광범위하게 사용되는 정책
- 뭔가를 효율적으로 돌리는 건 아니고 버그를 잡는데 유용함
- 특히 메모리 오염 버그를 잘 잡아줌
Allocator.h
/*-------------------------
StompAllocator
-------------------------*/
class StompAllocator {
// 페이지 단위(4KB)로 할당
enum { PAGE_SIZE = 0x1000 };
public:
static void* Alloc(int32 size);
static void Release(void* ptr);
};
- 페이지 단위(4KB)로 할당
Allocator.cpp
/*-------------------------
StompAllocator
-------------------------*/
void* StompAllocator::Alloc(int32 size) {
const int64 pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
return ::VirtualAlloc(NULL, pageCount * PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
}
void StompAllocator::Release(void* ptr) {
::VirtualFree(ptr, 0, MEM_RELEASE);
}
- size가 8byte처럼 작은 경우에도 페이지 단위(4KB)로 할당해 주기 때문에 메모리 낭비가 발생할 수 있음
- 하지만 메모리 오염 버그를 잡는데 유용하므로 디버깅 모드에서 사용하면 유용함
CoreMacro.h
/*----------------
Memory
----------------*/
#ifdef _DEBUG
#define xxalloc(size) StompAllocator::Alloc(size)
#define xxrelease(ptr) StompAllocator::Release(ptr)
#else
#define xxalloc(size) BaseAllocator::Alloc(size)
#define xxrelease(ptr) BaseAllocator::Release(ptr)
#endif
- StompAllocation은 속도도 느리고, 메모리 낭비가 발생할 수 있지만, 메모리 오염 버그를 잡는데 유용하므로 디버깅 모드에서 사용하도록 함
GameServer.cpp
class Knight {
public:
Knight() { cout << "Knight()" << endl; }
Knight(int32 hp) : _hp(hp) { cout << "Knight(int32 hp): " << hp << endl; }
~Knight() { cout << "~Knight()" << endl; }
int32 _hp = 100;
int32 _mp = 100;
};
int main() {
Knight* knight = xnew<Knight>(100);
xdelete(knight);
knight->_hp = 200;
return 0;
}
- knight->_hp = 200; 에서 메모리가 해제된 곳을 접근하려고 한다고 시스템에서 CRASH를 내줌
Overflow 오류 검증 추가 Stomp Allocator
GameServer.cpp
class Player {
public:
Player() { cout << "Player()" << endl; }
virtual ~Player() { cout << "~Player()" << endl; }
};
class Knight : public Player {
public:
Knight() { cout << "Knight()" << endl; }
Knight(int32 hp) : _hp(hp) { cout << "Knight(int32 hp): " << hp << endl; }
~Knight() { cout << "~Knight()" << endl; }
int32 _hp = 100;
int32 _mp = 100;
};
int main() {
Knight* knight2 = (Knight*)xnew<Player>(); // Player로 메모리 할당 후 Knight으로 강제 형변환
knight2->_hp = 200; // Overflow 발생
return 0;
}
- StompAllocator를 사용하면 메모리 오염을 잡기 위해 메모리 할당을 페이지 단위(4KB)로 함
- 이 때문에 Player가 할당된 공간 뒤에 아주 작은 약간의 공간(Padding)이 남아있다면 Knight의 _hp가 그 안에 들어가 원래라면 Overflow가 나지만 이를 잡지 못하는 상황이 발생할 수 있음
- 페이지 단위 [ ]에서 [ [A] ] 다음과 같이 A에 메모리를 할당하면 뒤에 공간에 원래였으면 들어가지 못하는 공간을 _hp의 공간으로 보고 값을 넣어버림
- 이를 [ [A]] 이렇게 페이지 경계선에 딱 맞게 메모리 주소를 위치시키면 Underflow는 날 수 있지만 Overflow를 확실하게 잡을 수 있게 됨
Allocator.cpp
/*-------------------------
StompAllocator
-------------------------*/
void* StompAllocator::Alloc(int32 size) {
const int64 pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
const int64 dataOffset = pageCount * PAGE_SIZE - size;
void* baseAddress = ::VirtualAlloc(NULL, pageCount * PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
void* dataAddress = static_cast<void*>((int8*)baseAddress + dataOffset);
return dataAddress;
}
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);
}
- dataOffset을 계산하여 baseAddress에 더해주면 메모리가 할당된 공간(페이지 단위)의 마지막 경계선에 객체가 위치하게 됨
- size: 8 (Player 객체 크기)
- pageCount: (8 + 4096 - 1) / 4096 = 1 (1 페이지면 충분함)
- dataOffset: 1 * 4096 - 8 = 4088
- 이 4088은 페이지 시작점에서 얼마나 떨어져서 데이터를 배치할 것인가?를 결정하는 값
- 메모리 해제할 때도 baseAddress를 계산하여 메모리 할당의 시작 주소로 갈 수 있음
- 나머지(address % PAGE_SIZE): 현재 주소(4088)에서 페이지 크기(4096)로 나눈 나머지, 즉 페이지 시작점으로부터 얼마나 떨어져 있는지(Offset)를 구해줌
- address - 나머지: 현재 주소에서 그 오프셋만큼 앞으로 돌아가면 정확히 해당 페이지의 시작 주소가 나옴
size가 페이지 단위보다 클 경우 (ex. StompAllocator::Alloc(5000))
- baseAddress: OS가 준 2페이지의 시작점이 0x0000이라고 가정
- Alloc
- size: 5000
- pageCount: (5000 + 4096 - 1) / 4096 = 9095 / 4096 = 2
- 5000 Byte를 담으려면 1페이지로는 부족하므로 2페이지(8192 Byte)를 할당해야 함
- dataOffset: 2 * 4096 - 5000 = 8192 - 5000 = 3192
- 2개의 페이지(총 8192 Byte) 중에서 앞부분 3192 Byte를 비우고, 3192번지부터 데이터를 채우겠다는 뜻
- 메모리 할당한 부분(8192 Byte)의 경계선까지 데이터를 채울 수 있음
- return baseAddress + dataOffset = 0 + 3192 = 3192를 return
- Release
- address: 3192
- address % PAGE_SIZE: 3192 % 4096 = 3192
- 이 나머지가 바로 아까 계산했던 dataOffset과 일치
- baseAddress: 3192 - 3192 = 0x0000
- 주소에서 나머지를 빼버리니, 다시 2페이지의 시작점인 0x0000으로 돌아옴

'C++ > Game Server' 카테고리의 다른 글
| [C++/Game Server] STL Allocator (0) | 2026.03.11 |
|---|---|
| [C++/Game Server] Allocator (New, Delete Operator Overloading) + std::forward, Placement New (0) | 2026.03.10 |
| [C++/Game Server] Reference Counting (in Shared Pointer) (1) | 2026.03.07 |
| [C++/Game Server] DeadLock Profiler (+ release, debug 모드) (0) | 2026.02.19 |
| [C++/Game Server] Reader-Writer Lock (0) | 2026.02.12 |
