메모리 안전성(Memory Safety)은 프로그램이 잘못된 메모리 접근을 하지 않도록 보장하는 속성이다. CVE의 약 70%가 메모리 안전성 취약점에서 비롯된다.
주요 메모리 취약점 유형
| 취약점 | 설명 | 예시 취약점 |
|---|
| 버퍼 오버플로우 | 할당된 버퍼 초과 쓰기 | 스택/힙 오버플로우 |
| Use-After-Free | 해제된 메모리 참조 | UAF |
| Null 포인터 역참조 | 널 포인터 접근 | NULL dereference |
| 이중 해제 | 동일 메모리 두 번 free | Double free |
| 초기화 안 된 메모리 | 쓰레기 값 참조 | Uninitialized read |
| 경계 외 읽기/쓰기 | 배열 인덱스 오류 | OOB read/write |
C 버퍼 오버플로우 예시
c
// 취약한 코드
void vulnerable(char *input) {
char buffer[64];
strcpy(buffer, input); // 길이 검사 없음!
// input이 64자 초과 시 → 스택 오버플로우
// → 반환 주소 덮어쓰기 → 임의 코드 실행
}
// 안전한 코드
void safe(const char *input) {
char buffer[64];
strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = 0; /* null terminator */
}
Rust의 메모리 안전성
rust
// Rust: 컴파일 타임에 메모리 안전성 보장
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1은 move됨
// println!("{}", s1); // 컴파일 오류! s1은 더 이상 유효하지 않음
// 불변 참조
let s3 = String::from("world");
let r1 = &s3;
let r2 = &s3; // 여러 불변 참조 허용
println!("{} {}", r1, r2);
// 가변 참조는 하나만
let mut s4 = String::from("hello");
let r3 = &mut s4;
r3.push_str(", world");
// let r4 = &mut s4; // 컴파일 오류! 가변 참조 중복 불가
}
방어 기법
ASLR (Address Space Layout Randomization):
메모리 주소 무작위화 → 공격자가 주소 예측 불가
Stack Canary:
반환 주소 앞에 무작위 값 배치 → 덮어쓰면 탐지
NX/DEP (No-Execute / Data Execution Prevention):
데이터 영역에서 코드 실행 불가
CFI (Control Flow Integrity):
합법적 제어 흐름만 허용
SafeStack / ShadowStack:
반환 주소를 별도 스택에 보관
관련 문서
- •[[zero-day|제로데이 취약점]]
- •[[privilege-escalation|권한 상승]]
- •[[supply-chain-attack|공급망 공격]]
- •[[secure-coding|시큐어 코딩]]