System Design

MiraclePtr: Chrome chặn use-after-free trong C++ thế nào

Tóm tắt

Một nửa bug khai thác được của Chrome là use-after-free. raw_ptr (MiraclePtr) giữ lại và đầu độc vùng nhớ còn con trỏ treo, kỳ vọng chặn khai thác khoảng 50% lỗi UAF trong browser process, đổi lại 3,5–6,5% bộ nhớ.

Trong Chrome, một nửa số bug có thể khai thác đã biết là use-after-free (UAF): code giữ con trỏ tới vùng nhớ đã bị giải phóng, rồi kẻ tấn công lấp vùng nhớ đó bằng dữ liệu của mình. Chrome có hàng chục triệu dòng C++ không thể viết lại trong vài năm. Đội bảo mật Chrome chọn một cách khác: thay con trỏ thô trong các trường của class bằng một con trỏ "thông minh nhẹ" tên raw_ptr<T>, triển khai thuật toán BackupRefPtr của dự án MiraclePtr. Mục tiêu: khiến khoảng 50% lỗi UAF trong browser process không còn khai thác được.

Cơ chế: đếm tham chiếu ẩn và cách ly

  1. Đếm tham chiếu ẩn: bộ cấp phát PartitionAlloc dành thêm chỗ trong mỗi vùng cấp phát cho một bộ đếm. Mỗi khi raw_ptr<T> được tạo, huỷ hay gán, bộ đếm tăng/giảm.
  2. Cách ly (quarantine): nếu vùng nhớ bị free trong khi bộ đếm còn lớn hơn 0 (tức còn con trỏ treo), PartitionAlloc không trả vùng nhớ về để tái sử dụng mà giữ lại.
  3. Đầu độc (poisoning): vùng nhớ cách ly được ghi đè bằng mẫu 0xEF..EF, nên đọc qua con trỏ treo dễ gây crash, giúp phát hiện và sửa bug.
  4. Giải phóng thật: chỉ khi bộ đếm về 0, vùng nhớ mới được tái sử dụng.

Điểm hay: kẻ tấn công cần tái chiếm vùng nhớ đã giải phóng để khai thác UAF. Khi vùng nhớ bị giữ lại và đầu độc, bước then chốt đó không làm được.

class Widget {
  raw_ptr<Delegate> delegate_;   // thay cho Delegate* delegate_;
};

Chi phí: số liệu thật

Hạng mụcKết quả (Chrome 99–102)
Bộ nhớ browser processTăng 4,5–6,5% trên Windows, 3,5–5% trên Android
Bộ nhớ bị cách lyKhoảng 0,01% bộ nhớ browser process
Hiệu năng WindowsKhông có suy giảm có ý nghĩa thống kê ở đa số chỉ số chính; tranh chấp main thread tăng khoảng 7%
Hiệu năng AndroidTranh chấp main thread +6%, First Input Delay +1%, First Contentful Paint +0,5%
Kích thước con trỏBằng con trỏ thô; truy cập (dereference) không tốn thêm

Phần lớn chi phí bộ nhớ đến từ bộ đếm 4 byte mỗi vùng cấp phát, có khi đẩy vùng cấp phát sang bucket lớn hơn. Đội Chrome bù lại bằng các tối ưu PartitionAlloc trước đó, vốn tiết kiệm 10–25% bộ nhớ.

Triển khai ở quy mô Chrome

  • Hơn 15.000 con trỏ thô được viết lại thành raw_ptr<T>.
  • Bật cho browser process trên Windows và Android từ Chrome 102; tài liệu hiện tại cho biết đã bật mặc định cho các process không phải renderer trên Android, Windows, ChromeOS, macOS, Linux và Fuchsia.
  • Quy tắc style: trường của class/struct dùng raw_ptr<T> thay vì T* (trừ code chỉ chạy trong renderer), được plugin Clang của Chromium kiểm tra tự động. Biến cục bộ, tham số và giá trị trả về vẫn dùng con trỏ thô.

Rủi ro và giới hạn

  • Không bảo vệ tất cả: chỉ trường của class/struct được bảo vệ; con trỏ cục bộ, con trỏ trong union, vùng nhớ ngoài PartitionAlloc (stack, V8 heap…) không được.
  • Vẫn là undefined behavior: đọc con trỏ treo vẫn là lỗi; MiraclePtr chỉ khiến nó khó khai thác hơn.
  • Rò rỉ do cách ly: theo tài liệu Chromium, vùng nhớ bị giữ lại vì con trỏ treo vài lần đã gây rò rỉ đáng kể.
  • Bug bounty: khi mới triển khai, Chrome nói MiraclePtr chưa được tính vào mức nghiêm trọng của bug cho đến khi phủ tới mọi người dùng.

Áp dụng thế nào?

  • Dev: Nếu dự án C++ của bạn có nhiều đối tượng sống lâu tham chiếu chéo nhau, đó là chỗ UAF hay xảy ra. Rà các trường con trỏ không sở hữu và xem xét smart pointer hoặc allocator có cách ly.
  • Tech Lead: Học cách Chrome làm: quy tắc rõ ràng (trường class dùng kiểu an toàn), công cụ tự động ép quy tắc, cơ chế miễn trừ có ghi lý do, và A/B test hiệu năng trước khi bật rộng.
  • PM: Chấp nhận khoảng 5% bộ nhớ để loại bỏ khả năng khai thác của một nửa lớp bug nguy hiểm nhất là đánh đổi có lợi. Hãy đo bằng số liệu như Chrome, không tranh luận bằng cảm tính.

Nguồn & tham khảo

  1. Use-after-freedom: MiraclePtr — Google Security Blog
  2. raw_ptr<T> (aka MiraclePtr, aka BackupRefPtr) — Chromium