25. TLV 옵션 리스트 역직렬화의 경계·길이 검증 부재
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 클라이언트가 캐릭터의 가변 "장식/외형 옵션"을 한 패킷에 담아 보낸다.
// payload 는 옵션 엔트리가 0개 이상 반복되는 구조다:
// [ type:1바이트 ][ len:2바이트 LE ][ value:len바이트 ] (반복)
// 서버는 payload 끝까지 엔트리를 순회하며 각 옵션을 파싱해 적용한다.
// len 과 개수는 모두 클라이언트(신뢰 불가)가 정한다.
//
// 요구사항:
// - 잘리거나 조작된 payload(잘못된 len, 끝에서 헤더가 부족한 경우 등)가 와도
// 서버는 안전하게 거부할 뿐, 버퍼 밖을 읽거나 비정상 종료해서는 안 된다.
// - 이 파서는 수많은 세션의 수신 패킷을 처리하는 공용 경로에서 호출된다.
//
// 과제: 신뢰 불가 입력에 대해 (A)~(C) 지점에서 무슨 일이 벌어질 수 있는지
// 진단하고, 안전하게 파싱하도록 수정하라.
using System;
using System.Collections.Generic;
public sealed class OptionParser
{
public List<(byte Type, byte[] Value)> Parse(byte[] buf)
{
var result = new List<(byte, byte[])>();
int pos = 0;
while (pos < buf.Length) // 남은 엔트리 순회
{
byte type = buf[pos];
int len = buf[pos + 1] | (buf[pos + 2] << 8); // (A) 길이 헤더 읽기
pos += 3;
var value = new byte[len]; // (C) 선언된 len 으로 버퍼 확보
Array.Copy(buf, pos, value, 0, len); // (B) len 바이트 복사
pos += len;
result.Add((type, value));
}
return result;
}
} 결함 코드 · C++
// 시나리오: 클라이언트가 캐릭터의 가변 "장식/외형 옵션"을 한 패킷에 담아 보낸다.
// payload 는 옵션 엔트리가 0개 이상 반복되는 구조다. 이 변형에서 각 엔트리의
// 길이 필드는 "엔트리 전체 크기(헤더 3바이트 포함)"를 뜻한다:
// [ entryLen:2바이트 LE ][ type:1바이트 ][ value:(entryLen-3)바이트 ] (반복)
// 서버는 payload 끝까지 엔트리를 순회하며 각 옵션을 파싱한다.
// entryLen, type, 개수는 모두 클라이언트(신뢰 불가)가 정한다.
//
// 요구사항:
// - 잘리거나 조작된 payload(잘못된 entryLen, 부족한 잔여 바이트 등)가 와도
// 서버는 안전하게 거부할 뿐, 버퍼 밖을 읽거나 멈춰서는 안 된다.
// - 이 파서는 수많은 세션의 수신 패킷을 처리하는 공용 경로에서 호출된다.
//
// 과제: 신뢰 불가 입력에 대해 (A)~(C) 지점에서 무슨 일이 벌어질 수 있는지
// 진단하고, 안전하게 파싱하도록 수정하라.
#include <cstdint>
#include <cstddef>
#include <vector>
struct Option { uint8_t type; std::vector<uint8_t> value; };
class OptionParser {
public:
std::vector<Option> parse(const uint8_t* buf, size_t n) {
std::vector<Option> out;
size_t pos = 0;
while (pos < n) { // 남은 엔트리 순회
uint16_t entryLen =
static_cast<uint16_t>(buf[pos] | (buf[pos + 1] << 8)); // (A) 길이 헤더
uint8_t type = buf[pos + 2];
size_t valLen = entryLen - 3; // (B) value 길이
Option o;
o.type = type;
o.value.assign(buf + pos + 3, buf + pos + 3 + valLen); // (C) value 복사
out.push_back(std::move(o));
pos += entryLen; // (A) 다음 엔트리로 전진
}
return out;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.