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++
// ============================================================================
// [코드리뷰 문제] C++ - 가변길이 문자열/바이트 필드 역직렬화와 길이 검증 (서버-클라)
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 / 서버-클라):
// - 클라이언트가 보내는 패킷 안에는 가변길이 문자열 필드가 들어있다(닉네임,
// 채팅 본문, 우편 제목 등). 인코딩은 [u16 len][len 바이트 UTF-8] 형식이고,
// 첨부 같은 큰 블롭은 [u32 len][len 바이트] 형식이다.
// - 우편 일괄 패킷은 [u16 count] 뒤에 count 개의 (제목 문자열) 이 이어진다.
// - 이 길이/개수 값은 모두 "클라이언트가 보낸 값" 이다(신뢰 불가, 공격자 제어 가능).
// - 서버는 수신 버퍼(고정 크기 바이트 배열)에서 이 구조를 역직렬화한다.
//
// 요구사항:
// - 신뢰할 수 없는 길이/개수로 버퍼 경계를 넘어 읽으면 안 된다(오버리드 금지).
// - 공격자가 부풀린 길이/개수로 거대한 메모리를 선할당하게 만들면 안 된다(DoS 금지).
// - 필드 길이/개수에는 프로토콜 상한이 있어야 하고, UTF-8 유효성도 보장돼야 한다.
// - 커서(pos) 산술이 언더플로/오버플로로 깨지면 안 된다(size_t 주의).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 오버리드·DoS·파싱붕괴가 나는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <string>
#include <vector>
class Reader {
public:
Reader(const uint8_t* data, size_t size) : data_(data), size_(size) {}
uint16_t readU16() {
// (A)
uint16_t v = static_cast<uint16_t>(data_[pos_] | (data_[pos_ + 1] << 8));
pos_ += 2;
return v;
}
uint32_t readU32() {
uint32_t v = static_cast<uint32_t>(data_[pos_]) |
(static_cast<uint32_t>(data_[pos_ + 1]) << 8) |
(static_cast<uint32_t>(data_[pos_ + 2]) << 16) |
(static_cast<uint32_t>(data_[pos_ + 3]) << 24);
pos_ += 4;
return v;
}
// [u16 len][len 바이트] 문자열
std::string readString() {
uint16_t len = readU16();
std::string s;
// (B)
s.reserve(len);
// (C)
for (uint16_t i = 0; i < len; ++i)
s.push_back(static_cast<char>(data_[pos_++]));
return s;
}
// [u32 len][len 바이트] 블롭(첨부)
std::vector<uint8_t> readBlob() {
uint32_t len = readU32();
// (B)
std::vector<uint8_t> buf(len);
for (uint32_t i = 0; i < len; ++i)
buf[i] = data_[pos_++]; // (C)
return buf;
}
size_t pos() const { return pos_; }
private:
const uint8_t* data_;
size_t size_;
size_t pos_ = 0;
};
// 우편 일괄: [u16 count] 그 뒤 count 개의 제목 문자열
std::vector<std::string> parseMailTitles(const uint8_t* buf, size_t n) {
Reader r(buf, n);
uint16_t count = r.readU16();
std::vector<std::string> titles;
// (B)
titles.reserve(count);
for (uint16_t i = 0; i < count; ++i)
titles.push_back(r.readString()); // (A)(C)
return titles;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.