39. 배치 개수 필드의 곱셈 오버플로와 경계 검증 우회 (과대 할당·OOB) (C#)

난이도 중 해설 보기 →

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

결함 코드 · C#
// 시나리오: 클라이언트가 "엔티티 스냅샷 배치" 패킷을 보낸다(관전/리플레이 업로드 등).
// 와이어 포맷(리틀 엔디안):
//   [0..4)   uint count          // 뒤따르는 엔티티 레코드 개수
//   [4..N)   EntityRecord[count]  // 각 레코드는 고정 12바이트
// EntityRecord(12B): int entityId(4) + float x(4) + float y(4)
//
// 요구사항:
//  1) count 와 실제 남은 페이로드 길이가 일치하지 않는 패킷은 거부한다.
//  2) 신뢰할 수 없는 count 로 큰 버퍼를 미리 할당하거나 버퍼 밖을 읽어선 안 된다.
//  3) 손상/악의적 패킷에도 서버는 예외로 죽거나 과대 할당하지 않고 안전하게 방어한다.
//
// 아래 파서에는 이 요구사항을 위반하는 지점이 있다. (A)(B)를 중심으로 진단·수정하라.

using System;

public struct EntityRecord
{
    public int EntityId;
    public float X;
    public float Y;
}

public static class SnapshotParser
{
    public const int RecordSize = 12;

    public static EntityRecord[] Parse(ReadOnlySpan<byte> payload)
    {
        if (payload.Length < 4)
            return Array.Empty<EntityRecord>();

        // (A) 개수 필드를 읽고 필요한 총 바이트 수를 계산한다.
        int count = (int)BitConverter.ToUInt32(payload.Slice(0, 4));
        int needed = 4 + count * RecordSize;
        if (payload.Length < needed)
            return Array.Empty<EntityRecord>();

        // (B) 개수만큼 레코드 배열을 만들고 순회하며 읽는다.
        var result = new EntityRecord[count];
        int offset = 4;
        for (int i = 0; i < count; i++)
        {
            result[i].EntityId = BitConverter.ToInt32(payload.Slice(offset, 4));
            result[i].X = BitConverter.ToSingle(payload.Slice(offset + 4, 4));
            result[i].Y = BitConverter.ToSingle(payload.Slice(offset + 8, 4));
            offset += RecordSize;
        }
        return result;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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