코드는 들어가지만 디버거 권한은 따로 없다: Python 3.14 원격 실행의 세 경계

1. sys.remote_exec()는 원격 셸이 아니다

Python 3.14에는 실행 중인 다른 CPython 프로세스에 파이썬 파일을 보내 실행시키는 sys.remote_exec(pid, script)가 들어갔다. 멈춘 서비스에 디버거를 붙인 뒤 진단 코드를 실행하거나, 프로파일러가 대상 프로세스 안에서 필요한 상태를 수집하는 용도다. 이름만 보면 네트워크 너머로 명령을 보내는 API처럼 들리지만 실제 경계는 훨씬 좁다. 호출자는 대상 프로세스의 PID와 로컬 .py 파일 경로를 넘기며, 대상 메모리에 접근할 수 있는 운영체제 권한이 이미 있어야 한다.

Python 3.14.7 공식 문서는 Linux의 ptrace·Yama, macOS의 task_for_pid, Windows의 SeDebugPrivilege를 예로 든다. 즉 Python이 새로운 인증 계층을 제공하는 것이 아니다. GDB나 LLDB가 요구하는 권한을 그대로 빌린다. 같은 UID라는 이유만으로 항상 붙을 수 있는 것도 아니다. Linux 배포판의 Yama 설정, 컨테이너 capability, 이미 붙어 있는 tracer, setuid·setgid 상태가 접근을 막을 수 있다.

호출 프로세스가 운영체제 디버거 권한을 거쳐 대상 CPython에 스크립트 경로를 전달하고 완료 신호를 기다리는 흐름

그림 1. Python 원격 실행은 별도 네트워크 권한이 아니라 운영체제 디버거 권한을 이용하고, 대상의 관찰 가능한 완료 효과를 따로 기다린다. 출처: docs.python.org/3/howto/remote_debugging.html.

2. 실행 요청은 메모리에 들어가고 코드는 파일에서 읽힌다

저수준 프로토콜은 대상의 PyRuntime과 debug offsets를 찾고, 대상 thread state 안의 스크립트 경로 버퍼와 pending flag를 쓴다. 이어 eval_breaker에 정지 요청 비트를 세운다. 대상 인터프리터는 다음 안전한 평가 지점에서 이 요청을 처리하고 디스크의 스크립트를 읽어 실행한다. 임의 시점에 C 구조체를 건드리며 곧바로 코드를 실행하는 방식이 아니라, 인터프리터가 일관된 상태를 확인하는 지점으로 실행을 넘기는 구조다.

여기에는 운영상 중요한 비동기 경계가 있다. sys.remote_exec()는 요청을 넣은 뒤 즉시 반환하고, 대상이 언제 파일을 읽었는지 알려 주는 완료 API가 없다. 호출자가 임시 파일을 바로 지우거나 같은 경로를 덮어쓰면 대상은 파일을 못 읽거나 다른 내용을 실행할 수 있다. 공식 문서는 대상이 소켓으로 되돌아오거나 결과 파일을 쓰는 등 관찰 가능한 효과가 나타날 때까지 스크립트를 보존하라고 설명한다.

호출 프로세스와 대상 프로세스는 CPython 주·부 버전이 같아야 한다. 둘 중 하나가 알파·베타·RC라면 정확한 버전까지 같아야 한다. free-threaded 빌드와 일반 빌드도 debug offsets가 달라 서로 붙지 못하도록 구현돼 있다. "Python이면 붙는다"가 아니라 ABI와 실행 모드가 맞는 도구 쌍을 운영해야 한다.

원격 실행의 권한 경계, CPython 호환성 경계, 대상 실행 증거 경계를 나란히 비교한 표

그림 2. attach 허가, CPython 버전과 빌드 호환성, 대상의 실제 실행 증거는 서로 다른 검증 단계다. 출처: docs.python.org/3/library/sys.html#sys.remote_exec.

3. 감사 이벤트는 두 프로세스에서 따로 발생한다

공식 sys 문서는 두 감사 이벤트를 구분한다. 호출 측에서는 sys.remote_exec가 PID와 스크립트 경로를 담아 발생한다. 대상이 실제 파일을 실행할 때는 cpython.remote_debugger_script가 대상 프로세스 안에서 발생한다. 요청 사실과 실제 실행 사실을 한 로그로 뭉개면 안 되는 이유다. 호출 이벤트만 남고 대상 이벤트가 없다면 아직 실행되지 않았거나 대상이 거부했을 수 있다.

CPython 3.14.0의 ceval_gil.c는 pending flag를 내린 직후 경로 버퍼를 별도 메모리로 복사한다. 다른 디버거가 동시에 버퍼를 바꾸더라도 감사한 경로와 실행하려는 경로가 서로 어긋나지 않게 하려는 방어다. 하지만 그 뒤 디스크 파일 바이트가 바뀌는 문제까지 해결하지는 않는다. 경로 일관성과 파일 무결성은 별개다.

감사 훅은 기록과 차단에 쓸 수 있지만 그 자체가 샌드박스는 아니다. Python 문서도 sys.addaudithook()로 추가한 파이썬 훅은 악성 코드가 우회할 수 있다고 경고한다. 보안 판단에 쓰려면 런타임 초기화 전에 C API로 훅을 설치하고, 임의 메모리 변경을 허용하는 모듈까지 통제해야 한다. 운영체제의 디버거 권한, 대상 프로세스의 감사 정책, 스크립트 파일의 무결성을 별도 층으로 다뤄야 한다.

4. 비활성화 스위치가 막는 범위를 정확히 보자

원격 디버깅이 필요 없는 서버는 세 단계에서 기능을 끌 수 있다. 실행 환경에서는 PYTHON_DISABLE_REMOTE_DEBUG=1, 프로세스 옵션에서는 -X disable_remote_debug, 빌드에서는 --without-remote-debug를 쓴다. 3.14.7 문서는 이를 공격 표면을 줄이는 심층 방어 수단으로 설명한다.

다만 이 스위치가 ptrace, /proc, task_for_pid 같은 운영체제 디버깅 인터페이스까지 막는 것은 아니다. 원격 실행 기능을 껐다는 사실을 "프로세스 메모리가 디버거로부터 보호됐다"고 표현하면 틀린다. 컨테이너의 SYS_PTRACE 부여, 호스트의 Yama 정책, 관리자·root 권한, 코어 덤프 정책은 여전히 따로 점검해야 한다.

진단 필요성, 권한 최소화, 완료와 감사 연결 여부를 확인한 뒤 원격 디버깅을 끌지 결정하는 흐름

그림 3. 운영에서는 진단 필요성보다 권한 범위와 완료 증거를 먼저 정하고, 불필요하면 원격 디버깅을 비활성화한다. 출처: docs.python.org/3/howto/remote_debugging.html#when-to-use-python-disable-remote-debug.

5. 운영 도구는 요청 ID와 파일 해시를 남겨야 한다

실전 래퍼라면 PID와 경로만 기록해서는 부족하다. 실행 전 스크립트 SHA-256과 대상 프로세스 시작 시각을 묶어 PID 재사용을 막고, 호출 이벤트와 대상 이벤트를 같은 요청 ID로 연결하는 편이 낫다. 스크립트는 다른 사용자가 바꾸지 못하는 디렉터리에 만들고, 대상의 완료 신호를 확인한 뒤에만 지운다. 결과가 불명확하면 같은 진단 코드를 즉시 다시 넣지 말고 대상 측 효과와 감사 로그부터 대조해야 한다.

컨테이너에서 진단 편의를 이유로 --privileged를 기본값으로 쓰는 것도 과하다. 필요한 경우에만 SYS_PTRACE를 좁게 부여하고, 운영 이미지에는 원격 디버깅을 끈 별도 프로필을 두는 쪽이 경계를 설명하기 쉽다.

도구의 성공 상태도 둘로 나누는 편이 안전하다. 첫 상태는 "요청 전달 완료"다. 대상 메모리에 경로와 pending flag를 썼다는 뜻일 뿐이다. 두 번째 상태는 "대상 실행 확인"이다. 대상 감사 이벤트와 결과 신호가 함께 있어야 한다. 대상이 바쁘거나 안전 평가 지점에 도달하지 못하면 두 상태 사이가 길어질 수 있다. 이때 타임아웃을 곧바로 실패로 바꾸고 같은 스크립트를 재주입하면 진단 코드가 두 번 실행될 가능성이 생긴다. 진단 스크립트도 요청 ID를 읽고 한 번만 결과를 쓰는 멱등 구조로 만드는 이유다.

파일 경로도 단순한 전달 문자열이 아니다. 대상 프로세스가 그 경로를 읽을 수 있어야 하고, 호출 뒤 실행 전까지 바이트가 바뀌지 않아야 한다. 공유 임시 디렉터리의 예측 가능한 이름 대신 전용 디렉터리와 고유 파일명을 쓰고, 생성 직후 계산한 해시를 완료 기록과 다시 맞춰야 한다. 스크립트에는 비밀번호나 토큰을 직접 넣지 말고 대상이 가진 안전한 설정 경로를 통해 필요한 값을 읽게 하는 편이 낫다.

Python 3.14의 새 기능은 디버거가 하던 일을 CPython 친화적인 안전 지점으로 가져왔다. 권한 분리나 완료 추적까지 대신 만들어 준 것은 아니다.

이번 글은 Python 3.14.7 공식 문서, PEP 768, CPython 3.14.0 고정 소스를 대조했다. 실제 운영 서버에 attach하거나 권한 설정을 완화하는 실험은 하지 않았다. 문서와 구현의 구조를 검토한 결과이지, 특정 운영체제 배포판의 권한 정책이나 보안 제품 설정을 재현한 결과도 아니다. 배포 전에는 사용하는 운영체제와 컨테이너 런타임의 실제 디버거 정책을 별도로 확인해야 한다. 운영 기록에는 원격 실행 요청, 운영체제 권한, 대상 내부 실행 증거를 각각 남겨야 한다.

참고 자료

댓글

이 블로그의 인기 게시물

체크아웃은 분리됐는데 Git 상태는 공유된다: Codex 0.154 worktree의 세 경계

잠근 작업이 커밋 뒤 다시 나온다: PostgreSQL 에이전트 큐의 소유권 설계

코딩 에이전트는 API 문서보다 AGENTS.md를 먼저 읽었다