← 문제로

23. 세션 객체 풀 재사용 시 상태 누수 (권한 승계 · 신원 혼동 · 패킷 오배달)

난이도 상
내 리뷰 · C#
해설 · C#

해설 — 세션 객체 풀 재사용 시 상태 누수 (권한 승계 · 신원 혼동 · 패킷 오배달)

난이도: 상

요약

Init(A)은 SocketRecvLen 만 초기화하고 나머지 필드(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)는 풀 재사용 객체에 오래 남기지 말고 명시적 클리어.

면접 포인트

  1. 오브젝트 풀의 핵심 위험은 상태 누수 — 재사용 경계에서 "전부 초기화"가 불변식. 특히 인증/식별 같은 보안 상태의 승계는 치명적.
  2. 비동기 I/O 가 남은 채 객체를 반납하면 use-after-return 으로 다음 사용자와 교차 오염 → drain + epoch 토큰.
  3. 리셋은 블랙리스트가 아니라 화이트리스트로, 필드 추가 시 회귀를 막는 테스트와 함께.