47. 섹션 오프셋 테이블의 offset/length 무검증 (임의 접근 경계 밖 읽기)
난이도 중해설 — 섹션 오프셋 테이블의 offset/length 무검증 (임의 접근 경계 밖 읽기)
난이도: 중상
요약
ReadSection이 디렉터리의 dir.Length로 버퍼를 할당하고 (A), dir.Offset에서 dir.Length만큼 Array.Copy 한다 (B). 그런데 Offset/Length가 이번 패킷의 유효 범위(_headerEnd <= Offset, Offset + Length <= _payloadLen, Length >= 0) 안에 있는지 전혀 검증하지 않는다. 오프셋은 순차 파서와 달리 임의 접근이라 공격자가 본문 밖(헤더 영역, 이전 패킷 잔여, 다른 섹션)을 가리키게 만들 수 있다. 재사용 풀 버퍼라 물리적 배열 범위 안이면 예외 없이 잔여 데이터를 조용히 읽어(정보 유출), 물리적 범위를 넘으면 ArgumentException(크래시/DoS). 과대 Length는 대량 할당(메모리 압박)도 유발한다.
문제점
- 분류태그: 역직렬화 임의 접근 오프셋/길이 무검증(입력 신뢰) → 경계 밖 읽기/정보 유출/과대 할당
- 증상: 조작 패킷으로 서버 메모리 잔여 노출(다른 세션 데이터 유출), 크래시, 과대 할당으로 인한 GC 압박/OOM.
- 재현조건:
Offset < _headerEnd(헤더 침범),Offset + Length > _payloadLen(본문 초과),Length과대, 음수 값. - 근본원인: offset/length가 신뢰 불가 입력인데 payload 경계와 대조하지 않는다. 순차 파서는 커서가 자연히 앞으로만 가지만, 오프셋 테이블은 임의 위치를 지시하므로 각 접근마다 명시적 범위 검증이 필요하다.
수정안
읽기 전에 오프셋·길이를 payload 범위로 검증하고, 실패 시 패킷을 거부한다. 오버플로도 주의(offset+length가 int 오버플로).
public bool TryReadSection(SectionDir dir, out byte[] section)
{
section = Array.Empty<byte>();
if (dir.Length < 0 || dir.Offset < _headerEnd) return false;
// 오버플로 안전 비교: long 승격
if ((long)dir.Offset + dir.Length > _payloadLen) return false;
if (dir.Length > MaxSectionBytes) return false; // 과대 할당 방어
section = new byte[dir.Length];
Array.Copy(_buf, dir.Offset, section, 0, dir.Length);
return true;
}
Parse는 반환값을 확인하고 하나라도 실패하면 폐기. 섹션끼리 겹치면 안 되는 규약이면 오프셋 단조·비중첩도 검증.
더 나은 설계
- 경계는 payload 슬라이스로 강제:
ReadOnlySpan<byte>로 payload 구간만 넘기고span.Slice(offset, length)를 쓰면 슬라이스가 범위를 자동 검사하고 잔여 노출이 불가능해진다. - 오프셋을 상대(본문 시작 기준)로 정의하고 전 섹션을 한 번에 검증(디렉터리 스캔 단계에서 모두 통과해야 파싱 진행).
- 재사용 풀 버퍼는 유효 길이 밖 접근을 물리적으로 차단(슬라이스) 또는 사용 전 클리어.
면접 포인트
- 순차 파서 vs 오프셋 테이블(임의 접근)의 검증 차이 — 오프셋마다 경계 검사 필요.
- 정수 오버플로를 고려한 경계 비교(
offset+length를 long으로). - 재사용 풀 버퍼가 정보 유출 벡터가 되는 이유와 span/슬라이스 방어.
해설 — 섹션 오프셋 테이블 offset/length 무검증 (OOB read UB, C++)
난이도: 중상
요약
ReadSection이 dir.length로 벡터를 만들고 (A), buf_ + dir.offset에서 dir.length만큼 memcpy 한다 (B). offset/length가 payload 범위(header_end_ <= offset, offset + length <= payload_len_) 안인지 검증하지 않는다. 임의 접근이라 공격자가 offset을 본문 밖으로 지정하면 C#의 예외와 달리 곧바로 out-of-bounds read(UB): 재사용 버퍼 잔여를 읽어 정보 유출, 물리 버퍼를 넘으면 세그폴트/인접 메모리 노출. length가 uint32_t라 과대값이면 대량 할당·memcpy 폭주. offset + length가 32비트 오버플로하면 검증을 넣어도 우회될 수 있어 승격 비교가 필요하다.
문제점
- 분류태그: 역직렬화 임의 접근 offset/length 무검증 → OOB read(UB)/정보 유출/과대 할당
- 증상: 조작 패킷으로 서버 메모리 잔여·인접 세션 데이터 유출, 크래시, 과대 할당.
- 재현조건:
offset < header_end,offset+length > payload_len, 과대length, 오버플로 값. ASan: heap-buffer-overflow. - 근본원인: 신뢰 불가 offset/length를 payload 경계와 대조하지 않음. 임의 접근은 접근마다 검증 필요.
수정안
읽기 전에 경계를 오버플로 안전하게 검증하고 실패를 전파한다.
bool TryReadSection(const SectionDir& dir, std::vector<uint8_t>& out) {
out.clear();
if (dir.offset < header_end_) return false;
// uint64_t 승격으로 오버플로 회피
uint64_t end = static_cast<uint64_t>(dir.offset) + dir.length;
if (end > payload_len_) return false;
if (dir.length > kMaxSectionBytes) return false; // 과대 할당 방어
out.resize(dir.length);
std::memcpy(out.data(), buf_ + dir.offset, dir.length);
return true;
}
ParseProfile은 반환값 확인 후 실패 시 폐기. 주요 스니펫은 g++ -std=c++17 -fsyntax-only로 검증.
더 나은 설계
- payload 슬라이스 강제:
std::span<const uint8_t>(C++20)로 payload 구간만 넘기고span.subspan(offset, length)가 범위를 확인하게. 잔여 노출 원천 차단. - 디렉터리 전체를 파싱 전에 일괄 검증(모두 통과해야 진행), 필요하면 비중첩·단조 조건도.
- fuzzing으로 파서 상시 검증.
면접 포인트
- 순차 파서 vs 오프셋 테이블의 검증 차이(임의 접근은 접근마다 검사).
offset+length정수 오버플로를 고려한 승격 비교.- C++에서 경계 미검증 read가 즉시 UB인 이유와 span 방어.