19. 압축 패킷 해제 시 압축 폭탄(decompression bomb) 방어 (C#)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 압축 패킷 해제 시 압축 폭탄(decompression bomb) 방어
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 · 서버-클라/서버-서버):
// - 대역폭 절약을 위해 일부 패킷(채팅 히스토리, 맵 청크, 배치 업데이트 등)은 압축돼 온다.
// 와이어 포맷: [u32 compressedLen][u32 originalLen][compressedLen 바이트 압축 데이터].
// - originalLen 은 "보낸 쪽이 선언한 원본 크기" 다(신뢰 불가, 공격자 제어 가능).
// - 서버는 DeflateStream 으로 풀어 원본 바이트를 얻는다.
// - 공격자는 작은 압축 데이터가 거대한 원본으로 팽창하는 "압축 폭탄" 을 만들 수 있다.
//
// 요구사항:
// - 선언된 originalLen 을 신뢰해 거대한 버퍼를 선할당하면 안 된다(over-allocation DoS 금지).
// - 압축 해제 출력 총량에 상한이 있어야 한다(ratio/absolute bomb DoS 금지).
// - compressedLen 이 실제 수신 버퍼 경계를 넘으면 안 된다.
// - 풀어낸 실제 크기와 선언 크기의 일치(또는 정책 위반 시 거부)를 검증해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 작은 패킷으로 OOM/CPU 고갈이 일어나는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.IO;
using System.IO.Compression;
public static class PacketCompression
{
private static uint ReadU32(byte[] b, int off)
{
return (uint)(b[off] | (b[off + 1] << 8) | (b[off + 2] << 16) | (b[off + 3] << 24));
}
// 압축 패킷을 풀어 원본 바이트를 돌려준다.
public static byte[] DecompressPacket(byte[] buf)
{
uint compressedLen = ReadU32(buf, 0);
uint originalLen = ReadU32(buf, 4); // 보낸 쪽이 선언한 원본 크기
// (A) 선언된 originalLen 으로 출력 버퍼 즉시 선할당 (신뢰)
var preallocated = new byte[originalLen];
// (D) compressedLen 이 실제 버퍼 경계 안인지 검사 안 함
using var input = new MemoryStream(buf, 8, (int)compressedLen);
using var inflate = new DeflateStream(input, CompressionMode.Decompress);
using var outStream = new MemoryStream();
// (B) 출력 총량 상한 없이 끝까지 전부 복사 — 압축 폭탄에 그대로 노출
inflate.CopyTo(outStream);
// (C) produced 와 originalLen 일치 검증 없음, 압축비 상한 없음
return outStream.ToArray();
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 압축 패킷 해제 시 압축 폭탄(decompression bomb) 방어
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 · 서버-클라/서버-서버):
// - 대역폭 절약을 위해 일부 패킷(채팅 히스토리, 맵 청크, 배치 업데이트 등)은 압축돼 온다.
// 와이어 포맷: [u32 compressedLen][u32 originalLen][compressedLen 바이트 압축 데이터].
// - originalLen 은 "보낸 쪽이 선언한 원본 크기" 다(신뢰 불가, 공격자 제어 가능).
// - 서버는 스트리밍 압축 해제기(zlib inflate 류, 아래 Inflater)로 풀어 원본 바이트를 얻는다.
// - 공격자는 아주 작은 압축 데이터가 거대한 원본으로 팽창하는 "압축 폭탄" 을 만들 수 있다
// (예: 1KB -> 1GB, 압축비 수백만 배).
//
// 요구사항:
// - 선언된 originalLen 을 신뢰해 거대한 버퍼를 선할당하면 안 된다(over-allocation DoS 금지).
// - 압축 해제 출력 총량에 상한이 있어야 한다(ratio/absolute bomb DoS 금지).
// - compressedLen 이 실제 수신 버퍼 경계를 넘으면 안 된다.
// - 풀어낸 실제 크기와 선언 크기의 일치(또는 정책 위반 시 거부)를 검증해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 작은 패킷으로 OOM/CPU 고갈이 일어나는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <cstddef>
// 외부 스트리밍 해제기(zlib inflate 류)를 감싼 인터페이스.
// step(): 입력에서 일부를 소비해 out[0..outCap) 에 일부를 쓴다.
// - 반환값: 이번 호출에 out 에 쓴 바이트 수.
// - done=true 로 설정되면 스트림 종료.
struct Inflater {
size_t step(const uint8_t* in, size_t inLen,
uint8_t* out, size_t outCap, bool& done);
};
class PacketReader {
public:
PacketReader(const uint8_t* d, size_t n) : data_(d), size_(n) {}
uint32_t readU32() {
uint32_t v = static_cast<uint32_t>(data_[pos_]) |
(static_cast<uint32_t>(data_[pos_ + 1]) << 8) |
(static_cast<uint32_t>(data_[pos_ + 2]) << 16) |
(static_cast<uint32_t>(data_[pos_ + 3]) << 24);
pos_ += 4;
return v;
}
size_t pos() const { return pos_; }
private:
const uint8_t* data_;
size_t size_;
size_t pos_ = 0;
};
// 압축 패킷을 풀어 원본 바이트를 돌려준다.
std::vector<uint8_t> decompressPacket(const uint8_t* buf, size_t n) {
PacketReader r(buf, n);
uint32_t compressedLen = r.readU32();
uint32_t originalLen = r.readU32(); // 보낸 쪽이 선언한 원본 크기
// (D) compressedLen 이 실제 남은 바이트 안에 있는지 검사 안 함
const uint8_t* in = buf + 8;
// (A) 선언된 originalLen 으로 출력 버퍼를 즉시 선할당 (신뢰)
std::vector<uint8_t> out(originalLen);
Inflater inf;
size_t produced = 0;
bool done = false;
// (B) 출력 총량 상한 없이 끝날 때까지 계속 풀어 out 에 채운다
while (!done) {
if (produced == out.size())
out.resize(out.size() * 2 + 1024); // 부족하면 무한정 키운다
produced += inf.step(in, compressedLen,
out.data() + produced, out.size() - produced, done);
}
// (C) 실제 produced 와 originalLen 일치 검증 없음, 압축비 상한 없음
out.resize(produced);
return out;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.