Arm v9 ra mắt Tiện ích gắn thẻ bộ nhớ (MTE) Arm, một chế độ triển khai phần cứng của bộ nhớ được gắn thẻ.
Ở cấp độ cao, MTE gắn thẻ từng hoạt động phân bổ/huỷ phân bổ bộ nhớ bằng siêu dữ liệu bổ sung. Nó chỉ định một thẻ cho vị trí bộ nhớ, sau đó có thể liên kết với các con trỏ tham chiếu đến vị trí bộ nhớ đó. Tại thời gian chạy, CPU sẽ kiểm tra để đảm bảo con trỏ và thẻ siêu dữ liệu khớp với nhau trên mỗi lần tải và lưu trữ.
Trong Android 12, trình phân bổ bộ nhớ heap không gian người dùng và nhân có thể tăng cường từng hoạt động phân bổ bằng siêu dữ liệu. Điều này giúp phát hiện các lỗi use-after-free và buffer-overflow. Đây là nguồn phổ biến nhất gây ra các lỗi về độ an toàn của bộ nhớ trong cơ sở mã của chúng tôi.
Các chế độ hoạt động của MTE
MTE có 3 chế độ hoạt động:
- Chế độ đồng bộ (SYNC)
- Chế độ không đồng bộ (ASYNC)
- Chế độ không đối xứng (ASYMM)
Chế độ đồng bộ (SYNC)
Chế độ này được tối ưu hoá cho độ chính xác của việc phát hiện lỗi hơn là hiệu suất và có thể được dùng làm công cụ phát hiện lỗi chính xác khi mức hao tổn hiệu suất cao hơn được chấp nhận. Khi được bật, chế độ SYNC của MTE sẽ hoạt động như một giải pháp giảm thiểu bảo mật.
Khi một thẻ không khớp, bộ xử lý sẽ huỷ ngay quá trình thực thi và chấm dứt quy trình bằng SIGSEGV
(mã SEGV_MTESERR
) cùng đầy đủ thông tin về quyền truy cập vào bộ nhớ và địa chỉ bị lỗi.
Bạn nên sử dụng chế độ này trong quá trình kiểm thử thay cho HWASan/KASAN hoặc trong phiên bản chính thức khi quy trình mục tiêu cho thấy một nền tảng dễ bị tấn công. Ngoài ra, khi chế độ ASYNC cho biết có lỗi, bạn có thể lấy được báo cáo lỗi chính xác bằng cách sử dụng API thời gian chạy để chuyển đổi quá trình thực thi sang chế độ SYNC.
Khi chạy ở chế độ SYNC, trình phân bổ Android sẽ ghi lại dấu vết ngăn xếp cho tất cả các hoạt động phân bổ và huỷ phân bổ, đồng thời sử dụng các dấu vết này để cung cấp báo cáo lỗi chi tiết hơn, bao gồm cả nội dung giải thích về lỗi bộ nhớ (chẳng hạn như use-after-free hoặc buffer-overflow) và dấu vết ngăn xếp của các sự kiện bộ nhớ có liên quan. Những báo cáo như vậy cung cấp nhiều thông tin theo ngữ cảnh hơn, đồng thời giúp việc theo dõi và khắc phục lỗi dễ dàng hơn.
Chế độ không đồng bộ (ASYNC)
Chế độ này được tối ưu hoá cho hiệu suất hơn là cho độ chính xác của báo cáo lỗi. Bạn có thể dùng chế độ này để phát hiện lỗi về độ an toàn của bộ nhớ đó ở mức hao tổn thấp.
Khi một thẻ không khớp, bộ xử lý sẽ tiếp tục thực thi cho đến mục nhân hệ điều hành gần nhất (ví dụ: gián đoạn lệnh gọi hệ thống hoặc bộ tính giờ), trong đó bộ xử lý chấm dứt quy trình này bằng SIGSEGV
(mã SEGV_MTEAERR
) mà không ghi lại địa chỉ bị lỗi hoặc quyền truy cập vào bộ nhớ.
Bạn nên sử dụng chế độ này trong phiên bản chính thức trên các cơ sở mã được kiểm thử kỹ lưỡng, trong đó mật độ lỗi về độ an toàn của bộ nhớ được biết đến là thấp, đạt được bằng cách dùng chế độ SYNC trong quá trình kiểm thử.
Chế độ không đối xứng (ASYMM)
Một tính năng bổ sung trong Arm v8.7-A, chế độ MTE bất đối xứng cung cấp tính năng kiểm tra đồng bộ khi đọc bộ nhớ và kiểm tra không đồng bộ khi ghi bộ nhớ, với hiệu suất tương tự như hiệu suất của chế độ ASYNC. Trong hầu hết các trường hợp, chế độ này là một điểm cải tiến so với chế độ ASYNC và bạn nên sử dụng chế độ này thay vì ASYNC bất cứ khi nào có thể.
Vì lý do này, không có API nào được mô tả bên dưới đề cập đến chế độ Bất đối xứng. Thay vào đó, bạn có thể định cấu hình hệ điều hành để luôn sử dụng chế độ Bất đối xứng khi được yêu cầu chế độ Bất đồng bộ. Vui lòng tham khảo phần "Định cấu hình cấp độ MTE ưu tiên dành riêng cho CPU" để biết thêm thông tin.
MTE trong không gian người dùng
Các phần sau đây mô tả cách bật MTE cho các quy trình hệ thống và ứng dụng. Theo mặc định, MTE bị tắt, trừ phi một trong các lựa chọn dưới đây được đặt cho một quy trình cụ thể (xem các thành phần mà MTE được bật bên dưới).
Bật MTE bằng hệ thống xây dựng
Là một thuộc tính trên toàn quy trình, MTE được kiểm soát bằng chế độ cài đặt thời gian xây dựng của tệp thực thi chính. Các lựa chọn sau đây cho phép thay đổi chế độ cài đặt này cho từng tệp thực thi hoặc cho toàn bộ thư mục con trong cây nguồn. Chế độ cài đặt này sẽ bị bỏ qua trên các thư viện hoặc bất kỳ mục tiêu nào không phải là tệp thực thi cũng như không phải là một bài kiểm thử.
1. Bật MTE trong Android.bp
(ví dụ), cho một dự án cụ thể:
Chế độ MTE | Xem xét |
---|---|
MTE không đồng bộ | sanitize: { memtag_heap: true, } |
MTE đồng bộ | sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, } |
hoặc trong Android.mk:
Chế độ MTE | Xem xét |
---|---|
Asynchronous MTE |
LOCAL_SANITIZE := memtag_heap |
Synchronous MTE |
LOCAL_SANITIZE := memtag_heap LOCAL_SANITIZE_DIAG := memtag_heap |
2. Bật MTE trên một thư mục con trong cây nguồn bằng cách sử dụng một biến sản phẩm:
Chế độ MTE | Danh sách bao gồm | Danh sách loại trừ |
---|---|---|
async | PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
đồng bộ hóa | PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS
MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
hoặc
Chế độ MTE | Xem xét |
---|---|
MTE không đồng bộ | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
MTE đồng bộ | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
hoặc bằng cách chỉ định đường dẫn loại trừ của một tệp thực thi:
Chế độ MTE | Xem xét |
---|---|
MTE không đồng bộ | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
MTE đồng bộ |
Ví dụ: (cách sử dụng tương tự như PRODUCT_CFI_INCLUDE_PATHS
)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor) PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \ vendor/$(vendor)/projectB
Bật MTE bằng cách sử dụng các thuộc tính hệ thống
Bạn có thể ghi đè các chế độ cài đặt bản dựng nêu trên trong thời gian chạy bằng cách đặt thuộc tính hệ thống sau:
arm64.memtag.process.<basename> = (off|sync|async)
Trong đó basename
là tên cơ sở của tệp thực thi.
Ví dụ: để đặt /system/bin/ping
hoặc /data/local/tmp/ping
để dùng MTE không đồng bộ, hãy dùng adb shell setprop arm64.memtag.process.ping async
.
Bật MTE bằng biến môi trường
Một cách khác để ghi đè chế độ cài đặt bản dựng cho các quy trình gốc (không phải ứng dụng) là xác định biến môi trường: MEMTAG_OPTIONS=(off|sync|async)
Nếu cả biến môi trường và thuộc tính hệ thống đều được xác định, thì biến sẽ được ưu tiên.
Bật MTE cho ứng dụng
Nếu không được chỉ định, MTE sẽ bị tắt theo mặc định, nhưng những ứng dụng muốn dùng MTE có thể làm như vậy bằng cách đặt android:memtagMode
bên dưới thẻ <application>
hoặc <process>
trong AndroidManifest.xml
.
android:memtagMode=(off|default|sync|async)
Khi được đặt trên thẻ <application>
, thuộc tính này ảnh hưởng đến tất cả các quy trình mà ứng dụng dùng, đồng thời có thể bị ghi đè cho từng quy trình riêng bằng cách đặt thẻ <process>
.
Để thử nghiệm, bạn có thể dùng các thay đổi về khả năng tương thích để đặt giá trị mặc định của thuộc tính memtagMode
cho một ứng dụng không chỉ định giá trị nào trong tệp kê khai (hoặc chỉ định default
).
Bạn có thể tìm thấy các thay đổi này trong phần System > Advanced > Developer options
> App Compatibility Changes
của trình đơn cài đặt chung. Thao tác đặt NATIVE_MEMTAG_ASYNC
hoặc NATIVE_MEMTAG_SYNC
sẽ bật MTE cho một ứng dụng cụ thể.
Ngoài ra, bạn có thể đặt chế độ này bằng lệnh am
như sau:
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
Tạo một hình ảnh hệ thống MTE
Bạn nên bật MTE trên tất cả các tệp nhị phân gốc trong quá trình phát triển và khởi động. Điều này giúp phát hiện sớm các lỗi an toàn bộ nhớ và cung cấp phạm vi phủ sóng thực tế cho người dùng nếu được bật trong các bản dựng thử nghiệm.
Bạn nên bật MTE ở chế độ Đồng bộ trên tất cả các tệp nhị phân gốc trong quá trình phát triển
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
Giống như mọi biến trong hệ thống xây dựng, SANITIZE_TARGET
có thể được dùng làm biến môi trường hoặc chế độ cài đặt make
(ví dụ: trong tệp product.mk
).
Xin lưu ý rằng thao tác này sẽ bật MTE cho tất cả các quy trình gốc, nhưng không bật cho các ứng dụng (được phân nhánh từ zygote64
) mà MTE có thể được bật theo hướng dẫn ở trên.
Định cấu hình cấp độ MTE ưu tiên dành riêng cho CPU
Trên một số CPU, hiệu suất của MTE ở chế độ ASYMM hoặc thậm chí là SYNC có thể tương tự như hiệu suất của ASYNC. Điều này khiến bạn nên bật các chế độ kiểm tra nghiêm ngặt hơn trên những CPU đó khi yêu cầu chế độ kiểm tra ít nghiêm ngặt hơn, để có được lợi ích phát hiện lỗi của các chế độ kiểm tra nghiêm ngặt hơn mà không gặp phải nhược điểm về hiệu suất.
Theo mặc định, các quy trình được định cấu hình để chạy ở chế độ ASYNC sẽ chạy ở chế độ ASYNC trên tất cả các CPU. Để định cấu hình nhân chạy các quy trình này ở chế độ SYNC trên các CPU cụ thể, bạn phải ghi giá trị sync vào mục sysfs
/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
tại thời điểm khởi động. Bạn có thể thực hiện việc này bằng một tập lệnh khởi động. Ví dụ: để định cấu hình CPU 0-1 chạy các quy trình ở chế độ ASYNC trong chế độ SYNC và CPU 2-3 sử dụng chế độ ASYMM, bạn có thể thêm nội dung sau vào mệnh đề init của một tập lệnh init của nhà cung cấp:
write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm
Các đánh dấu xoá từ các quy trình ở chế độ ASYNC đang chạy ở chế độ SYNC sẽ chứa một dấu vết ngăn xếp chính xác về vị trí của lỗi bộ nhớ. Tuy nhiên, chúng sẽ không bao gồm dấu vết ngăn xếp phân bổ hoặc huỷ phân bổ. Bạn chỉ có thể sử dụng các dấu vết ngăn xếp này nếu quy trình được định cấu hình để chạy ở chế độ SYNC.
int mallopt(M_THREAD_DISABLE_MEM_INIT, level)
trong đó level
là 0 hoặc 1.
Tắt quá trình khởi tạo bộ nhớ trong malloc và tránh thay đổi thẻ bộ nhớ, trừ phi cần thiết để đảm bảo tính chính xác.
int mallopt(M_MEMTAG_TUNING, level)
trong đó level
là:
M_MEMTAG_TUNING_BUFFER_OVERFLOW
M_MEMTAG_TUNING_UAF
Chọn chiến lược phân bổ thẻ.
- Chế độ cài đặt mặc định là
M_MEMTAG_TUNING_BUFFER_OVERFLOW
. M_MEMTAG_TUNING_BUFFER_OVERFLOW
– cho phép phát hiện lỗi tràn và thiếu bộ nhớ đệm tuyến tính một cách xác định bằng cách chỉ định các giá trị thẻ riêng biệt cho các hoạt động phân bổ liền kề. Chế độ này có khả năng phát hiện lỗi sử dụng sau khi giải phóng thấp hơn một chút vì chỉ có một nửa số giá trị thẻ có thể có cho mỗi vị trí bộ nhớ. Xin lưu ý rằng MTE không thể phát hiện tràn trong cùng một hạt thẻ (đoạn được căn chỉnh 16 byte) và có thể bỏ lỡ các tràn nhỏ ngay cả ở chế độ này. Tình trạng tràn như vậy không thể là nguyên nhân gây ra lỗi hỏng bộ nhớ, vì bộ nhớ trong một hạt không bao giờ được dùng cho nhiều lượt phân bổ.M_MEMTAG_TUNING_UAF
– cho phép các thẻ được ngẫu nhiên hoá độc lập với xác suất đồng nhất là ~93% để phát hiện cả lỗi không gian (tràn bộ đệm) và lỗi tạm thời (sử dụng sau khi giải phóng bộ nhớ).
Ngoài các API được mô tả ở trên, người dùng có kinh nghiệm có thể muốn biết những điều sau:
- Việc thiết lập thanh ghi phần cứng
PSTATE.TCO
có thể tạm thời ngăn chặn việc kiểm tra thẻ (ví dụ). Ví dụ: khi sao chép một dải ô nhớ có nội dung thẻ không xác định hoặc giải quyết tình trạng tắc nghẽn hiệu suất trong một vòng lặp nóng. - Khi sử dụng
M_HEAP_TAGGING_LEVEL_SYNC
, trình xử lý sự cố hệ thống sẽ cung cấp thêm thông tin, chẳng hạn như dấu vết ngăn xếp phân bổ và huỷ phân bổ. Chức năng này yêu cầu quyền truy cập vào các bit thẻ và được bật bằng cách truyền cờSA_EXPOSE_TAGBITS
khi đặt trình xử lý tín hiệu. Mọi chương trình đặt trình xử lý tín hiệu riêng và uỷ quyền các sự cố không xác định cho hệ thống đều nên làm như vậy.
MTE trong nhân
Để bật KASAN được tăng tốc MTE cho nhân, hãy định cấu hình nhân bằng CONFIG_KASAN=y
, CONFIG_KASAN_HW_TAGS=y
. Các cấu hình này được bật theo mặc định trên các nhân GKI, bắt đầu từ Android
12-5.10
.
Bạn có thể kiểm soát tham số này tại thời điểm khởi động bằng cách sử dụng các đối số dòng lệnh sau:
kasan=[on|off]
– bật hoặc tắt KASAN (mặc định:on
)kasan.mode=[sync|async]
– chọn giữa chế độ đồng bộ và không đồng bộ (mặc định:sync
)kasan.stacktrace=[on|off]
– có thu thập dấu vết ngăn xếp hay không (mặc định:on
)- việc thu thập dấu vết ngăn xếp cũng yêu cầu
stack_depot_disable=off
.
- việc thu thập dấu vết ngăn xếp cũng yêu cầu
kasan.fault=[report|panic]
– có chỉ in báo cáo hay cũng gây ra lỗi nghiêm trọng cho nhân (mặc định:report
). Bất kể lựa chọn này là gì, tính năng kiểm tra thẻ sẽ bị tắt sau lỗi đầu tiên được báo cáo.
Mức sử dụng được đề xuất
Bạn nên sử dụng chế độ SYNC trong quá trình khởi động, phát triển và thử nghiệm. Bạn nên bật tuỳ chọn này trên toàn cầu cho tất cả các quy trình bằng cách sử dụng biến môi trường hoặc bằng hệ thống bản dựng. Ở chế độ này, các lỗi được phát hiện sớm trong quá trình phát triển, cơ sở mã được ổn định nhanh hơn và tránh được chi phí phát hiện lỗi sau này trong quá trình phát hành.
Bạn nên sử dụng chế độ ASYNC trong quá trình sản xuất. Điều này cung cấp một công cụ hao tổn thấp để phát hiện sự hiện diện của các lỗi về độ an toàn của bộ nhớ trong một quy trình cũng như khả năng phòng thủ chuyên sâu hơn. Sau khi phát hiện lỗi, nhà phát triển có thể tận dụng các API thời gian chạy để chuyển sang chế độ SYNC và nhận được dấu vết ngăn xếp chính xác từ một nhóm người dùng được lấy mẫu.
Bạn nên định cấu hình cấp MTE ưu tiên dành riêng cho CPU cho SoC. Chế độ Asymm thường có các đặc điểm hiệu suất giống như ASYNC và hầu như luôn được ưu tiên hơn. Các lõi nhỏ theo thứ tự thường cho thấy hiệu suất tương tự trong cả 3 chế độ và có thể được định cấu hình để ưu tiên SYNC.
Nhà phát triển nên kiểm tra sự cố bằng cách kiểm tra /data/tombstones
, logcat
hoặc bằng cách giám sát quy trình DropboxManager
của nhà cung cấp để tìm lỗi của người dùng cuối. Để biết thêm thông tin về cách gỡ lỗi mã gốc của Android, hãy xem thông tin tại đây.
Các thành phần nền tảng đã bật MTE
Trong Android 12, một số thành phần hệ thống quan trọng về bảo mật sử dụng MTE ASYNC để phát hiện sự cố của người dùng cuối và đóng vai trò là một lớp phòng thủ theo chiều sâu bổ sung. Các thành phần này là:
- Các tiện ích và chương trình nền về mạng (ngoại trừ
netd
) - Bluetooth, SecureElement, NFC HAL và các ứng dụng hệ thống
statsd
trình nềnsystem_server
zygote64
(để cho phép ứng dụng chọn sử dụng MTE)
Các mục tiêu này được chọn dựa trên những tiêu chí sau:
- Một quy trình có đặc quyền (được xác định là một quy trình có quyền truy cập vào một thứ mà miền SELinux unprivileged_app không có)
- Xử lý dữ liệu đầu vào không đáng tin cậy (Quy tắc 2)
- Tốc độ giảm hiệu suất chấp nhận được (tốc độ giảm không tạo ra độ trễ mà người dùng có thể nhận thấy)
Nhà cung cấp nên bật MTE trong quá trình sản xuất cho nhiều thành phần hơn, theo các tiêu chí nêu trên. Trong quá trình phát triển, bạn nên kiểm thử các thành phần này bằng chế độ SYNC để dễ dàng phát hiện các lỗi có thể khắc phục và đánh giá tác động của chế độ ASYNC đến hiệu suất của các thành phần.
Trong tương lai, Android dự định mở rộng danh sách các thành phần hệ thống mà MTE được bật, dựa trên đặc điểm hiệu suất của các thiết kế phần cứng sắp ra mắt.