28. 존재 비트맵(presence bitmap) 역직렬화의 경계·예약 비트 검증 결함 (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 존 서버가 클라이언트로 엔티티 상태 변경(델타)을 보낼 때, 변한 필드만 골라
// 보내려고 "존재 비트맵(presence bitmap)"을 쓴다. 선두 2바이트(16비트) 각 비트가
// "해당 옵션 필드가 이 메시지에 포함됨"을 뜻하고, 켜진 비트 순서대로 각 필드의
// 고정 크기 값이 뒤따른다. 수신 측은 비트맵을 읽고 켜진 필드만 역직렬화한다.
//
// 요구사항:
// - buf 는 신뢰할 수 없는 네트워크 입력이다(클라/원격이 보냄).
// - 알려진(정의된) 필드는 인덱스 0..4뿐이고 5..15는 아직 미정의(예약)다.
// - 잘못된/악의적 입력에도 서버가 안전하게 거부해야 한다(예외로 죽지 않기).
//
// 과제: (A)(B) 지점을 검토하라. 어떤 입력에서 버퍼 밖을 읽거나 예약 비트를
// 잘못 처리하는지 밝히고, 안전하게 거부/파싱하도록 고쳐라.
using System;
public struct EntityState
{
public int Hp;
public float X, Y;
public ushort BuffMask;
public byte Level;
}
public sealed class StateReader
{
public const int Fields = 16;
// 정의된 필드는 0..4(크기 4,4,4,2,1), 5..15는 미정의(0)
private static readonly int[] FieldSize =
{ 4,4,4,2,1, 0,0,0,0,0,0,0,0,0,0,0 };
public bool Parse(byte[] buf, out EntityState outState)
{
outState = default;
int cur = 0;
ushort mask = (ushort)(buf[0] | (buf[1] << 8)); // 선두 2바이트 = 비트맵
cur += 2;
for (int i = 0; i < Fields; i++)
{
if ((mask & (1 << i)) != 0)
{
int sz = FieldSize[i]; // (B)
ApplyField(ref outState, i, buf, cur, sz);// (A)
cur += sz;
}
}
return true;
}
private static void ApplyField(ref EntityState s, int i, byte[] buf, int off, int sz)
{
switch (i)
{
case 0: s.Hp = BitConverter.ToInt32(buf, off); break;
case 1: s.X = BitConverter.ToSingle(buf, off); break;
case 2: s.Y = BitConverter.ToSingle(buf, off); break;
case 3: s.BuffMask = BitConverter.ToUInt16(buf, off); break;
case 4: s.Level = buf[off]; break;
default: break; // 미정의 필드
}
}
} 결함 코드 · C++
// 시나리오: 존 서버가 클라이언트로 엔티티 상태 변경(델타)을 보낼 때, 변한 필드만 골라
// 보내려고 "존재 비트맵(presence bitmap)"을 쓴다. 선두 2바이트(16비트) 각 비트가
// "해당 옵션 필드가 이 메시지에 포함됨"을 뜻하고, 켜진 비트 순서대로 각 필드의
// 고정 크기 값이 뒤따른다. 수신 측은 비트맵을 읽고 켜진 필드만 역직렬화한다.
//
// 요구사항:
// - buf[0..len) 는 신뢰할 수 없는 네트워크 입력이다(클라/원격이 보냄).
// - 알려진(정의된) 필드는 인덱스 0..4뿐이고 5..15는 아직 미정의(예약)다.
// - 잘못된/악의적 입력에도 서버가 메모리 안전을 유지하고 거부해야 한다.
//
// 과제: (A)(B) 지점을 검토하라. 어떤 입력에서 버퍼 밖을 읽거나 예약 비트를
// 잘못 처리하는지 밝히고, 안전하게 거부/파싱하도록 고쳐라.
#include <cstdint>
#include <cstring>
#include <cstddef>
struct EntityState {
int32_t hp = 0;
float x = 0, y = 0;
uint16_t buffMask = 0;
uint8_t level = 0;
};
class StateReader {
public:
static constexpr int FIELDS = 16;
// 정의된 필드는 0..4(크기 4,4,4,2,1), 5..15는 미정의(0)
static constexpr int FIELD_SIZE[FIELDS] =
{4,4,4,2,1, 0,0,0,0,0,0,0,0,0,0,0};
bool parse(const uint8_t* buf, size_t len, EntityState& out) {
const uint8_t* cur = buf;
const uint8_t* end = buf + len;
uint16_t mask = (uint16_t)(cur[0] | (cur[1] << 8)); // 선두 2바이트 = 비트맵
cur += 2;
for (int i = 0; i < FIELDS; i++) {
if (mask & (1u << i)) {
int sz = FIELD_SIZE[i]; // (B)
std::memcpy(fieldPtr(out, i), cur, (size_t)sz);// (A)
cur += sz;
}
}
(void)end;
return true;
}
private:
void* fieldPtr(EntityState& s, int i) {
switch (i) {
case 0: return &s.hp; case 1: return &s.x;
case 2: return &s.y; case 3: return &s.buffMask;
case 4: return &s.level; default: return nullptr;
}
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.