1P by GN⁺ | ★ favorite | 댓글 1개
  • Google은 eBPF verifier의 CVE-2023-2163을 Buzzer로 발견했고, 이 취약점이 로컬 권한 상승과 컨테이너 탈출에 악용될 수 있음을 확인함
  • eBPF는 커널 기능을 런타임에 확장하지만 높은 권한에서 임의 바이트코드를 실행하므로, 로드 전 verifier 검증이 보안의 핵심 축이 됨
  • 문제는 verifier의 path pruning이 실제로 다른 실행 경로를 이미 안전한 경로와 동등하다고 잘못 판단하면서 발생함
  • 익스플로잇은 verifier가 0으로 본 레지스터가 런타임에는 다른 값을 갖는 차이를 이용해 eBPF 스택 포인터를 오염시키고, 임의 읽기/쓰기와 KASLR 우회로 이어짐
  • 패치는 precise register에 영향을 주는 imprecise register까지 precise로 표시하는 방식이며, 같은 포인터 산술 퍼징 전략에서는 이후 추가 이슈가 발견되지 않음

eBPF verifier가 넓히는 커널 공격 표면

  • eBPF는 복잡한 커널 모듈 없이도 Linux 커널 기능을 런타임에 확장할 수 있게 하는 기술임
  • eBPF 프로그램은 커스텀 바이트코드로 작성되며, 특정 이벤트가 발생했을 때 실행되기 전에 안전성 검증을 거침
    • 대표 예시는 특정 syscall 호출 시 실행되는 eBPF 프로그램임
  • 높은 권한 수준에서 임의 코드를 실행할 수 있는 구조라서 커널의 공격 표면이 크게 늘어남
  • 프로그램은 로드 전에 verifier를 통과해야 하며, verifier는 eBPF의 보안 가정이 충족되는지 검사함
  • verifier 취약점이 악용되면 보통 로컬 권한 상승 또는 컨테이너 환경의 컨테이너 탈출로 이어짐

Buzzer와 포인터 산술 퍼징

  • Google은 eBPF verifier 코드를 자동으로 감사하기 위해 Buzzer를 만들었음
  • Buzzer는 구문적으로 유효한 eBPF 프로그램을 대량 생성하는 퍼저이며, 논리 버그를 유발하기 위한 전략을 설정할 수 있음
  • CVE-2023-2163 발견에는 포인터 산술 전략이 사용됨
    • 레지스터를 임의 값으로 초기화하는 헤더를 생성함
    • 임의의 산술 및 점프 명령어 시퀀스를 생성함
    • 임의 레지스터를 선택해 eBPF map element 포인터와 더하기 연산을 수행함
    • 해당 element에 매직 값을 기록함
    • 사용자 공간에서 기록된 값이 관찰되지 않으면 경계 밖 쓰기가 발생했을 가능성이 있음
  • Buzzer가 발견한 CVE-2023-2163은 eBPF path pruning 로직에 있었고, 컨테이너 탈출과 로컬 권한 상승에 모두 사용할 수 있는 익스플로잇으로 이어짐

path pruning과 precise tracking 버그

  • eBPF verifier는 프로그램이 안전하게 실행될 수 있는지 확인하기 위해 가능한 실행 경로를 시뮬레이션함
  • 조건 분기에서 레지스터 값이 확실하지 않으면 verifier는 가능한 모든 상태의 실행을 따라가려 함
  • 조건 점프가 많아질수록 실행 경로 수가 지수적으로 늘어나며, 커널에 프로그램을 로드하는 성능에도 악영향을 줌
  • 이를 줄이기 위해 eBPF 개발자들은 path pruning을 도입함
    • verifier가 특정 상태와 동등한 상태가 이미 안전하게 exit 명령에 도달한다고 보장할 수 있으면, 해당 경로를 더 탐색하지 않음
  • 더 효율적인 pruning을 위해 precise tracking 개념이 함께 쓰임
    • 레지스터가 포인터 산술 연산에 관여하거나 helper 함수에 상수로 전달되면 precise로 표시됨
    • verifier는 해당 레지스터가 관련된 모든 상태를 탐색해야 함
  • CVE-2023-2163에서는 r6의 정밀도에 영향을 주는 r9가 제대로 표시되지 않았음
    • verifier는 r9가 r6의 preciseness에 기여하지 않는다고 가정함
    • 이전 경로에서 r6로 안전하게 exit에 도달할 수 있다고 판단한 뒤, 나머지 상태를 동등하다고 보고 pruning함
    • 런타임에는 1:2:4:6 경로가 실행됐고, 6에서 verifier가 0이라고 본 r6이 실제로는 다른 값을 가진 채 포인터 산술 연산에 사용될 수 있었음

임의 읽기/쓰기와 KASLR 우회

  • 익스플로잇 코드는 Google security research repository에 공개됨
  • 익스플로잇 개발에는 @chompie@_manfp의 작업이 중요하게 활용됨
  • 전체 흐름은 먼저 임의 읽기/쓰기를 얻고, 이후 프로세스 credentials를 찾아 uid와 fs_struct 포인터를 패치해 권한을 상승시키는 방식임
  • 첫 단계에서는 오염된 레지스터 값을 1로 만듦
    • 수동 분석에서 런타임 r6 값은 0x400이었고 verifier는 이를 0으로 봄
    • r6 >>= 10 명령으로 원하는 값 1을 만들었음
  • 이후 bpf_skb_load_bytes_relative helper 함수로 eBPF 스택의 포인터를 오염시킴
    • verifier는 len이 8이라고 판단하지만, 실제 런타임에서는 r6 값 때문에 len이 9가 됨
    • 결과적으로 8바이트가 아니라 9바이트를 써서 스택 offset -32 값의 첫 바이트를 오염시킴
  • 오염된 스택 포인터를 조작하면 eBPF map pointer를 누출할 수 있음
    • 사용자 공간에서 R2와 R3 값을 읽고, R2가 0xBACA가 되는지 확인함
    • 이 조건이 맞으면 R3가 map pointer 누출값이 됨
    • eBPF map pointer 누출은 KASLR을 우회한 상태로 이어짐
  • 같은 전략으로 단일 바이트가 아니라 연속된 스택 영역 전체를 원하는 포인터로 덮어쓰면 임의 읽기/쓰기가 가능함
    • verifier 관점에서는 BPF 스택 조작이지만, 실제로는 커널 메모리를 읽고 쓸 수 있음

map leak에서 root shell까지

  • map pointer 누출 이후 익스플로잇은 Chompie의 익스플로잇과 크게 다르지 않으며, 일부 코드를 차용함
  • 대략적인 절차는 다음과 같음
    • kstrtab에서 init_pid_ns 문자열을 반복적으로 검색함
    • 해당 문자열을 참조하는 ksymtab 심볼을 찾아 init_pid_ns 구조체 주소를 얻음
    • radix tree를 순회해 comm 필드가 실행 중인 익스플로잇 실행 파일 이름과 일치하는 entry를 찾음
      • 컨테이너 안에서 실행 중이면 PID가 신뢰할 수 있는 휴리스틱이 아니므로 PID만 사용하지 않음
    • uid를 0으로 패치하고 fs_struct 포인터를 패치함
    • fs_struct를 PID 1이 참조하는 포인터와 같은 값으로 패치하면, 컨테이너 안에서 실행 중일 때 호스트 파일 시스템을 관찰할 수 있음
    • system("/bin/bash")를 실행해 root shell을 얻음
  • GitHub에 공개된 코드는 특정 Linux 버전에서만 컨테이너 탈출로 동작함
  • Ubuntu와 일부 배포판에서는 덮어쓴 데이터 구조의 offset이 달라 로컬 권한 상승으로만 동작함
  • 어떤 Linux 배포판에서도 동작하게 하려면 코드 조정이 필요함

패치와 후속 검증

  • CVE-2023-2163의 원인 분석과 패치는 kernel mailing list에서 확인할 수 있음
  • 수정은 precise register에 영향을 준 연산의 imprecise register를 precise로 표시하는 방식임
  • 이 수정이 eBPF verifier 성능에 영향을 주는지는 명확하지 않음
  • 같은 포인터 산술 퍼징 전략을 계속 실행했지만 추가 이슈는 발견되지 않았음
  • Buzzer는 계속 개발 중이며, 오픈소스 커뮤니티의 기여를 GitHub repo를 통해 받고 있음

댓글과 토론

Hacker News 의견들
  • eBPF가 가장 흔히 쓰이는 플랫폼에서는 애초에 비권한 코드가 eBPF 프로그램을 로드할 수 없어서, 검증기 버그의 영향이 크지 않은 경우가 많음
    이런 버그는 결국 root → ring0 취약점인데, 별것 아니라는 뜻은 아니지만 서버 측 작업에서는 대체로 감수할 만한 절충임
    특히 eBPF의 커널 로컬 권한 상승 이력은 커널 전체와 비교하면 꽤 양호한 편이고, 현재 eBPF 환경에서 검증기의 가장 큰 가치는 잘못된 eBPF 프로그램으로 커널을 실수로 죽이기 어렵게 해준다는 데 있음
    일반적인 로드 가능한 커널 모듈에서는 이 말이 우스울 정도로 성립하지 않음

    • PoC는 범위 밖 포인터로 eBPF 맵을 쓰지만, 단순한 스칼라 값 범위 추적 오류라면 seccomp를 통해 로드 가능한 비확장 BPF 프로그램으로도 악용 가능해 보임
      이 경우 대부분의 플랫폼에서 권한이 필요하지 않음
      그리고 비권한 사용자 네임스페이스가 있으면 스스로 “root”가 될 수 있으니 root → ring0도 덜 제한적인 문제가 됨
      배포판들이 이를 켰다가 대부분 다시 끈 뒤로 나온 eBPF 버그 PoC에서 계속 봐온 흐름임
    • 컨테이너에 CAP_BPF를 줄 수 있다는 점도 잊으면 안 됨
      Cilium 같은 도구가 커질수록 cap_bpf가 있는 컨테이너 환경에 진입하는 공격 경로가 점점 더 현실적이 됨
    • 검증기 버그를 해결하는 일은 비권한 eBPF 사용을 안전하게 만들기 위한 전제조건이라 중요함
  • “Uno no es ninguno”는 문자 그대로는 “하나는 없음이 아니다”, 즉 “One is not none”에 가까움

    • 스페인어에서는 이중 부정이 실제로 이중 부정이 아닌 경우가 흔함
      예를 들어 “여기 아무것도 없다”는 “no hay nada aquí”라고 하는데, 단어별로 옮기면 “여기 아무것도 없는 게 아니다”처럼 보임
      Royal Spanish Academy도 이렇게 설명함:

      https://www.rae.es/espanol-al-dia/doble-negacion-no-vino-nad...

      이른바 “이중 부정”은 스페인어와 다른 로망스어에서 특정 상황에 반드시 성립해야 하는 부정 일치 때문에 생기며, 그 결과 부사 no와 부정 의미를 가진 다른 요소가 한 문장에 함께 나타남
      이 두 “부정”이 동시에 있다고 해서 문장의 부정 의미가 상쇄되지는 않음

  • 예전에 eBPF를 써보려 했을 때, 필요한 작업을 하기에는 표현력이 부족했음
    제한된 유연성을 얻기 위해 커널 공간 복잡성을 늘리는 게 정말 정당한지 의문임
    패킷 필터링 용도라면 이해하지만, 샌드박싱처럼 다른 용도에 쓰이는 건 설득력이 약해 보임

    • 이런 용도에는 DTrace 같은 다른 기술도 있음
      커널의 선택지는 eBPF 아니면 아무것도 없음이 아니라, eBPF 아니면 그와 비슷한 다른 무언가임
      직접 많이 쓰지 않을 수는 있지만, 하루 종일 쓰는 사람들도 있음
      FAANG 엔지니어들이 모든 서버에서 항상 이런 프로그램을 수십 개, 어쩌면 수백 개씩 돌린다고 했던 것 같고, 일회성 사용은 빼고도 그렇다고 함
      FAANG은 전담 커널 개발자도 고용하고 있으니, 자기들이 쓰는 이 복잡성에 자금도 대는 셈임
      나도 eBPF로 문제를 해결한 적이 있음
      eBPF 없이는 커널 전문가가 아닌 사람에게 사실상 풀 수 없는 문제들이었고, 자주 필요하진 않지만 필요할 때는 대체재가 없음
      어떤 경우에는 커널 전문가에게도 eBPF를 쓰느냐, 아니면 커스텀 커널 패치를 영원히 유지하느냐의 선택임
    • 전통적인 로드 가능한 커널 모드 드라이버가 패치나 eBPF보다 낫지 않을까?
      안전하지 않다는 건 알지만, 다루는 사람들은 큰 힘에는 책임이 따른다는 걸 알고 있음
  • “Uno no es ninguno”는 “One is not none”으로 번역되는 게 맞는 것 같음

    https://bughunters.google.com/blog/6303226026131456/a-deep-d...

    • 그렇지 않고 “One is none”으로 번역됨
      외국어 화자들이, 나를 포함해, 고생하는 악명 높은 이중 부정

      https://spanish.stackexchange.com/questions/26777/how-does-d...

    • 직역하면 그렇지만, 스페인어에서는 이상하게도 이중 부정이 보통 그냥 부정으로 쓰임

    • “one ain't nothin'” 정도로 옮기면 더 맞을지도 모름

  • 우리나라에는 “바지 속의 고슴도치”라는 표현이 있음
    아무리 쓸모가 많아도, 이건 안전하고 신중하게 작성된 것 같지는 않음

    • 경험이 쌓이면 안전하고 신중하게 했어도 실수는 생길 수 있다는 걸 알게 됨