13. 가변길이(VarInt) 정수 디코딩과 악성 길이 방어 — C#

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 가변길이(VarInt) 정수 디코딩과 악성 길이 방어
// ----------------------------------------------------------------------------
// 시나리오:
//   - 클라이언트-서버 패킷의 여러 필드(아이템 개수, 문자열 길이 등)를 공간 절약을
//     위해 VarInt(LEB128 유사) 로 인코딩한다.
//     · 각 바이트의 하위 7비트가 값, 최상위 비트(0x80)가 "다음 바이트 계속" 표시.
//   - 서버는 수신 버퍼(고정 크기, 신뢰 불가)에서 VarInt 를 읽어 개수/길이를 얻고,
//     그 개수만큼 아이템을 파싱한다.
//   - 입력은 전적으로 신뢰할 수 없다(치터/퍼저/변조 패킷이 임의 바이트열을 보낸다).
//
// 요구사항:
//   - 손상/악의적 입력에도 서버가 크래시·무한루프·과대 할당(메모리 고갈) 없이
//     안전하게 거부해야 한다.
//   - 디코딩된 개수/길이는 남은 버퍼로 실제 수용 가능한지 검증해야 한다.
//   - VarInt 는 64비트 범위를 넘기지 않아야 하고, 정수 오버플로가 없어야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 어떤 입력이 무엇을 깨뜨리는지(예외/무한루프/DoS)
//   설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Text;

public struct Reader
{
    public byte[] Buf;
    public int    Pos;
}

public class Item
{
    public uint   Id;
    public string Name;
}

public static class VarIntParser
{
    // 하나의 VarInt 를 읽어 값을 반환한다.
    public static ulong ReadVarInt(ref Reader r)
    {
        ulong result = 0;
        int   shift  = 0;
        while (true)
        {
            // (A) 다음 바이트를 읽는다
            byte b = r.Buf[r.Pos++];
            // (B) 7비트씩 누적
            result |= (ulong)(b & 0x7F) << shift;
            if ((b & 0x80) == 0)
                break;          // 계속 비트가 꺼지면 끝
            shift += 7;
        }
        return result;
    }

    // 길이프리픽스로 추려진 한 메시지 본문(buf)에서 아이템 목록을 파싱한다.
    // 형식: [VarInt count][ item: VarInt id, VarInt nameLen, nameLen bytes ] * count
    public static bool ParseItems(byte[] buf, out List<Item> outItems)
    {
        var r = new Reader { Buf = buf, Pos = 0 };
        outItems = new List<Item>();

        ulong count = ReadVarInt(ref r);

        // (C) count 개수만큼 미리 공간 확보
        outItems.Capacity = (int)count;

        for (ulong i = 0; i < count; ++i)
        {
            var it = new Item();
            it.Id = (uint)ReadVarInt(ref r);

            ulong nameLen = ReadVarInt(ref r);
            // (D) 이름 바이트를 그대로 복사
            it.Name = Encoding.UTF8.GetString(r.Buf, r.Pos, (int)nameLen);
            r.Pos += (int)nameLen;

            outItems.Add(it);
        }
        return true;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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