내 최소 메모리 안전 Go rsync가 취약점을 피하는 방법

3 hours ago 4
  • gokrazy/rsync는 Go로 작성한 최소 rsync 구현으로, 2025년 1월·2026년 5월 공개된 rsync 취약점 12개를 기준으로 검토됨
  • Go의 경계 검사와 0 초기화는 힙 오버플로·스택 정보 유출을 panic 또는 무해한 값으로 바꾸지만, panic은 여전히 서비스 거부가 될 수 있음
  • 경로 순회와 TOCTOU 계열은 Go 1.24의 os.Root 같은 순회 저항 파일 API가 핵심 방어가 되며, gokrazy/rsync는 전면 전환됨
  • 최소 구현 전략은 --inc-recursive, 압축, --safe-links, 프록시, 호스트명 ACL 등 미구현 기능 관련 취약점을 피하게 해 공격 표면을 줄임
  • 취약점 대부분은 검증 누락과 과도한 복잡성에서 나왔고, 단순한 사용 사례에는 단순한 구현이 더 적절할 수 있음

배경과 범위

  • 2025년 1월 여러 보안 연구자가 upstream Samba rsync에서 총 6개 보안 취약점을 공개했고, 일부는 임의 코드 실행과 파일 유출을 허용함
  • Go로 작성한 호환·최소 구현인 gokrazy/rsync가 이런 취약점 계열을 실제로 피할 수 있는지가 핵심 검토 대상임
  • 분석 대상은 2025년 1월 배치와 2026년 5월 배치를 합친 12개 취약점임
  • upstream rsync를 프로덕션에서 실행 중이면 3.4.3 이상으로 업그레이드해야 하며, gokrazy/rsyncv0.3.3 이상으로 올려야 함
  • gokrazy/rsync는 Linux 배포판 연구 프로젝트 distri의 소프트웨어 패키지를 router7에서 제공하려고 작성됐고, router7은 Go 어플라이언스 플랫폼 gokrazy 기반임
  • 초기에는 rsync 서버만 있었지만 현재는 모든 방향을 지원하며, Go 프로그램에 링크할 수 있는 rsync 기본 기능으로 여러 gokrazy/rsync 서버에서 사용 중임

2025년 1월 취약점

  • CVE-2024-12084: 힙 버퍼 오버플로, 심각도 9.8

    • rsync는 네트워크에서 받은 체크섬 길이를 MAX_DIGEST_LEN과 비교했지만, 내부 자료구조는 항상 16바이트 버퍼 char sum2[SUM_LENGTH]를 사용함
    • MAX_DIGEST_LEN은 SHA256 또는 SHA512 체크섬 지원으로 컴파일되면 더 커질 수 있어, 공격자가 sum2 버퍼 한계를 최대 48바이트 넘겨 쓸 수 있었음
    • 문제는 SHA256/SHA512 체크섬 지원을 추가한 2022년 9월 commit ae16850에서 도입됨
    • upstream 수정은 sum2를 동적 할당되는 sum2_array로 바꾸고, 전송 알고리듬의 체크섬 길이인 xfer_sum_len으로 할당·검사하도록 고침
    • Go에서는 잘못된 경계 검사가 힙 버퍼 오버플로로 이어지지 않고 런타임 경계 검사로 panic이 발생함
    • gokrazy/rsync에도 sum header 검증 누락이 있었지만 크기 혼동은 아니었고, ChecksumLength를 512로 바꾸면 Go 런타임이 panic: runtime error: slice bounds out of range [:512] with length 16을 발생시킴
    • 서버 전체 크래시는 바람직한 실패 모드가 아니므로, 누락된 경계 검사를 추가해 panic을 error로 바꿈
  • CVE-2024-12085: 스택 정보 유출로 ASLR 우회, 심각도 7.5

    • CVE-2024-12084와 같은 검증 누락 때문에 공격자가 짧은 체크섬 알고리듬을 선택한 뒤 더 긴 체크섬을 보냈다고 주장할 수 있었음
    • Google Security report에 따르면 힙 버퍼 오버플로와 정보 유출이 결합되면 익명 읽기 접근만 가진 클라이언트가 rsync 서버 머신에서 임의 코드를 실행할 수 있음
    • hash_search()는 스택의 char sum2[MAX_DIGEST_LEN]에 청크 digest를 만들고 memcmp()로 비교했지만, 로컬 sum2 스택 버퍼가 초기화되지 않아 남은 바이트가 스택 내용을 담을 수 있었음
    • 악성 클라이언트는 파일 다운로드당 1바이트를 유출할 수 있고, 유출 데이터에는 heap 객체 포인터, stack cookie, 지역 변수, 전역 변수 포인터, return pointer가 포함될 수 있음
    • “Some checksum buffer fixes”는 공격자 제어 s->s2length가 전송 체크섬 길이보다 커질 수 없게 했고, “prevent information leak off the stack”는 sum2 메모리를 0으로 초기화함
    • Go는 모든 변수를 zero value로 초기화하므로 이 취약점에 영향받지 않으며, gokrazy/rsync는 MD4 외 체크섬 선택이 도입된 프로토콜 버전 30이 아니라 프로토콜 버전 27을 구현함
  • CVE-2024-12087: 심볼릭 링크를 이용한 경로 순회, 심각도 7.5

    • Google Security report에 따르면 -l 또는 -a(--archive)로 심볼릭 링크 동기화가 활성화됐을 때, 악성 서버가 클라이언트에게 대상 디렉터리 밖 임의 파일을 쓰게 만들 수 있음
    • 공격은 --inc-recursive 모드에서 여러 파일 목록을 보내고, 첫 목록에서는 symlink를 디렉터리로, 다음 목록에서는 같은 이름을 대상 디렉터리 밖을 가리키는 심볼릭 링크로 바꾸는 방식임
    • Go 자체는 이 취약점을 막지 못하며, 원인은 여러 파일 목록을 병합한 뒤 다시 검증하지 않은 논리 오류임
    • upstream 수정은 누락된 검증을 추가함
    • gokrazy/rsync는 incremental recursion 모드(--inc-recursive)를 구현하지 않아 영향받지 않음
    • incremental recursion은 전체 파일 집합을 전송 시작 전에 모두 스캔하지 않고 “windowed” 방식으로 처리하게 해주지만, 구현 복잡성과 자원 사용량 사이의 트레이드오프가 있음
  • CVE-2024-12088: --safe-links 우회, 심각도 7.5

    • Google Security report에 따르면 --safe-links는 서버에서 받은 심볼릭 링크가 대상 디렉터리 안을 가리키도록 검증하는 기능임
    • 우회는 심볼릭 링크 대상 경로 중간에 다른 심볼릭 링크가 있는지를 고려하지 않아 발생함
    • 예를 들어 {DESTINATION}/a -> .와 {DESTINATION}/foo -> a/a/a/a/a/a/../../가 있으면, foo는 실제로 대상 디렉터리 밖을 가리키지만 unsafe_symlink()는 a/를 디렉터리로 가정해 안전하다고 판단함
    • upstream 수정은 경로 시작 부분을 제외한 위치의 ../를 허용하지 않도록 unsafe_symlink()를 더 엄격하게 만듦
    • Go 자체는 잘못된 검증 함수를 막지 못하며, gokrazy/rsync는 아직 --safe-links 기능을 구현하지 않아 취약하지 않음
  • CVE-2024-12086: 임의 파일 유출, 심각도 6.8

    • rsync receiver가 클라이언트 모드에서 rsync sender가 제공한 파일 이름을 정리하지 않았고, 대상 트리 밖 파일 열기를 막지 못함
    • 악성 sender는 receiver에게 대상 트리 밖 임의 파일의 체크섬을 비교하도록 지시하고, 1바이트 체크섬에 대한 receiver 반응을 관찰해 임의 파일을 유출할 수 있었음
    • Google Security report에 따르면 클라이언트가 악성 서버에 연결하면 서버는 클라이언트 머신의 임의 파일 내용을 유출할 수 있음
    • upstream 수정은 sender 제공 경로를 검증해 대상 트리 밖 파일 열기를 방지함
    • Go에는 이를 막을 수 있는 API가 있으며, 관련 방어로 Go의 os.Root가 제시됨
    • gokrazy/rsync는 fuzzy matching 기능이 도입된 프로토콜 버전 29가 아니라 프로토콜 버전 27을 구현하므로 취약하지 않음
  • CVE-2024-12747: 심볼릭 링크 경쟁 조건, 심각도 5.6

    • Red Hat Security Advisory에 따르면 rsync의 심볼릭 링크 처리 중 경쟁 조건에서 발생한 결함임
    • rsync의 기본 동작은 심볼릭 링크를 만나면 건너뛰는 것이지만, 공격자가 적절한 시점에 일반 파일을 심볼릭 링크로 바꾸면 기본 동작을 우회해 심볼릭 링크를 순회할 수 있었음
    • upstream 수정은 rsync sender의 open() 호출에 O_NOFOLLOW 옵션을 사용하도록 바꿈
    • gokrazy/rsync는 commit 1b1fbf6 전까지 취약했으며, 이 커밋은 upstream rsync와 같은 O_NOFOLLOW 완화를 도입함
    • Damien Neil은 gokrazy의 CVE-2024-12747 수정이 불충분하다고 봤고, O_NOFOLLOW는 마지막 경로 구성 요소의 심볼릭 링크 순회만 막는다고 판단함
    • 예를 들어 os.Open("dir/passwd")에서 이전 경로 구성 요소인 dir을 /etc로 향하는 심볼릭 링크로 바꾸면 여전히 우회 가능함
    • 이 문제는 2025년 4월 rsync 보안 연락처로 보고됐고, 2026-05-20에 공개된 CVE-2026-29518로 이어짐

2026년 5월 취약점

  • CVE-2026-29518: 심볼릭 링크 경쟁 조건, 심각도 7.0

    • rsync 3.4.3 NEWS entry에 따르면 chroot 없이 daemon mode에서 로컬 권한 상승을 허용하는 TOCTOU 심볼릭 링크 경쟁 조건임
    • use chroot = no로 설정된 rsync daemon은 parent path component에 대한 time-of-check/time-of-use 경쟁에 노출됨
    • 모듈에 쓰기 접근 권한이 있는 로컬 공격자는 receiver의 검사와 open() 사이에 parent directory component를 심볼릭 링크로 바꿔, 읽기에서는 basis-file disclosure를, 쓰기에서는 module 밖 file overwrite를 유발할 수 있음
    • 기본값인 use chroot = yes는 노출되지 않음
    • upstream 수정은 Go의 os.Root API와 유사한 secure_relative_open()을 사용함
    • gokrazy/rsync는 senderreceiver를 순회 저항 os.Root API로 전환하기 전까지 취약했음
  • CVE-2026-43618: 정수 오버플로로 원격 메모리 유출, 심각도 8.1

    • rsync 수신자의 압축 토큰 디코더가 32비트 부호 있는 카운터를 오버플로 검사 없이 누적해, 악의적 송신자가 프로세스 메모리 내용을 유출할 수 있음
    • 유출 대상에는 환경 변수, 비밀번호, 힙과 라이브러리 포인터가 포함될 수 있으며, ASLR을 약화하고 추가 익스플로잇을 쉽게 만들 수 있음
    • 영향 범위는 압축이 활성화된 인증된 데몬 연결이며, 프로토콜 30 이상에서 양쪽 피어가 압축을 광고하면 기본값으로 활성화됨
    • 우회책은 rsyncd.conf에서 refuse options = compress로 데몬 압축을 비활성화하는 것임
    • upstream 수정은 누락된 검사를 추가함
    • gokrazy/rsync는 압축을 구현하지 않아 취약하지 않으며, 압축 지원이 단순해 보이지만 비자명한 이유는 gokrazy/rsync issue #35에 정리돼 있음
  • CVE-2026-43620: 범위 밖 읽기 이후 서비스 거부, 심각도 6.5

    • 2025년에 send_files()에 추가된 parent_ndx<0 가드가 시각적으로 동일한 recv_files() 블록에는 적용되지 않아 발생함
    • 악의적 rsync 서버가 CF_INC_RECURSE 호환성 플래그와 잘못 구성된 flist를 보내면 수신자가 dir_flist->files[-1]을 읽고 역참조해 결정적 SIGSEGV로 이어질 수 있음
    • 영향 범위는 공격자가 제어하는 URL에서 일반 pull을 수행하는 모든 rsync 클라이언트이며, 프로토콜 30 이상의 기본값인 inc_recurse 때문에 피해자 쪽 특수 옵션이 필요 없음
    • 클라이언트에서 --no-inc-recursive를 사용하는 것이 우회책이며, upstream 수정은 recv_files()에도 parent_ndx<0 가드를 추가함
    • gokrazy/rsync는 --inc-recursive 증분 재귀 모드를 구현하지 않아 CVE-2024-12087과 마찬가지로 영향을 받지 않음
  • CVE-2026-43619: 추가 심볼릭 링크 경쟁 조건, 심각도 6.3

    • 수신자의 open() 호출에 대한 심볼릭 링크 경쟁 조건 수정(CVE-2026-29518)이 chmod, lchown, utimes, rename, unlink, mkdir, symlink, mknod, link, rmdir, lstat 등 다른 경로 기반 시스템 호출에는 적용되지 않았음
    • use chroot = no로 구성된 rsync 데몬에서 로컬 공격자가 수신자의 검사와 시스템 호출 사이에 부모 디렉터리 구성요소를 심볼릭 링크로 바꿔 내보낸 모듈 밖으로 리디렉션할 수 있음
    • 기본값인 use chroot = yes는 노출되지 않음
    • upstream 수정은 Linux 5.6+의 openat2, FreeBSD 13+와 macOS 15+의 O_RESOLVE_BENEATH, 그 외 환경의 구성요소별 O_NOFOLLOW 순회처럼 커널이 강제하는 제한 아래 열린 부모 dirfd를 통해 영향을 받는 경로 기반 시스템 호출을 처리함
    • gokrazy/rsync는 Go의 os.Root API를 전반적으로 사용하기 때문에 영향을 받지 않음
  • CVE-2026-43617: 호스트명 기반 ACL 우회, 심각도 4.8

    • 전역 daemon chroot = /X rsyncd.conf 설정이 있는 rsync 데몬에서 접속 클라이언트의 역방향 DNS 조회가 데몬이 /X로 chroot한 뒤 수행됐음
    • /X에 glibc 해석에 필요한 /etc/resolv.conf, /etc/nsswitch.conf, /etc/hosts, NSS 서비스 모듈이 없으면 조회가 실패하고 접속 호스트명이 "UNKNOWN"으로 설정됨
    • hosts deny = *.evil.example 같은 호스트명 기반 deny 규칙이 매칭되지 않아, PTR 레코드를 제어하는 공격자가 관리자가 차단하려던 호스트명에서 접속할 수 있음
    • IP 기반 ACL은 영향을 받지 않고, 모듈별 use chroot 설정은 이 취약점과 무관함
    • upstream 수정은 DNS 조회를 프로토콜의 더 이른 시점으로 이동함
    • gokrazy/rsync는 호스트명 기반 allow/deny 목록을 구현하지 않고 IP 기반 allow/deny 목록만 구현해 취약하지 않음
  • CVE-2026-45232: 스택 범위 밖 쓰기, 심각도 3.1

    • rsync 클라이언트의 HTTP CONNECT 프록시 지원에는 establish_proxy_connection()의 off-by-one 스택 범위 밖 쓰기가 있음
    • 프록시 또는 중간자 공격자가 첫 응답 줄에 '\n' 없이 1023바이트 이상을 반환하면, 후속 코드가 1024바이트 버퍼 바로 뒤에 '\0'을 써 인접 스택 슬롯을 손상시킬 수 있음
    • AddressSanitizer는 establish_proxy_connection 프레임의 socket.c:95에서 stack-buffer-overflow를 보고함
    • upstream 수정은 공격자가 제공한 데이터를 검증함
    • gokrazy/rsync는 이런 프록시 지원을 구현하지 않아 취약하지 않음

Go와 gokrazy/rsync가 실제로 막은 것

  • Go 런타임의 경계 검사는 더 심각한 보안 문제를 panic으로 바꾸며, panic은 여전히 서비스 거부 위험이지만 더 나은 실패 모드로 평가됨
  • Go는 메모리를 0으로 초기화해 CVE-2024-12085 같은 정보 유출을 불가능하게 만듦
  • Go의 os.Root API는 남은 취약점 대부분을 방지함
  • 12개 취약점 중 CVE-2026-43617 하나만 Go 사용으로 막을 수 없는 애플리케이션 로직 버그로 분류됨
  • gokrazy/rsync와 공식 upstream rsync의 핵심 차이는 Go로 작성됐다는 점 외에도 구현이 최소화돼 있다는 점임
  • gokrazy/rsync는 --inc-recursive, --safe-links, 압축, 프록시, 호스트명 ACL처럼 문제가 된 여러 기능을 구현하지 않아 여러 취약점을 피함
  • 모든 wire protocol 호환 rsync 구현처럼 gokrazy/rsync는 프로토콜 버전 27을 대상으로 하며, 이후 프로토콜 버전은 상당한 복잡성을 도입함
  • 게시 시점 기준 CVE별 gokrazy/rsync 상태:
    • 2024-12084: 구현은 있었고 panic 발생
    • 2024-12085: 프로토콜 30 기능이라 구현하지 않아 취약하지 않음
    • 2024-12086: 프로토콜 29 기능이라 구현하지 않아 취약하지 않음
    • 2024-12087: inc-rec를 구현하지 않아 취약하지 않음
    • 2024-12088: safe-links를 구현하지 않아 취약하지 않음
    • 2024-12747: 구현이 있었고 취약했음
    • 2026-29518: 구현이 있었고 패치됨
    • 2026-43617: 호스트 deny 목록을 구현하지 않아 취약하지 않음
    • 2026-43618: 압축을 구현하지 않아 취약하지 않음
    • 2026-43619: 구현이 있었고 패치됨
    • 2026-43620: inc-rec를 구현하지 않아 취약하지 않음
    • 2026-45232: 프록시를 구현하지 않아 취약하지 않음
  • gokrazy/rsync의 알려진 모든 취약점은 수정됐으며, 위 상태는 각 CVE가 공개됐던 시점의 상태를 나타냄
  • 2025년 1월 취약점 공개 당시 gokrazy/rsync는 CVE-2024-12084에서 panic이 발생했고 CVE-2024-12747 TOCTOU 경쟁 조건에는 취약했음
  • TOCTOU 문제를 수정하는 과정에서 CVE-2026-29518이 발견됐고, 해당 CVE 공개 전에 gokrazy/rsync에서 수정됨
  • CVE-2026-43619는 더 늦게 발견됐지만, Go의 os.Root 전면 사용이라는 같은 수정으로 이미 gokrazy/rsync에서 해결돼 있었음

역할 구분과 취약점 명명

  • 여러 취약점 보고서는 “server”와 “client”라는 표현을 사용했지만, rsync 전송에서는 rsync 클라이언트와 서버 양쪽 모두가 송신자(sender, 파일 업로드) 또는 수신자(receiver, 파일 다운로드) 역할을 맡을 수 있음
  • 데몬 모드에서는 파일시스템 접근을 미리 구성된 모듈 경로로 제한할 수 있지만, command mode에서는 그렇지 않음
  • 4가지 구성과 역할/프로토콜 계층은 rsync combinations 다이어그램에서 확인 가능함
  • Arbitrary File Leak 취약점(CVE-2024-12086)의 원래 제목인 “Server leaks arbitrary client files”는 오해를 부르기 쉬움
  • 더 정확한 표현은 rsync 수신자악의적 송신자에게 임의 파일을 유출한다는 것임
  • 악의적 클라이언트 송신자는 SSH를 통한 command mode 같은 환경에서 패치되지 않은 원격 rsync가 대상 트리 밖의 파일, 예를 들어 /etc/shadow 시스템 비밀번호 데이터베이스를 열게 만들 수 있음
  • 데몬 모드에서는 서버가 추가 경로 정규화(path sanitization)를 활성화해 이 공격을 막음
  • Symlink Path Traversal 취약점(CVE-2024-12087)도 “malicious server”라고 표현하지만, 정확히는 클라이언트 또는 서버 어느 쪽이든 될 수 있는 악의적 송신자

OpenBSD openrsync와 비교

  • OpenBSD 프로젝트는 보안에 집중하는 것으로 알려져 있으며, openrsync는 체크섬 길이를 검증하고 체크섬 크기/알고리듬으로 MD4 하나만 지원해 Heap Buffer Overflow(CVE-2024-12084)와 Stack Info Leak(CVE-2024-12085)의 영향을 받지 않음
  • openrsync는 gokrazy/rsync처럼 관련 기능을 구현하지 않아 CVE-2024-12086, CVE-2024-12087, CVE-2024-12088의 영향을 받지 않음
  • 설령 취약했더라도 OpenBSD에서 실행될 때는 OpenBSD unveil(2)pledge(2)로 파일시스템 접근을 제한하는 심층 방어가 성공적 악용을 막았을 수 있음
  • openrsync는 심볼릭 링크 지원을 구현한 순간부터 O_NOFOLLOW를 사용했기 때문에 CVE-2024-12747의 영향을 받지 않음
  • 하지만 O_NOFOLLOW는 이 문제에 충분한 수정이 아니므로 openrsync는 CVE-2026-29518에는 영향을 받음
  • 2026년 5월 묶음도 대부분 관련 기능을 구현하지 않았다는 점에서 유사함
  • openrsync는 검증을 성실히 구현하고 공격 표면을 제한하며 심층 방어를 적용해 보고된 취약점 대부분의 영향을 받지 않음

심층 방어와 os.Root

  • Linux mount namespace

    • gokrazy/rsync 프로젝트를 시작한 지 몇 주 안에 파일시스템 객체 접근을 제한하기 위해 Linux에서 권한 강하와 mount/pid namespace 사용을 지원하는 기능이 추가됨
    • 이 접근은 경로 순회 공격 완화에 잘 작동하지만 권한이 필요해 root로 실행하거나 배포판/시스템에서 활성화된 경우 Linux user namespace 안에서 실행해야 함
    • mount namespace는 서버 구성에는 적합하지만, 보통 사람 사용자의 계정으로 실행되는 대화형 일회성 전송에는 사용할 수 없는 경우가 많음
  • systemd 하드닝

    • Linux mount/pid namespace 지원을 도입한 같은 커밋에는 홈 디렉터리로 파일시스템 접근을 제한하는 systemd 서비스 파일도 포함됐고, README에서는 사용 사례에 따라 파일시스템 접근을 더 제한하도록 권장함
    • 이런 파일시스템 제한이 올바르게 구성되면 File Leak(CVE-2024-12086)과 Path Traversal(CVE-2024-12087) 취약점을 완화함
    • Symlink Race Condition(CVE-2024-12747)은 rsync 프로세스를 통한 권한 상승에 의존하지만, DynamicUser 기능 덕분에 프로세스가 다른 사용자보다 적은 권한을 가짐
    • mount namespace와 마찬가지로 서버 구성에는 좋지만 대화형 일회성 사용에 설정하기에는 번거로움
  • Linux Landlock

    • Porting OpenBSD pledge() to Linux (2022)를 통해 Linux에도 OpenBSD의 unveil(2)와 유사한 비권한·프로세스별 접근 제어인 Landlock API가 있음을 상기함
    • 기본 아이디어는 프로그램이 작업할 디렉터리를 알고 나면 unveil("/home/michael/backups", "rw"); 같은 호출로 해당 경로 외의 파일시스템 위치에 더 이상 접근하지 못하게 하는 것임
    • 2025년 3월 파일시스템 접근 제한을 위해 Landlock 지원이 구현됨
    • 설정이 맞춰진 뒤에는 비권한 gokrazy/rsync 실행에서도 rsync 전송을 소스에는 읽기 전용, 대상 디렉터리에는 읽기·쓰기 권한으로 제한할 수 있음
    • 단점은 Landlock이 프로세스 수준에서 동작한다는 점임
    • Landlock 정책에는 프로그램이 필요한 파일도 포함돼야 하며, 예를 들어 gokrazy/rsync는 사용자 ID 조회를 위해 /etc/passwd를 읽을 수 있어야 하므로 공격자가 /etc/passwd를 노린다면 Landlock은 도움이 되지 않음
  • Go의 os.Root

    • 2025년 2월 Go 1.24는 경로 순회에 강한 os.Root API를 도입했으며, 관련 배경은 Damien Neil의 The Go Blog: Traversal-resistant file APIs에 정리돼 있음
    • os.Root는 Landlock과 비교해 파일시스템 작업별로 더 세밀한 제어를 제공함
    • 2025년 8월 출시된 Go 1.25는 os.Root에 더 많은 메서드를 추가해 대부분의 파일시스템 사용에 편리한 선택지가 됨
    • gokrazy/rsync의 모든 파일시스템 사용은 os.Root로 전환됐고, 사용자가 입출력 디렉터리를 구성하지만 네트워크로 받은 파일명은 신뢰할 수 없다는 모델에 잘 맞음
    • mknod(2)처럼 API로 직접 만들 수 없을 것 같은 시스템 호출도 안전하게 사용할 수 있음
    • Damien Neil은 os.Root.OpenFile로 대상의 부모 디렉터리를 열고, File.Fd로 해당 디렉터리 파일 디스크립터를 얻은 뒤, golang.org/x/sys/unix#Mknodat로 파일을 만들 수 있다고 설명함
    • 실제 사용 예시는 internal/receiver/generatormknod_linux.go, line 15-29에 있음
    • Linux에는 mknodat(2)는 있지만, Linux 7.0 기준 bind(2)에 대응하는 bindat는 없음
    • Lennart Poettering은 bindat 없이 경로 해석을 건너뛰는 트릭으로 /proc/self/<fd>/foobar에 bind할 수 있다고 제안함
    • 알려진 안전한 /proc/self/<fd> 뒤에 경로가 아니라 basename, 즉 마지막 경로 구성요소만 지정하면 경로 해석이 건너뛰어지며, 관련 코드는 line 49-56에 있음
    • 이 두 가지 팁을 통해 gokrazy/rsync v0.3.1 이상은 os.Root를 완전히 사용하며, 모든 파일시스템 접근이 경로 순회 안전성을 갖게 됨

핵심 교훈

  • TOCTOU 취약점(CVE-2024-12747, CVE-2026-29518, CVE-2026-43619)을 제외한 모든 취약점은 입력 검증 누락 또는 잘못된 입력 검증에서 비롯됨
  • 세 경우에는 애초에 검증이 없었고, CVE-2024-12088은 파일시스템 경로 해석이라는 주제가 까다로워 기존 검증이 모든 경계 사례를 다루지 못한 경우임
  • 가장 가치 있는 구조적 수정은 경계 검사처럼 항상 켜진 검증과 Go의 os.Root 같은 기본 안전 API를 제공하는 것임
  • 일부 취약점은 rsync 프로토콜 진화에서 발생했으며, 원래는 충분한 검증을 하던 코드에 새 기능이 추가되면서 검증이 올바르게 갱신되지 않았음
  • 체크섬 알고리듬 협상과 증분 재귀는 프로토콜 버전 30에 추가됐고, 기존 검증은 새 기능의 처리 방식에 맞게 업데이트되지 않았음
  • gokrazy/rsync와 openrsync는 취약한 기능을 구현하지 않았다는 이유만으로 12개 보안 취약점 중 8개에 취약하지 않았음
  • 해당 기능들은 어느 시점에 누군가에게 가치가 있었기 때문에 rsync에 추가됐으며, 소프트웨어 개발을 멈춰야 한다는 의미는 아님
  • 이상적인 선택은 사용 사례의 복잡성에 적절하고 비례하는 복잡성의 구현을 사용하는 것이며, 단순한 사용 사례에는 단순한 구현을 선택하고 필요할 때만 모든 기능을 갖춘 구현을 선택하는 것임
Read Entire Article