← 문제로

43. 인증 완료 전 도착 패킷의 미인증 처리와 상태 게이트 부재

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

해설 — 인증 완료 전 도착 패킷의 미인증 처리와 상태 게이트 부재

난이도: 중상

요약

OnReceive 의 default 분기 (B) 는 세션 상태를 확인하지 않고 게임 명령을 즉시 처리한다. 클라가 접속 직후 인증 응답을 기다리지 않고 MOVE/USEITEM 을 보내면, 인증이 끝나기 전(AccountId==0) 그 명령이 실행된다. 게다가 (A) 의 인증 완료 처리는 비동기 콜백에서 신원과 상태를 순서 없이 채워, "상태만 먼저 보이고 신원은 아직" 인 창을 만든다.

문제점

  • [상태 게이트 부재] 미인증 명령 실행: (B) 는 session.State 를 검사하지 않는다. 접속 직후 보낸 게임 패킷이 State == Connecting/Authenticating 인데도 _handler.Handle 로 들어간다. 핸들러는 session.AccountId(기본 0)를 사용하므로, 존재하지 않는/잘못된 계정에 이동·아이템 사용이 적용되거나, 0번 계정 데이터를 건드려 오염·예외가 난다. 인증 우회(권한 상승) 통로가 된다.
  • [발행 순서·가시성] 신원과 상태의 비원자 게시: (A) 는 별도 스레드(ContinueWith 콜백)에서 AccountId 를 먼저 쓰고 State 를 나중에 쓴다. 수신 스레드가 State == Authenticated 를 본 시점에 AccountId 쓰기가 반드시 보인다는 보장이 없다(메모리 가시성/재배열). 상태 게이트를 추가하더라도 이 발행 순서를 동기화하지 않으면 신원 없이 인증됨으로 오인할 수 있다.
  • [입력 유실 vs 실행 사이의 선택]: 올바른 설계는 "미인증 중 도착한 명령"을 (버려서 입력을 잃거나) 임의 실행하는 대신, 인증 완료까지 보류(큐잉) 하거나 명시적으로 거부/절단해야 한다.
  • 재현 조건: 지연을 숨기려 접속 직후 낙관적으로 명령을 보내는 정상 클라, 또는 인증을 건너뛰려는 악성 클라. 외부 인증 지연이 클수록 창이 커진다.

수정안

게임 명령은 인증 완료를 전제로만 처리한다. 미인증 중 도착분은 세션별로 순서를 지켜 보류했다가 인증 완료 후 순서대로 처리하고, 신원·상태 전이는 원자적으로(같은 실행 컨텍스트에서) 게시한다.

public void OnReceive(Session session, Packet msg)
{
    if (msg.Type == MsgType.Auth)
    {
        if (session.State != SessionState.Connecting) return;   // 중복 AUTH 방지
        session.State = SessionState.Authenticating;
        _auth.ValidateAsync(msg.Token).ContinueWith(t =>
        {
            EnqueueToSessionActor(session, () =>                 // 세션 단위 직렬 실행
            {
                if (t.IsFaulted || t.Result == null) { Close(session); return; }
                session.AccountId = t.Result.AccountId;
                session.State = SessionState.Authenticated;      // 신원 먼저, 그 다음 상태 — 같은 컨텍스트
                foreach (var pending in session.Pending) _handler.Handle(session, pending);
                session.Pending.Clear();
            });
        });
        return;
    }

    // 게임 명령: 인증 전이면 보류, 인증 후에만 처리 (모두 세션 액터에서 직렬 처리)
    EnqueueToSessionActor(session, () =>
    {
        if (session.State != SessionState.Authenticated) { session.Pending.Add(msg); return; }
        _handler.Handle(session, msg);
    });
}

더 나은 설계

  • 세션 단위 액터/직렬 큐: 한 세션의 모든 이벤트(수신·인증 완료)를 단일 순서로 처리하면 상태 전이와 명령 처리 사이의 경합·가시성 문제가 원천적으로 사라진다(문제 22 의 수신 직렬화와 연결).
  • 보류 큐 상한: 미인증 보류 큐에 개수/바이트 상한과 핸드셰이크 타임아웃을 둬 slowloris 류 자원 고갈을 막는다(문제 24 와 연결).
  • 명시적 상태 머신: 허용 메시지 집합을 상태별로 정의(Connecting 에선 AUTH 만 허용)해, 상태 위반 패킷은 거부·절단한다.
  • 트레이드오프: 보류는 정상 클라의 낙관적 입력을 살리지만 메모리·지연을 늘린다. 즉시 거부는 단순하지만 접속 직후 입력을 잃는다 — 게임 특성에 맞게 선택한다.

면접 포인트

  • 상태 머신에서 "허용 메시지" 를 상태별로 강제하지 않으면 인증 우회가 생긴다는 점.
  • 비동기 완료 콜백에서 여러 필드를 게시할 때의 순서·가시성 문제와, 세션 직렬화로 해결하는 방법.
  • 미인증 입력을 보류할지 거부할지의 트레이드오프(입력 유실 vs 자원/지연).