Peihua Zhang, Chenggang Wu, Xiangyu Meng, Yinqian Zhang, ... and Zhe Wang 33rd USENIX Security Symposium (USENIX Security 2024)
개요
eBPF의 한계를 극복하기 위해서 Runtime check을 통해서 static verification의 한계를 극복한 논문이다.
Motivation
eBPF는 크게 두개의 문제가 있다.
- Complexity issue: eBPF verifier가 legal program을 false positive하게 reject하는 경우
- Security issue: Malicious program이 false negative하게 accept되는 경우
이 두개의 문제는 eBPF static verifier가 full-path anslysis, 즉 모든 code path를 검사하기 때문에 생기는 현상이다.
Importance
Complexity issue는 프로그래머들이 static verifier를 통과하도록 프로그램을 짜게 하는 extra work을 강요하며, Security issue는 커널에서 벌어지기 떄문에, 전체 Kernel의 Security guarantees에 치명적이다.
Challenge
eBPF를 Dynamic하게 Runtime check을 하는 것은 좋으나, 크게 3개의 문제점을 해결해야 한다.
- Memory safety: eBPF program이 Arbitrarily한 커널 메모리에 접근하지 못하도록 할것
- leakage prevention: eBPF program이 kernel information을 leak하는 것을 막을 것
- DoS prevention: eBPF program이 crash를 일으키거나 CPU time을 한도이상 사용하지 않도록 할것
Main Idea
eBPF program에 Runtime check을 삽입하여서 해결하도록 하였다. 이를 명확히 하기 위해서 HIVE는 포인터를 두가지 타입으로 분류하였다.
- Inclusive type pointers: eBPF가 내부적으로 사용하는 포인터로써, Information leak해도 별 문제가 없다. Kernel과 eBPF가 사용하는 Address space를 분리함으로써 information leak을 방지하였다.
- Exclusive type pointers: 커널로 직접 갈수 있는 포인터로써, leak되면 안된다. Relaxed된 static verifier로는 막지 못한다. 이를 위해서 Runtime SFI boundary check를 삽입하였다.
Background
간략한 AArch64에 대한 하드웨어 아키텍쳐에 대한 정리
- Unprivileged load/store: AArch64는 ldtr, sttr이라는 커널 모드에서도 user mode의 메모리만 접근할 수 있는 명령어를 제공한다.
- E0PD: E0PD를 사용하면 Timing attack을 막기 위해서 특정 Fault들을 모두 constant time뒤에 interrupt를 보낸다.
- Pointer authentication code: Pointer의 integrity를 지키기 위해서, PA Code라는 것을 unused bit에 추가하고, pac*혹은 aut*라는 명령어를 통해서 sign, authentication을 하도록 한다.
Design
HIVE는 Independent address space를 eBPF program각각 만들어 주었다. 이 BPF address space는 User-level에 존재한다. BPF는 내부적으로 모든 load/store operation을 위해서 E0PD 명령어만 사용하였다. 그러나 이 경우에, helper fuction이 접근하지 못함으로, shadow BPF space라는 kernel to user space영역을 만들어서 helper function들이 이 영역을 참조하도록 하였다. 만약 잘못된 커널 접근이 발생할 경우에는, exception이 발생하며, 이 exception은 eBPF program을 termination하도록 하였다. exception이 발생하면 HIVE는 kernel state를 entry point로 roll-back하였다.
위의 Design포인트를 적용하기 위해서는 다음의 3가지의 Challenge point를 해결해야 한다.
- BPF Map은 object-granular isolation을 지원해야 한다. 예를 들어서 eBPF는 오브젝트의 matadata를 같은 page map에 저장할 수도 있다.
- Kernel opbject에 대한 fine-grained한 직접접근이 필요할 수도 있다. 이는 performance를 위해서 필수적이다.
- Kernel object는 leak되면 안된다.
- eBPF Pointer Types
- eBPF는 포인터를 내부적으로 두 타입으로 구분한다. inclusive type은 내부 데이터를 가르키는 타입이며, exlusive type은 메타데이터 같은 커널 오브젝트를 가르키는 타입이다. Inclusive type같은 경우에는 Stack, Map, Packet과 같은 것이 들어가며 User-level Address space에 위치하도록 하였다. 이때 BPF Program에서 User-level에 접근하지 못하도록 E0PD를 이용하여서 Access permission을 Isolation하였다. 또한 커널의 Helper function argument로 넘겨질 경우에는, Pointer에 특정 padding추가하여서 kernel address space로 변경, seamless하게 접근 가능하도록 하였다. Exclusive type과 같은 경우에는 PAC을 이용하여서 integrity check를 수행하도록 하였다.
- Information Leakage Prevention
- HIVE는 불리된 address space를 각각의 eBPF program별로 만들어서, KASLR을 해치지 않도록 하였다. 또한 한번 사용된 Address space는 재사용되지 않도록 하여서 reply attack을 막았다. 커널 포인터와 같은 경우에는 object type descriptor라는 새로운 table을 만들어서, 내부적으로는 이 object type descriptor만 보이도록 하였다. 나중에 코드가 패치되면, object type을 참조하는 모든 instruction들은 자동으로 pointer arithmetic으로 바뀌며, helper function에 Argument로 넘길때도 같은 일이 일어난다.
- Passive DoS Prevention
- Timer가 있어서, 특정 시간이 지나면 자동으로 eBPF program을 termination할 수 있도록 하였다.
Conclusion
이 논문은 eBPF의 Safety property를 잘 분석하였다. 그러나 우선 중간에 termination될 경우 kernel state를 어떻게 복구할 것이냐에 대한 내용이 없다. 또한 분석하고 있는 Security analysis들은 static verification이 막지 못하는 것으로써, dynamic runtime이 막았다고는 하나, dynamic runtime은 TCB증가로 훨씬 더 큰 hole이 될것이라 생각한다. Evaluation cases들도 다 기존에 eBPF에 작동하던 것으로 좀더 흥미로운 Evaluation point들이 있었으면 한다. 마지막으로 propose된 솔루션들은 ad-hoc적인 내용이 많아, simple한 eBPF static verifier를 여러개의 isolation domain을 통해서 막는 것이 가지는 한계는 명확하다고 생각한다. 그러나 전반적으로 eBPF의 대한 깊은 분석과, 각각의 pitfall들을 어떻게 isolation시킬 수 있는지 철저하게 분석하여서 이 부분에 장점은 명확하다고 생각한다.