48. 프래그먼트 재조립의 offset/length 경계·겹침·구멍 미검증 · C#
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: UDP 기반 신뢰성 전송의 대용량 메시지 프래그먼트 재조립 (C#)
//
// 큰 메시지(예: 인벤토리 스냅샷, 맵 데이터)는 여러 UDP 프래그먼트로 쪼개져 전송된다.
// 각 프래그먼트 헤더: msgId, totalSize(전체 바이트), offset(이 조각의 시작 위치),
// payload(이 조각의 데이터). moreFlag 대신 조각 payload 길이로 판단.
// 수신 측은 msgId 별로 재조립 버퍼를 만들고 offset 위치에 payload를 채워 넣는다.
// 모든 바이트가 채워지면(received == totalSize) 완성으로 보고 메시지를 디스패치한다.
//
// 클라이언트는 신뢰할 수 없다(변조된/악의적 프래그먼트가 올 수 있다).
//
// 과제:
// 1) (A)(B) 지점을 중심으로, 조작된 프래그먼트가 서버에 어떤 영향을 주는지 설명하라.
// 2) 구체적 공격/오작동 시나리오를 최소 두 가지 제시하라.
// 3) 견고한 재조립을 위한 수정안을 제시하라.
using System;
using System.Collections.Generic;
public struct Fragment
{
public int MsgId;
public int TotalSize;
public int Offset;
public byte[] Payload;
}
public class Reassembler
{
private class Pending
{
public byte[] Buffer = Array.Empty<byte>();
public int Received;
}
private readonly Dictionary<int, Pending> _pending = new Dictionary<int, Pending>();
// 완성된 메시지를 반환하거나, 아직 미완성이면 null.
public byte[] OnFragment(in Fragment f)
{
if (!_pending.TryGetValue(f.MsgId, out var p))
{
p = new Pending();
// (A) 첫 조각이 알려준 totalSize 만큼 버퍼 확보
p.Buffer = new byte[f.TotalSize];
_pending[f.MsgId] = p;
}
// (B) 이 조각을 offset 위치에 채운다
Array.Copy(f.Payload, 0, p.Buffer, f.Offset, f.Payload.Length);
p.Received += f.Payload.Length;
if (p.Received >= p.Buffer.Length)
{
_pending.Remove(f.MsgId);
return p.Buffer; // 완성
}
return null;
}
} 결함 코드 · C++
// 시나리오: UDP 기반 신뢰성 전송의 대용량 메시지 프래그먼트 재조립 (C++ 게임서버)
//
// 큰 메시지는 여러 UDP 프래그먼트로 쪼개져 온다.
// 프래그먼트 헤더: msgId, totalSize, offset, 그리고 payload(이 조각의 바이트열).
// 수신 측은 msgId 별 재조립 버퍼를 만들고 offset 위치에 payload를 memcpy 한다.
// 채워진 바이트 수가 totalSize 에 도달하면 완성으로 보고 디스패치한다.
//
// 클라이언트는 신뢰할 수 없다(조작된 프래그먼트 가능).
//
// 과제:
// 1) (A)(B) 지점을 중심으로 조작 프래그먼트가 어떤 영향을 주는지 설명하라.
// 2) C++ 에서 특히 위험한 결과(메모리 안전)를 구체적으로 논하라.
// 3) 수정안을 제시하라. g++ -std=c++17 로 컴파일된다고 가정한다.
#include <cstdint>
#include <cstring>
#include <vector>
#include <unordered_map>
struct Fragment {
uint32_t msgId;
uint32_t totalSize;
uint32_t offset;
std::vector<uint8_t> payload;
};
class Reassembler {
public:
// 완성 시 true 를 돌려주고 out 에 메시지를 채운다.
bool onFragment(const Fragment& f, std::vector<uint8_t>& out) {
Pending& p = pending_[f.msgId];
if (p.buffer.empty()) {
// (A) 첫 조각이 알려준 totalSize 만큼 버퍼 확보
p.buffer.resize(f.totalSize);
}
// (B) offset 위치에 payload 복사
std::memcpy(p.buffer.data() + f.offset, f.payload.data(), f.payload.size());
p.received += (uint32_t)f.payload.size();
if (p.received >= p.buffer.size()) {
out = std::move(p.buffer);
pending_.erase(f.msgId);
return true;
}
return false;
}
private:
struct Pending {
std::vector<uint8_t> buffer;
uint32_t received = 0;
};
std::unordered_map<uint32_t, Pending> pending_;
};
int main() { return 0; } 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.