PUMM: Preventing Use-After-Free Using Execution Unit Partitioning


Carter Yagemann, Simon P. Chung, Brendan Saltaformaggio, Wenke Lee
USENIX Security 2023

개요

PUMM은 Intel PT나 Perf와 같은 Tool을 통해서 CFG를 생성하여, 프로그램에서 독립적으로 실행되는 부분을 파악, 그 부분에 대해서만 One-time-allocator를 적용시켜서, OTA의 단점중에 하나였던 Virtual address explosion문제를 해결하였다.

Motivation

Use-after-free 버그는 C나 C++에서 자주 발생하는 버그지만, 그 파급력에 불구하고, Detect하는 것은 매우 어려운 일이다. 이 문제를 해결하기 위해서 One-time-allocatorGarbage collection과 같은 방식들이 많이 사용되지만, OTA는 Virtual address exhaustion문제가 발생하며 Garbage collection방식은 Pointer obfuscation이나 False positive detection문제를 해결하기 힘들다.

Importantce

기존 One time allocator방식은 Globally한 단위로 VA들이 겹치지 않도록 배치 하기때문에, VA가 Exhaust되는 문제가 생겼다. 또한 기존에 프로그램 분석을 통한 Execution Unit을 통한 프로그램의 Dependency를 분석하는 여러 방법들 특히 그중에서도 Execution unit paritioning이라는 방식이 있었지만 OTA에 적용된 적은 없었다. 추가적으로 EUP를 직접 OTA에 적용시키는 것은 여러 Challenge가 있기 때문에, 바로 적용시키는 것도 힘든 일 이었다.

Background

Execution unit partitioning
Execution unit partitioning이란 프로그램을 데이터 dependency가 없는 최소한의 unit으로 나누는 작업을 말한다. 예를 들어서 HTTP서버에서 한 리퀘스트를 처리하는 Loop은 EUP분석에서 하나의 Unit으로 분석된다.

Main Idea

Main Idea
  • OTA를 Global하게 구현하는 것이 아니라 Execution unit별로 쪼개어서 적용한다. 이를 통해서 VA Exhaustion문제를 없앤다.
  • 이러한 적용 범위를 결정하는 것을 Runtime에 결정 하는 것이 아니라 Offline에 결정하도록 하여서 CPU Overhead를 줄인다.
Challenge
  • Execution unit을 어떻게 그리고 어느 정도의 범위로 정할 것인가.
  • 기존에 System call paritioning이나 Managing memory를 위해서 사용되던 EUP알고리즘을 어떻게 OTA에 최적화 시킬 것인가.
  • EUP를 통해서 획득한 정보를 통해서 어떻게 효율적인 OTA Memory management policy를 세올 것인가.

Design

Offline에서 소스 코드를 binary instrumentation이나 PT를 통해서 tracing을 해서 CFG를 만든다. 이 CFG를 통해서 program중에서 memory release해도 괜찮은 부분을 파악하여서, 그 부분에 국한하여서 OTA를 실행시킨다.

Dynamic Profiling
Execution path를 LLVM IR없이 Bytecode에서 직접회득하기 위해서, Intel PT를 통해서 Fuzzer를 돌렸을 때의 Instruction을 분석하였다. Intel PT를 통해서 명령어들의 수행을 가져오게 되면, Interrupt로 인한 Hole이 발생한다. 이를 PUMM에서는 Challenge로 두고 해결하기 위해서 Interrupt가 들어온 경우에는 Interrupt 수행 루틴이 포함된 결과를 무시하도록 하였다. 후에 Fuzzer로 인해서 그 수행루틴이 포함된 경우가 포함될 확률이 많음으로 문제되지 않는다.
Execution Unit Partitioning
마지막으로 CFG에서 EUP를 추출하여서 최종적인 분석을 위한 EUP를 만들어 내었다. 여기서 loop 안에 branch condition이 있는 loop이 있을 경우에, return코드를 삽입함으로서, 이 문제를 해결하였다. <사실 잘 이해가 되지 않는 부분이었음. 설명이 부족하거나 배경지식이 부족한것 같음.>
Policy Generation & Enforcement
Runtime에서는, 주어진 EUP에 대해서, 각 Execution unit에서 처음으로 실행되는 Allocator API (즉 PUMM이 체크해야 할 포인트)를 확인하여서 만약 현재 불려진 Allocator API가 이 지점일 경우, Execution unit에서 호출한 모든 API들이 할당한 메모리를 사용해도 안전한 것으로 체크한다. 보다 구체적으로는 (Return address, Quarantine list)의 데이터에서 현재 stack의 return address가 일치할 경우, Quarantine list에 있는 모든 Memory를 해제하는 방식으로 작동한다.

Conclusion

  • Execution path분석을 통해서 OTA를 효율적으로 구현할 수 있다는 점을 지적한 점이 Novel point이다.
  • Fuzzer을 통해서 Execution path를 분석하였는데, Enginnering적으로는 좋지만, Research방면으로는 조금 엄밀하지 못한 방법인것 같다.
  • Challenge point로 잡은 부분이 불명확하며, Introduction의 설명과는 다르게 Challenge point의 해결방법이 Straight forward하거나, 이미 기존 연구에서 수행했을 것 같다는 생각이 드는 부분이 많았다.
  • PUMM은 Heuristic적으로 UAF버그를 Prevent하는 방식임으로, 이 가정에 어긋나는 패턴 (예를 들어서 한 EUP에서 다른 EUP로 Pointer migration이 일어난다던지)와 같은 일이 발생하면 FFmalloc, MarkUS와는 다르게 Full-prevention이 불가능하다.