23. 세션 객체 풀 재사용 시 상태 누수 (권한 승계 · 신원 혼동 · 패킷 오배달)
난이도 상해설 — 세션 객체 풀 재사용 시 상태 누수 (권한 승계 · 신원 혼동 · 패킷 오배달)
난이도: 상
요약
Init(A)은 Socket 과 RecvLen 만 초기화하고 나머지 필드(Authenticated, PlayerId, SendQueue, RecvBuf 내용)는 이전 사용분을 그대로 둔다. 인증된 플레이어 P 의 세션이 풀로 돌아간 뒤 새 연결 Q 가 그 세션을 Rent 하면, Q 는 Authenticated=true, PlayerId=P 로 시작한다 — 인증을 안 거치고 P 의 권한·신원으로 동작(권한 승계/신원 혼동, 심각한 보안 결함). 또 P 의 SendQueue 잔여 패킷이 Q 에게 전송되고(타 플레이어 데이터 누출), RecvBuf 의 옛 바이트가 Q 의 프레임 파싱에 섞인다. 게다가 Return(C)이 in-flight 비동기 I/O 를 기다리지 않아, 늦게 도착한 완료 콜백이 이미 재사용된 세션을 오염시킨다.
문제점
1. (상태 누수 / 보안) 불완전 초기화 — (A)
- 증상: Q 가 인증 절차 없이 P 의
PlayerId·Authenticated를 물려받는다. - 재현조건: P 인증 → 종료 → 세션 풀 반납 → Q 접속이 같은 세션 Rent. 핸들러가
if (Authenticated) ...로 분기하면 Q 는 그대로 통과. - 근본원인: "민감 상태를 포함한 모든 필드"를 재사용 경계에서 초기화하지 않음. 화이트리스트(초기화할 것)가 아니라 블랙리스트(몇 개만)로 리셋.
2. (데이터 누출) SendQueue 미정리 — (A)
- P 에게 보내려다 큐에 남은 패킷이 Q 소켓으로 나간다(다른 플레이어의 정보가 Q 에게).
3. (프레임 디싱크) RecvBuf 잔여
RecvLen=0으로만 리셋하니 길이는 0이지만, 만약 파서가 버퍼 용량/이전 내용을 가정하면 옛 바이트가 새 프레임에 섞일 수 있다(특히 부분 수신 누적 로직과 결합 시).
4. (use-after-return) in-flight 비동기와 경합 — (C)
Return직후 풀에서 즉시 재대여될 수 있는데, P 의ReceiveAsync/SendAsync완료 콜백이 아직 실행 중이면 그 콜백이 Q 가 쓰는 세션의 버퍼/큐를 건드린다(교차 오염, UAF 성격).
수정안
재사용 경계에서 모든 상태를 완전 초기화하고, 모든 in-flight I/O 가 끝난 뒤에만 반납한다. 초기화는 "지울 것 목록"이 아니라 "완전 리셋"으로.
public void ResetForReuse() // 반납 시(또는 대여 시) 전체 초기화
{
Socket = null;
Authenticated = false;
PlayerId = 0;
RecvLen = 0;
Array.Clear(RecvBuf, 0, RecvBuf.Length); // 민감 바이트 잔류 제거
SendQueue.Clear();
}
public void Return(Session ses)
{
// in-flight 비동기가 모두 끝났는지 보장(예: 미완료 작업 카운터가 0일 때만 반납)
if (Interlocked.Read(ref ses.PendingOps) != 0) { ses.ReturnWhenDrained(); return; }
ses.ResetForReuse(); // 완전 초기화 후 반납
_pool.Enqueue(ses);
}
- 더 견고하게는 새 연결에 새 논리 세션 ID(generation/epoch) 를 부여하고, 비동기 콜백이 자신이 시작될 때의 epoch 와 현재 epoch 가 같을 때만 세션을 만지게 한다(늦은 콜백 무력화 — 이 저장소 session/problem21 의 재바인딩 방어와 동형).
- 인증/식별 같은 보안 상태는 풀 재사용으로 절대 새지 않도록, 가능하면 세션 객체와 분리된 연결별 인증 컨텍스트로 둔다.
더 나은 설계
- 풀링 리셋은 화이트리스트(모든 필드를 명시적으로 초기화) 로. 필드를 추가했는데 리셋을 빠뜨리는 회귀를 막으려면 리셋 단위 테스트 + "대여 직후 상태는 기본값" 불변식 검증.
- drain-then-return: 미완료 비동기 작업 카운터가 0이 될 때까지 반납 보류. epoch 토큰으로 늦은 콜백을 무시.
- 민감 데이터(인증/PII)는 풀 재사용 객체에 오래 남기지 말고 명시적 클리어.
면접 포인트
- 오브젝트 풀의 핵심 위험은 상태 누수 — 재사용 경계에서 "전부 초기화"가 불변식. 특히 인증/식별 같은 보안 상태의 승계는 치명적.
- 비동기 I/O 가 남은 채 객체를 반납하면 use-after-return 으로 다음 사용자와 교차 오염 → drain + epoch 토큰.
- 리셋은 블랙리스트가 아니라 화이트리스트로, 필드 추가 시 회귀를 막는 테스트와 함께.
해설 — 세션 객체 풀 재사용 시 상태 누수 (권한 승계 · 패킷 오배달 · UAF)
난이도: 상
요약
init(A)은 fd 와 recvLen 만 초기화하고 authenticated, playerId, sendQueue, recvBuf 내용은 이전 사용분을 그대로 둔다. 인증된 플레이어 P 의 세션이 풀(free_)로 돌아간 뒤 새 연결 Q 가 rent 하면 Q 는 authenticated=true, playerId=P 로 시작 — 인증 없이 P 의 권한/신원으로 동작(권한 승계). P 의 sendQueue 잔여 패킷이 Q 로 나가고(타 플레이어 데이터 누출), 옛 recvBuf 바이트가 Q 의 파싱에 섞인다. 또 release(C)가 진행 중인 비동기 I/O(완료 핸들러)를 기다리지 않아, 늦은 콜백이 재사용된 세션을 만져 use-after-free 성 손상이 난다.
문제점
1. (상태 누수 / 보안) 불완전 초기화 — (A)
- Q 가 P 의
authenticated/playerId를 물려받아 인증을 우회. 리셋이 일부 필드만(블랙리스트) 다룬다.
2. (데이터 누출) sendQueue 미정리 — (A)
- P 큐에 남은 패킷이 Q 의 소켓으로 전송된다.
3. (프레임 디싱크) recvBuf 잔여
recvLen=0만으론 옛 바이트가 버퍼에 남아, 누적 수신/부분 프레임 로직과 결합하면 새 프레임에 섞일 수 있다.
4. (use-after-free) in-flight 비동기와 경합 — (C)
- IOCP/epoll 완료 핸들러가 아직 이
Session*을 참조하는데release로free_에 넣으면, 즉시 다른 연결이 같은 포인터를rent한다. 늦은 핸들러가 Q 의 세션 버퍼/큐를 덮어쓴다(교차 오염, UAF).
수정안
재사용 경계에서 전체 초기화하고, 모든 in-flight 작업이 끝난 뒤에만 반납한다.
void Session::resetForReuse() {
fd = -1;
authenticated = false;
playerId = 0;
recvLen = 0;
std::fill(std::begin(recvBuf), std::end(recvBuf), uint8_t{0}); // 민감 바이트 제거
sendQueue.clear();
}
void SessionPool::release(Session* s) {
// 미완료 비동기 작업 카운터가 0일 때만 실제 반납(아니면 drain 후 반납 예약)
if (s->pendingOps.load(std::memory_order_acquire) != 0) {
s->returnWhenDrained();
return;
}
s->resetForReuse();
std::lock_guard<std::mutex> lk(mtx_);
free_.push_back(s);
}
- 늦은 콜백 방어로 세대(generation/epoch) 토큰을 둔다:
rent마다++s->generation, 비동기 작업 시작 시 generation 을 캡처해 완료 시 현재 generation 과 같을 때만 세션을 만진다(이 저장소 session/problem21 의 재바인딩 방어와 동형). - 풀 객체를 직접 노출하기보다
rent가 소유권 핸들(RAII) 을 돌려주고, 핸들 소멸 시 drain+reset+반납을 강제하면 누수/조기 반납을 구조적으로 막는다.
더 나은 설계
- 리셋은 화이트리스트(모든 필드 초기화) + 리셋 단위 테스트(필드 추가 회귀 방지).
- drain-then-return + epoch 토큰으로 in-flight 콜백의 늦은 접근을 무력화.
- 보안 상태(인증/식별)는 풀 재사용 객체에 길게 남기지 않거나, 연결별 별도 컨텍스트로 분리.
면접 포인트
- 오브젝트 풀의 핵심 위험은 상태 누수 — 재사용 경계에서 모든 필드 초기화가 불변식, 특히 인증/식별 승계는 치명적.
- C++ 비동기 서버에서 풀 객체를 in-flight 작업이 남은 채 반납하면 UAF/교차 오염 → drain 카운터 + generation 토큰 + RAII 핸들.
- recvBuf 같은 버퍼는 길이만 0으로 두지 말고 내용도 정리(민감 데이터/디싱크 방지).