1P by GN⁺ | ★ favorite | 댓글 1개
  • xz.git의 CMakeLists.txt 변경은 Landlock 샌드박스 감지를 헤더 존재 확인에서 실제 컴파일 테스트로 바꾸는 과정에서, 테스트 코드 안에 단독 . 문자가 들어간 사례임
  • 새 검사는 linux/landlock.h가 있어도 필요한 syscall 정의가 없을 수 있는 시스템을 걸러내기 위해 추가됨
  • 컴파일 테스트는 <linux/landlock.h>, <sys/syscall.h>, <sys/prctl.h>를 포함하고 prctl, SYS_landlock_create_ruleset, SYS_landlock_restrict_self, LANDLOCK_CREATE_RULESET_VERSION을 참조함
  • 기존 기준인 HAVE_LINUX_LANDLOCK_HHAVE_LINUX_LANDLOCK으로 바뀌며, SANDBOX_COMPILE_DEFINITIONSANDBOX_FOUND 설정도 이 결과를 따름
  • 커밋은 Autotools와 CMake 빌드의 Linux Landlock feature test 수정이며, 제공된 diff는 CMake 쪽 변경을 보여줌

CMake의 Landlock 감지 방식 변경

  • 대상 파일은 xz.git의 CMakeLists.txt이며, 변경 지점은 # Sandboxing: Landlock 블록임
  • 기존 CMake 로직은 ENABLE_SANDBOXON 또는 landlock이고 SANDBOX_FOUND가 아닐 때 check_include_file(linux/landlock.h HAVE_LINUX_LANDLOCK_H)로 헤더 존재만 확인함
  • 변경 후에는 check_c_source_compiles로 작은 C 코드를 실제 컴파일해 Landlock 사용 가능 여부를 확인함
    • 일부 시스템은 linux/landlock.h를 갖고 있어도 Linux Landlock에 필요한 syscall 정의가 없을 수 있음
    • 테스트 코드는 <linux/landlock.h>, <sys/syscall.h>, <sys/prctl.h>를 포함함
    • my_sandbox() 안에서 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), SYS_landlock_create_ruleset, SYS_landlock_restrict_self, LANDLOCK_CREATE_RULESET_VERSION을 참조함

단독 문자와 변수 이름 변경

  • 삽입된 컴파일 테스트 코드의 include 블록 다음에 단독 . 줄이 있음
  • 결과 변수는 HAVE_LINUX_LANDLOCK_H에서 HAVE_LINUX_LANDLOCK으로 바뀜
  • 조건문도 if(HAVE_LINUX_LANDLOCK_H)에서 if(HAVE_LINUX_LANDLOCK)로 변경됨
  • SANDBOX_COMPILE_DEFINITION 값은 "HAVE_LINUX_LANDLOCK_H" 대신 "HAVE_LINUX_LANDLOCK"를 사용함
  • 이후 SANDBOX_FOUND ON 설정은 유지됨

댓글과 토론

Hacker News 의견들
  • 답: https://git.tukaani.org/?p=xz.git;a=commitdiff;h=f9cf4c05edd...
    Linux Landlock 접근 제어 시스템을 잘 모르면 설명은 여기 있음: https://docs.kernel.org/userspace-api/landlock.html
    xz 공식(아마도...) 사고 대응 페이지: https://tukaani.org/xz-backdoor/

    • 그러니까 그 함수는 뒤따르는 C 코드가 컴파일되는지 확인하고, 그 경우에만 Landlock을 켰던 건가?
      그런데 diff의 왼쪽 가장자리 근처에 붙어 있어서 알아보기 어려운 작은 마침표 하나 때문에 C 코드가 항상 유효하지 않게 되고, 그래서 Landlock이 항상 꺼져 있게 된 거고?
      비열할 정도로 인상적이고, 인상적일 정도로 비열함. 처음 읽었을 때는 나도 못 봤음
    • 이건 그럴듯한 부인 가능성이 있음
      숨기려면 Unicode 유사 문자를 바꾸는 더 나은 방법도 있음. 일부는 글꼴에 따라 픽셀까지 같아 보임
      내가 흐름을 놓친 걸 수도 있지만, 여기서 고의성이 확정된 건가?
    • 이건 너무 악질이라 PR 중에 현장에서 잡혀도 “아, 내 IDE 자동 포맷이 이렇게 했네” 하고 넘길 수 있을 정도임
    • Lasse Collin이 다시 등장했고, 아마 화가 난 듯함
    • 내 버전 관리 시스템은 이런 통짜 초록/빨강 문자열보다 변경된 문자를 더 잘 강조해 주는 느낌임
  • 조심해서 훑어보다가 분명 있으면 안 되는 자리에 마침표가 보여서 “생각보다 어렵지 않네”라고 생각함
    화면을 톡 건드렸더니 그 마침표가 움직였음
    빌어먹을 모니터 먼지

  • 시스템 보안이 이렇게 느슨한 정확성의 사슬에 의존한다는 게 믿기지 않음. 막을 수 있는 지점이 얼마든지 있었음
    + # A compile check is done here because some systems have
    + # linux/landlock.h, but do not have the syscalls defined
    + # in order to actually use Linux Landlock.
    그런 시스템의 헤더를 고쳐서 명시적으로 제외하게 해야 함. 헤더가 자신의 기능을 선언하지 않는다면 무슨 의미가 있나?
    그리고 바이너리 blob이 만들어진 뒤(오픈소스에서 컴파일됐더라도) 보안이 온전한지 확인하는 테스트가 하나도 없는 이유도 모르겠음
    웹사이트 결제 기능도 핵심 기능에 대한 종단 간 테스트 없이 배포하지는 않을 것임. 기능 추가가 안정성보다 우선되는 우선순위 불일치가 있어 보임
    수정이 단순히 .을 제거하는 방식이 아니길 바람. HN의 다른 글에서 . 제거를 보여주는 걸 방금 봤음

    • 그는 완전히 새로운 테스트 프레임워크를 도입한 뒤, 2년에 걸쳐 천천히 백도어를 심었음
    • 인용한 건 Jia Tan의 말임 [1]. 그 악성 행위자가 애초에 검사를 의도적으로 망가뜨리면서 쓴 주석임
      헤더 수정이나 추가 테스트로는 막지 못했을 것임. 애초에 헤더가 망가졌다는 징후가 없고, 추가 테스트도 다른 방식으로 손상되거나 릴리스 tarball에서 무시될 수 있었기 때문임
      [1] https://git.tukaani.org/?p=xz.git;a=commit;h=328c52da8a2bbb8...
    • 약간 옆길이지만, GCC 9.4.0이 망가진 arm_acle.h 헤더를 배포한 적이 있었음 [https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100985] — 그 헤더를 포함한 코드는 항상 컴파일에 실패했음
      사용자가 GCC 9.4.0에서 컴파일하려 할 수 있고, 그 헤더에 의존하는 기능이 애플리케이션에 핵심적이지 않았기 때문에, 빌드가 헤더가 망가졌다고 감지하면 그냥 그 기능을 끄고 경고를 냈음
      xz로 돌아오면, 보안이 최우선순위가 아니라면 선택 기능 실패를 무시하고 진행하는 것도 합리적으로 보임. 물론 사후적으로는 손가락질하기 쉽지만, 그 맥락을 빼면 완전히 말이 안 되는 판단은 아님
    • 오픈소스 사용자로서 이런 제안을 할 만큼 잘 알지는 못하니, 직접 개발해서 기여해 준다면 고마울 것 같음
    • 인용한 주석 코드가 정확하다는 건 알고 있는 건가?
  • 이런, 그의 마지막 커밋은 보안 보고를 더 나쁘게 만들고 있음: https://git.tukaani.org/?p=xz.git;a=commitdiff;h=af071ef7702...

    • 왜 이게 받아들여진 거지? 진지하게 궁금함
  • Landlock은 어디서/어떻게 쓰이도록 되어 있었던 건가?
    압축/해제 같은 범용 라이브러리에서는 실제로 쓰기 어려울 것 같음. 라이브러리는 프로그램이 무엇을 해야 하는지, 무엇을 제한해야 하는지 알 수 없음
    프로그램이라면 더 명확할 수 있음
    sshd 공격은 liblzma를 라이브러리로 사용했음. 그러면 Landlock을 끄는 건 무관해 보이는데? 아직 발견되지 않은 나쁜 코드가 더 있거나, 나중에 넣을 계획이었다는 신호인가?

    • 아마도 sshd 자체가 실행의 특정 지점 이후 자기 권한을 잠그는 데 사용했을 것임
      Landlock을 제거하면 데몬이 스스로를 잠그지 않아서, 악용 단계에 도달했을 때 페이로드 실행이 더 쉬워짐
      이 둘이 무관하다고 보지 않음. 이미 페이로드를 염두에 두고 있었고, 이것이 장애물이 될 걸 알아챘던 것 같음
    • 이상함. 같은 프로젝트에서 서로 무관한 백도어 비슷한 작업을 두 개 하는 건 정말 엉성하다고 봄. 얻는 건 적은데 발각 위험만 크게 높이는 듯함
      이 사건은 매우 혼란스러움. 어떤 부분은 믿기 어려울 만큼 정교한데, 다른 부분은 꽤 허술해 보임
    • 사용할 수 있고 좋은 생각이지만, 의도적 백도어보다는 취약점 악용을 막기 위한 용도임
      아이디어는 라이브러리가 복잡한 작업을 별도 스레드에서 시작하고, 사용자 코드가 호출한 API 안에서 그 스레드를 기다리는 것임. 스레드는 스스로 Landlock을 적용한 뒤 작업을 시작함. 뭔가 잘못되면 코드가 격리됨
      물론 이 경우에는 샌드박스가 공격자에게 제어되므로 작동하지 않음. 공격자가 그냥 끄거나 원래보다 약하게 만들 수 있기 때문임. 그래도 다른 방식으로도 할 수는 있음
  • 대체 여기서 노린 게 뭐였을까? 나중에 더 많은 백도어, 더 그럴듯하게 부인 가능한 백도어를 넣으려던 건가? 내가 보기엔 oss-fuzz도 이 변경도 발견된 백도어를 실제로 찾아내지는 못했을 것 같음
    그런데 왜 백도어 달걀을 한 바구니, 즉 한 라이브러리에 몰아넣었을까?

    • 그 라이브러리는 충분히 깊게 자리 잡았고, 충분히 신뢰받고 있으며, 주 개발자는 정신 건강 문제 때문에 인터넷을 오래 쉬는 일이 있었음
      게다가 라이브러리 자체를 백도어화하는 게 아니라, 그걸 쓰는 도구를 노리는 방식임. “Reflections on trusting trust” 스타일임
      실패하기 전까지는 완벽한 계획처럼 들림
    • 그런데 그게 라이브러리 하나뿐이었다고 누가 말할 수 있나?
  • xz에서 Landlock이 켜지는 걸 막는 게 왜 유용했는지 궁금함. 나중 단계에서 xz 아카이브에 악성 내용을 주입하려던 걸까? 그렇다면 왜 xz 자체에 악성 동작을 그냥 넣지 않았을까?

    • 의도적인 취약점은 실제 악성 내용보다 훨씬 숨기기 쉽기 때문임
      유지 관리하는 C 프로젝트에 버퍼 오버플로나 해제 후 사용을 몰래 넣는 건 들키지 않고 가능할 수도 있음. 실제 트로이 목마를 배포하는 건 훨씬 어렵고, xz-to-sshd 백도어에서 그게 드러났음
    • ssh를 노린 것 외에 xz 자체를 겨냥한 더 미묘한 백도어가 있을 수도 있음. 분명 목표는 은밀함이었음
  • 이건 의외로 너무 눈에 띔. 기능을 끄는 기법은 영리하고 커밋도 꽤 그럴듯함. 그런데 왜 단순히 철자를 틀리는 대신 명백한 문법 오류를 택했을까? 예를 들어 실수가 PR_SET_NO_NEW_PRIV였다면 알아봤을까?
    그쪽이 부인 가능성도 더 높았을 것임

  • 별개로, 이 악성코드 팀은 보안 문제를 식별하도록 AI를 훈련시키는 데 훌륭한 데이터셋을 만들어 줬음. 모든 커밋에 보안 문제가 있고, 오픈소스 커뮤니티가 하나씩 훑으며 찾아낼 테니까
    정리 작업을 하는 유지보수자들에게 고마움. 분명 재미있는 일은 아닐 것임

    • 일반화가 잘 될 것 같지는 않음. 기껏해야 군비 경쟁일 뿐임
    • 내가 본 AI 활용 중 꽤 멋진 편임
    • 이런 건 처음 듣는데, 관련 정보를 좀 올려줄 수 있나?
  • 그래서 자동으로 기능을 켜고 끄는 configure 스타일 빌드 시스템을 정말 좋아하지 않음. 뭔가 원하면 명시적으로 켜고 싶음. 기능을 기본값으로 둘 충분한 이유가 있다면, 대신 명시적으로 끌 수 있게 해야 함

    • 패키징할 때 이게 엄청난 고통임. 패키지를 설정하고, 빌드될 때까지 의존성을 추가한 뒤 끝났다고 생각함. 그런데 기능 X가 빠져 있음. 뭐지? 아, libY가 없어서 조용히 꺼졌네. 다시 돌아가서 추가함. 그러면 사용자가 기능 Z가 없다고 보고함
      그냥 기본 기능 묶음을 두고 필요에 따라 --enable-a --disable-b를 허용하면 됨
      검사 과정의 버그를 조용히 삼켜 버린다는 건 또 다른 문제임