17. 가변길이 문자열/바이트 필드 역직렬화와 길이 검증 (C#)

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 가변길이 문자열/바이트 필드 역직렬화와 길이 검증 (서버-클라)
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 / 서버-클라):
//   - 클라이언트가 보내는 패킷 안에는 가변길이 문자열 필드가 들어있다(닉네임,
//     채팅 본문, 우편 제목 등). 인코딩은 [u16 len][len 바이트 UTF-8] 형식이고,
//     첨부 같은 큰 블롭은 [u32 len][len 바이트] 형식이다.
//   - 우편 일괄 패킷은 [u16 count] 뒤에 count 개의 (제목 문자열) 이 이어진다.
//   - 이 길이/개수 값은 모두 "클라이언트가 보낸 값" 이다(신뢰 불가, 공격자 제어 가능).
//   - 서버는 수신 버퍼(byte[])에서 이 구조를 역직렬화한다.
//
// 요구사항:
//   - 신뢰할 수 없는 길이/개수로 버퍼 경계를 넘어 읽으면 안 된다.
//   - 공격자가 부풀린 길이/개수로 거대한 메모리를 선할당하게 만들면 안 된다(DoS 금지).
//   - 필드 길이/개수에는 프로토콜 상한이 있어야 하고, UTF-8 유효성도 보장돼야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 오버리드·DoS·파싱붕괴가 나는지
//   설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

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

public class Reader
{
    private readonly byte[] _data;
    private int _pos;

    public Reader(byte[] data) { _data = data; _pos = 0; }

    public ushort ReadU16()
    {
        // (A)
        ushort v = (ushort)(_data[_pos] | (_data[_pos + 1] << 8));
        _pos += 2;
        return v;
    }

    public uint ReadU32()
    {
        uint v = (uint)(_data[_pos]
                      | (_data[_pos + 1] << 8)
                      | (_data[_pos + 2] << 16)
                      | (_data[_pos + 3] << 24));
        _pos += 4;
        return v;
    }

    // [u16 len][len 바이트] 문자열
    public string ReadString()
    {
        ushort len = ReadU16();
        // (B)(C)
        string s = Encoding.UTF8.GetString(_data, _pos, len);
        _pos += len;
        return s;
    }

    // [u32 len][len 바이트] 블롭(첨부)
    public byte[] ReadBlob()
    {
        uint len = ReadU32();
        // (B)
        byte[] buf = new byte[len];
        Array.Copy(_data, _pos, buf, 0, (int)len);   // (C)
        _pos += (int)len;
        return buf;
    }
}

public static class MailParser
{
    // 우편 일괄: [u16 count] 그 뒤 count 개의 제목 문자열
    public static List<string> ParseMailTitles(byte[] buf)
    {
        var r = new Reader(buf);
        ushort count = r.ReadU16();
        // (B)
        var titles = new List<string>(count);
        for (int i = 0; i < count; i++)
            titles.Add(r.ReadString());    // (A)(C)
        return titles;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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