46. 널 종료 문자열 필드 파싱의 프레임 경계 미준수 (오배열·프레임 오염, C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 로그인 패킷의 페이로드 파싱 (C# 게임서버)
// - 상위 수신 루프가 길이 프리픽스로 "이 논리 메시지"의 바이트 범위
// [offset, offset+frameLen) 를 이미 잘라서 넘겨준다.
// - 페이로드 레이아웃: [널 종료 문자열: 계정명]['\0']['uint16 클라이언트 버전']
// 즉 계정명은 C 스타일 널 종료 문자열이고, 그 뒤에 2바이트 버전이 온다.
// - 계정명은 신뢰할 수 없는 클라이언트 입력이며, 악성/손상 패킷은 널 종료
// 바이트를 생략하거나 프레임 경계까지 채워 보낼 수 있다.
//
// 과제: 아래 파서는 정상 패킷은 잘 처리하지만, 특정 입력에서 프레임 밖의
// 바이트를 읽거나 다음 필드를 계정명으로 오인해 파싱이 어긋난다.
// (A),(B) 를 근거로 문제와 수정안을 설명하라.
using System;
using System.Text;
public static class LoginParser
{
// buffer: 세션 수신 링버퍼(여러 프레임이 연속으로 쌓여 있음)
// offset..offset+frameLen: 이번 메시지의 범위
public static (string account, ushort version) Parse(byte[] buffer, int offset, int frameLen)
{
int nul = Array.IndexOf(buffer, (byte)0, offset); // (A)
int nameLen = nul - offset;
string account = Encoding.UTF8.GetString(buffer, offset, nameLen);
int p = nul + 1;
ushort version = (ushort)(buffer[p] | (buffer[p + 1] << 8)); // (B)
return (account, version);
}
} 결함 코드 · C++
// 시나리오: 로그인 패킷의 페이로드 파싱 (C++ 게임서버)
// - 상위 수신 루프가 길이 프리픽스로 이번 논리 메시지의 범위
// [data, data+frame_len) 를 잘라 넘겨준다.
// - 페이로드 레이아웃: [널 종료 문자열: 계정명]['\0']['uint16 클라 버전(LE)']
// - 계정명은 신뢰할 수 없는 입력. 악성 패킷은 널 종료 바이트를 생략할 수 있다.
//
// 과제: 아래 파서는 정상 입력엔 동작하지만, 널 종료가 없는 입력에서
// 인접 프레임/메모리의 바이트를 계정명으로 읽어 파싱이 어긋난다.
// (A),(B) 를 근거로 원인·수정안을 설명하라.
// (컴파일 가능해야 함)
#include <cstdint>
#include <cstring>
#include <string>
#include <cstdio>
struct LoginInfo { std::string account; uint16_t version; };
// data: 이번 프레임의 시작, frame_len: 프레임 길이(바이트)
LoginInfo Parse(const char* data, size_t frame_len) {
(void)frame_len;
size_t name_len = std::strlen(data); // (A)
LoginInfo info;
info.account.assign(data, name_len);
const unsigned char* p =
reinterpret_cast<const unsigned char*>(data + name_len + 1);
info.version = static_cast<uint16_t>(p[0] | (p[1] << 8)); // (B)
return info;
}
int main() {
// 정상: "abc\0" + version(0x0001)
const char buf[] = { 'a','b','c','\0', 0x01, 0x00 };
LoginInfo i = Parse(buf, sizeof(buf));
std::printf("acc=%s ver=%u\n", i.account.c_str(), i.version);
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.