29. 비트 단위 스트림 리더의 경계·폭 검증 부재 (C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: MMORPG 델타 스냅샷을 대역폭 절약을 위해 "비트 패킹"으로 보낸다. 각 필드는
// 바이트 경계가 아니라 비트 단위 폭으로 이어 붙는다(예: HP 는 10비트, 상태 플래그 3비트).
// 서버는 클라가 보낸 비트스트림을 BitReader 로 필드 단위로 읽어 상태를 복원한다.
//
// 와이어 포맷(공격자가 조작 가능한 입력):
//   [엔티티 수: 8비트] 반복 { [필드폭 w: 5비트] [값: w 비트] }
//
// 요구사항:
//   - ReadBits(n): 스트림에서 n비트를 읽어 정수로 반환하고 커서를 전진한다.
//   - 파서는 선언된 개수/폭만큼 읽어 필드를 복원한다.
//   - 입력은 신뢰할 수 없다(길이·개수·폭 모두 조작 가능).
//
// 과제: (A)(B) 지점을 검토하라. 특정 입력에서 버퍼 밖을 읽거나 파싱이 폭주할 수 있다.
//       어떤 입력에서 그런지 밝히고 고쳐라.

using System.Collections.Generic;

public sealed class BitReader
{
    private readonly byte[] _buf;
    private int _bitPos;                     // 현재 비트 오프셋

    public BitReader(byte[] buf) { _buf = buf; _bitPos = 0; }

    // n비트를 MSB-first 로 읽어 정수로 반환
    public uint ReadBits(int n)
    {
        uint v = 0;
        for (int i = 0; i < n; i++)
        {
            int byteIdx = _bitPos >> 3;
            int bitIdx  = 7 - (_bitPos & 7);
            int bit = (_buf[byteIdx] >> bitIdx) & 1;   // (A)
            v = (v << 1) | (uint)bit;
            _bitPos++;
        }
        return v;
    }
}

public struct Field { public int Width; public uint Value; }

public static class SnapshotParser
{
    public static List<Field> Parse(byte[] packet)
    {
        var r = new BitReader(packet);
        var fields = new List<Field>();

        uint count = r.ReadBits(8);
        for (uint i = 0; i < count; i++)
        {
            int w = (int)r.ReadBits(5);            // 필드 폭(0..31)
            uint val = r.ReadBits(w);              // (B) 선언된 폭만큼 읽기
            fields.Add(new Field { Width = w, Value = val });
        }
        return fields;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.