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++
// 시나리오: 클라이언트가 "엔티티 스냅샷 배치" 패킷을 보낸다(관전/리플레이 업로드 등).
// 와이어 포맷(리틀 엔디안):
// [0..4) uint32_t count // 뒤따르는 엔티티 레코드 개수
// [4..N) EntityRecord[count] // 각 레코드는 고정 12바이트
// EntityRecord(12B): int32 entityId(4) + float x(4) + float y(4)
//
// 요구사항:
// 1) count 와 실제 남은 페이로드 길이가 일치하지 않는 패킷은 거부한다.
// 2) 신뢰할 수 없는 count 로 큰 버퍼를 미리 할당하거나 버퍼 밖을 읽어선 안 된다.
// 3) 손상/악의적 패킷에도 서버는 정의되지 않은 동작 없이 방어한다.
//
// 아래 파서에는 이 요구사항을 위반하는 지점이 있다. (A)(B)를 중심으로 진단·수정하라.
#include <cstdint>
#include <cstring>
#include <vector>
struct EntityRecord {
int32_t entityId;
float x;
float y;
};
class SnapshotParser {
public:
static constexpr uint32_t RecordSize = 12;
// data: 페이로드 시작, len: 페이로드 바이트 수
static std::vector<EntityRecord> parse(const uint8_t* data, uint32_t len) {
std::vector<EntityRecord> result;
if (len < 4) return result;
// (A) 개수 필드를 읽고 필요한 총 바이트 수를 계산한다.
uint32_t count = 0;
std::memcpy(&count, data, 4);
uint32_t needed = 4 + count * RecordSize;
if (len < needed) return result;
// (B) 개수만큼 예약하고 순회하며 읽는다.
result.resize(count);
uint32_t offset = 4;
for (uint32_t i = 0; i < count; ++i) {
std::memcpy(&result[i].entityId, data + offset, 4);
std::memcpy(&result[i].x, data + offset + 4, 4);
std::memcpy(&result[i].y, data + offset + 8, 4);
offset += RecordSize;
}
return result;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.