GWP-ASan เป็นฟีเจอร์ตัวจัดสรรหน่วยความจำของระบบที่ช่วยค้นหาข้อบกพร่องของการใช้งานหลังช่วงใช้ฟรี (Use After Free) และบัฟเฟอร์ล้นฮีป (Heap Buffer Overflow) ชื่ออย่างไม่เป็นทางการคือคำย่อแบบเรียกซ้ำ "GWP-ASan Will Provide Allocation SANity" GWP-ASan ไม่จำเป็นต้องมีแหล่งที่มาหรือการคอมไพล์ใหม่ (กล่าวคือ ใช้ได้กับ Prebuilt) และใช้ได้กับการประมวลผลทั้งแบบ 32 บิตและ 64 บิต (แม้ว่าข้อขัดข้องแบบ 32 บิตจะมีข้อมูลการแก้ไขข้อบกพร่องน้อยกว่า) ซึ่งแตกต่างจาก HWASan หรือ Malloc Debug หัวข้อนี้จะอธิบายการดำเนินการที่คุณต้องทำเพื่อเปิดใช้ฟีเจอร์นี้ในแอปของคุณ GWP-ASan พร้อมใช้งานในแอปที่กำหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ขึ้นไป
ภาพรวม
GWP-ASan จะเปิดใช้ในแอปพลิเคชันระบบและแพลตฟอร์ม ที่เรียกใช้งานได้บางรายการซึ่งเลือกแบบสุ่มเมื่อเริ่มกระบวนการ (หรือเมื่อ zygote แยก) เปิดใช้ GWP-ASan ในแอปของคุณเองเพื่อช่วยค้นหาข้อบกพร่องที่เกี่ยวข้องกับหน่วยความจำ และเพื่อเตรียมแอปให้พร้อมสำหรับการรองรับ ARM Memory Tagging Extension (MTE) กลไกการสุ่มตัวอย่างการจัดสรรยังช่วยให้มั่นใจได้ว่าคำค้นหาที่ทำให้เกิดการหยุดทำงานจะมีความน่าเชื่อถือ
เมื่อเปิดใช้แล้ว GWP-ASan จะสกัดกั้นการจัดสรรฮีปชุดย่อยที่เลือกแบบสุ่ม และวางไว้ในรีเจียนพิเศษที่ตรวจจับข้อบกพร่องของการเสียหายของหน่วยความจำฮีป ที่ตรวจจับได้ยาก หากมีผู้ใช้มากพอ แม้จะมีอัตราการสุ่มตัวอย่างต่ำเช่นนี้ ก็จะพบข้อบกพร่องด้านความปลอดภัยของหน่วยความจำฮีปที่การทดสอบปกติไม่พบ ตัวอย่างเช่น GWP-ASan พบข้อบกพร่องจำนวนมากในเบราว์เซอร์ Chrome (ซึ่งหลายรายการยังอยู่ภายใต้การดูแบบจำกัด)
GWP-ASan จะรวบรวมข้อมูลเพิ่มเติมเกี่ยวกับการจัดสรรทั้งหมดที่ สกัดกั้น ข้อมูลนี้จะพร้อมใช้งานเมื่อ GWP-ASan ตรวจพบการละเมิดความปลอดภัยของหน่วยความจำ และจะอยู่ในรายงานข้อขัดข้องของระบบโดยอัตโนมัติ ซึ่งจะช่วยในการแก้ไขข้อบกพร่องได้อย่างมาก (ดูตัวอย่าง)
GWP-ASan ได้รับการออกแบบมาเพื่อไม่ให้เกิดค่าใช้จ่าย CPU ที่สำคัญ GWP-ASan จะเพิ่มค่าใช้จ่าย RAM คงที่เล็กน้อยเมื่อเปิดใช้ ค่าใช้จ่ายนี้กำหนดโดยระบบ Android และปัจจุบันอยู่ที่ประมาณ 70 กิบิไบต์ (KiB) สำหรับแต่ละกระบวนการที่ได้รับผลกระทบ
เลือกใช้แอป
แอปอาจเปิดใช้ GWP-ASan ในระดับต่อกระบวนการได้โดยใช้แท็ก
android:gwpAsanMode
ในไฟล์ Manifest ของแอป โดยมีตัวเลือกต่อไปนี้ที่
รองรับ
ปิดใช้เสมอ (
android:gwpAsanMode="never"
): การตั้งค่านี้จะปิดใช้ GWP-ASan ในแอปของคุณโดยสมบูรณ์ และเป็นค่าเริ่มต้นสำหรับแอปที่ไม่ใช่ของระบบค่าเริ่มต้น (
android:gwpAsanMode="default"
หรือไม่ได้ระบุ): Android 13 (API ระดับ 33) และต่ำกว่า - ปิดใช้ GWP-ASan Android 14 (API ระดับ 34) ขึ้นไป - เปิดใช้ GWP-ASan ที่กู้คืนได้เปิดใช้เสมอ (
android:gwpAsanMode="always"
): การตั้งค่านี้จะเปิดใช้ GWP-ASan ในแอป ซึ่งรวมถึงรายการต่อไปนี้ระบบปฏิบัติการจะสงวน RAM จำนวนคงที่ไว้สำหรับการดำเนินการ GWP-ASan โดยประมาณ ~70KiB สำหรับแต่ละกระบวนการที่ได้รับผลกระทบ (เปิดใช้ GWP-ASan หากแอปของคุณไม่ได้มีความละเอียดอ่อนอย่างยิ่งต่อการเพิ่มการใช้หน่วยความจำ)
GWP-ASan จะสกัดกั้นการจัดสรรฮีปชุดย่อยที่เลือกแบบสุ่มและ วางไว้ในภูมิภาคพิเศษที่ตรวจหาการละเมิดความปลอดภัยของหน่วยความจำได้อย่างน่าเชื่อถือ
เมื่อเกิดการละเมิดความปลอดภัยของหน่วยความจำในรีเจียนพิเศษ GWP-ASan จะสิ้นสุดกระบวนการ
GWP-ASan ให้ข้อมูลเพิ่มเติมเกี่ยวกับข้อบกพร่องในรายงานข้อขัดข้อง
หากต้องการเปิดใช้ GWP-ASan ทั่วโลกสำหรับแอป ให้เพิ่มข้อมูลต่อไปนี้ลงในไฟล์
AndroidManifest.xml
<application android:gwpAsanMode="always"> ... </application>
นอกจากนี้ คุณยังเปิดหรือปิดใช้ GWP-ASan อย่างชัดเจนสำหรับ กระบวนการย่อยที่เฉพาะเจาะจงของแอปได้ด้วย คุณสามารถกำหนดเป้าหมายกิจกรรมและบริการโดยใช้กระบวนการ ที่เลือกใช้หรือเลือกไม่ใช้ GWP-ASan อย่างชัดเจน ดูตัวอย่างได้ที่ด้านล่าง
<application> <processes> <!-- Create the (empty) application process --> <process /> <!-- Create subprocesses with GWP-ASan both explicitly enabled and disabled. --> <process android:process=":gwp_asan_enabled" android:gwpAsanMode="always" /> <process android:process=":gwp_asan_disabled" android:gwpAsanMode="never" /> </processes> <!-- Target services and activities to be run on either the GWP-ASan enabled or disabled processes. --> <activity android:name="android.gwpasan.GwpAsanEnabledActivity" android:process=":gwp_asan_enabled" /> <activity android:name="android.gwpasan.GwpAsanDisabledActivity" android:process=":gwp_asan_disabled" /> <service android:name="android.gwpasan.GwpAsanEnabledService" android:process=":gwp_asan_enabled" /> <service android:name="android.gwpasan.GwpAsanDisabledService" android:process=":gwp_asan_disabled" /> </application>
GWP-ASan ที่กู้คืนได้
Android 14 (API ระดับ 34) ขึ้นไปรองรับ GWP-ASan ที่กู้คืนได้ ซึ่งจะช่วย
นักพัฒนาแอปค้นหาข้อบกพร่องของบัฟเฟอร์ล้นฮีป (Heap Buffer Overflow) และการใช้งานฮีปหลังช่วงใช้ฟรี (Heap Use After Free) ใน
เวอร์ชันที่ใช้งานจริงโดยไม่ลดประสบการณ์ของผู้ใช้ เมื่อไม่ได้ระบุ android:gwpAsanMode
ใน AndroidManifest.xml
แอปจะใช้ GWP-ASan ที่กู้คืนได้
GWP-ASan ที่กู้คืนได้แตกต่างจาก GWP-ASan พื้นฐานในลักษณะต่อไปนี้
- GWP-ASan ที่กู้คืนได้จะเปิดใช้ในการเปิดแอปประมาณ 1% เท่านั้น แทนที่จะเป็นการเปิดแอปทุกครั้ง
- เมื่อตรวจพบข้อบกพร่อง heap-use-after-free หรือ heap-buffer-overflow ข้อบกพร่องนี้
จะปรากฏในรายงานข้อขัดข้อง (tombstone) รายงานข้อขัดข้องนี้พร้อมใช้งานผ่าน
ActivityManager#getHistoricalProcessExitReasons
API เช่นเดียวกับ GWP-ASan เดิม - Recoverable GWP-ASan อนุญาตให้เกิดการเสียหายของหน่วยความจําและแอปทํางานต่อไปแทนที่จะออกหลังจากทิ้งรายงานข้อขัดข้อง แม้ว่ากระบวนการอาจดำเนินต่อไปตามปกติ แต่ลักษณะการทำงานของแอปจะไม่ได้รับการระบุอีกต่อไป เนื่องจากหน่วยความจำเสียหาย แอปอาจขัดข้องในอนาคต ณ จุดใดก็ได้ หรืออาจทำงานต่อไปโดยไม่มีผลกระทบที่ผู้ใช้มองเห็น
- ระบบจะปิดใช้ GWP-ASan ที่กู้คืนได้หลังจากที่ทิ้งรายงานข้อขัดข้อง ดังนั้น แอปจะรับรายงาน GWP-ASan ที่กู้คืนได้เพียงรายการเดียวต่อการเปิดแอป 1 ครั้ง
- หากมีการติดตั้งตัวแฮนเดิลสัญญาณที่กำหนดเองในแอป ระบบจะไม่เรียกใช้ตัวแฮนเดิลสำหรับสัญญาณ SIGSEGV ที่บ่งบอกถึงข้อบกพร่อง GWP-ASan ที่กู้คืนได้
เนื่องจากข้อขัดข้องของ GWP-ASan ที่กู้คืนได้บ่งบอกถึงอินสแตนซ์จริงของการเสียหายของหน่วยความจำในอุปกรณ์ของผู้ใช้ปลายทาง เราจึงขอแนะนำอย่างยิ่งให้จัดลำดับความสำคัญและแก้ไขข้อบกพร่องที่ระบุโดย GWP-ASan ที่กู้คืนได้โดยให้ความสำคัญสูง
การสนับสนุนสำหรับนักพัฒนาซอฟต์แวร์
ส่วนเหล่านี้จะอธิบายปัญหาที่อาจเกิดขึ้นเมื่อใช้ GWP-ASan และวิธี แก้ไขปัญหา
ไม่มีร่องรอยการจัดสรร/ยกเลิกการจัดสรร
หากคุณกำลังวินิจฉัยข้อขัดข้องของเนทีฟที่ดูเหมือนว่าไม่มีเฟรมการจัดสรร/การยกเลิกการจัดสรร แสดงว่าแอปพลิเคชันของคุณอาจไม่มีตัวชี้เฟรม GWP-ASan ใช้ตัวชี้เฟรมเพื่อบันทึกร่องรอยการจัดสรรและการยกเลิกการจัดสรรด้วยเหตุผลด้านประสิทธิภาพ และไม่สามารถคลายการติดตามสแต็กได้หากไม่มีตัวชี้เฟรม
ตัวชี้เฟรมจะเปิดอยู่โดยค่าเริ่มต้นสำหรับอุปกรณ์ arm64 และปิดอยู่โดยค่าเริ่มต้นสำหรับอุปกรณ์ arm32
เนื่องจากแอปพลิเคชันไม่มีสิทธิ์ควบคุม libc โดยทั่วไป GWP-ASan จึงไม่สามารถรวบรวมร่องรอยการจัดสรร/การยกเลิกการจัดสรรสำหรับไฟล์ปฏิบัติการหรือแอป 32 บิต แอปพลิเคชัน 64 บิตควรตรวจสอบว่าไม่ได้สร้างด้วย -fomit-frame-pointer
เพื่อให้ GWP-ASan รวบรวมการจัดสรรและ
การยกเลิกการจัดสรร Stack Trace ได้
การจำลองการละเมิดนโยบายด้านความปลอดภัย
GWP-ASan ออกแบบมาเพื่อตรวจจับการละเมิดความปลอดภัยของหน่วยความจำฮีปในอุปกรณ์ของผู้ใช้ GWP-ASan จะให้บริบทเกี่ยวกับข้อขัดข้องมากที่สุดเท่าที่จะเป็นไปได้ (การติดตามการเข้าถึงของการละเมิด สตริงสาเหตุ และการติดตามการจัดสรร/การยกเลิกการจัดสรร) แต่อาจยังคงยากที่จะสรุปว่าการละเมิดเกิดขึ้นได้อย่างไร ขออภัย เนื่องจากข้อบกพร่อง การตรวจหาเป็นแบบอิงความน่าจะเป็น รายงาน GWP-ASan จึงมักจะทำซ้ำในอุปกรณ์ เครื่องได้ยาก
ในกรณีเหล่านี้ หากข้อบกพร่องส่งผลต่ออุปกรณ์ 64 บิต คุณควรใช้ HWAddressSanitizer (HWASan) HWASan ตรวจพบการละเมิดความปลอดภัยของหน่วยความจำ ได้อย่างน่าเชื่อถือในสแต็ก ฮีป และตัวแปรส่วนกลาง การเรียกใช้แอปพลิเคชันด้วย HWASan อาจสร้างผลลัพธ์เดียวกันกับที่รายงานโดย GWP-ASan ได้อย่างน่าเชื่อถือ
ในกรณีที่การเรียกใช้แอปพลิเคชันภายใต้ HWASan ไม่เพียงพอต่อการ หาสาเหตุที่แท้จริงของข้อบกพร่อง คุณควรลองฟัซโค้ด ที่เป็นปัญหา คุณกำหนดเป้าหมายการฟัซได้โดยอิงตามข้อมูลในรายงาน GWP-ASan ซึ่งตรวจหาและเปิดเผยปัญหาด้านความสมบูรณ์ของโค้ดพื้นฐานได้อย่างน่าเชื่อถือ
ตัวอย่าง
โค้ดแบบเนทีฟตัวอย่างนี้มีข้อบกพร่องในการใช้ฮีปหลังจากปล่อย
#include <jni.h>
#include <string>
#include <string_view>
jstring native_get_string(JNIEnv* env) {
std::string s = "Hellooooooooooooooo ";
std::string_view sv = s + "World\n";
// BUG: Use-after-free. `sv` holds a dangling reference to the ephemeral
// string created by `s + "World\n"`. Accessing the data here is a
// use-after-free.
return env->NewStringUTF(sv.data());
}
extern "C" JNIEXPORT jstring JNICALL
Java_android11_test_gwpasan_MainActivity_nativeGetString(
JNIEnv* env, jobject /* this */) {
// Repeat the buggy code a few thousand times. GWP-ASan has a small chance
// of detecting the use-after-free every time it happens. A single user who
// triggers the use-after-free thousands of times will catch the bug once.
// Alternatively, if a few thousand users each trigger the bug a single time,
// you'll also get one report (this is the assumed model).
jstring return_string;
for (unsigned i = 0; i < 0x10000; ++i) {
return_string = native_get_string(env);
}
return reinterpret_cast<jstring>(env->NewGlobalRef(return_string));
}
สำหรับการทดสอบโดยใช้ตัวอย่างโค้ดด้านบน GWP-ASan ตรวจพบการใช้งานที่ไม่ถูกต้องและทริกเกอร์รายงานข้อขัดข้องด้านล่างได้สำเร็จ GWP-ASan ได้ปรับปรุงรายงานโดยอัตโนมัติ ด้วยการให้ข้อมูลเกี่ยวกับประเภทข้อขัดข้อง ข้อมูลเมตาการจัดสรร และการจัดสรรที่เกี่ยวข้องและการยกเลิกการจัดสรร Stack Trace
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sargo/sargo:10/RPP3.200320.009/6360804:userdebug/dev-keys'
Revision: 'PVT1.0'
ABI: 'arm64'
Timestamp: 2020-04-06 18:27:08-0700
pid: 16227, tid: 16227, name: 11.test.gwpasan >>> android11.test.gwpasan <<<
uid: 10238
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x736ad4afe0
Cause: [GWP-ASan]: Use After Free on a 32-byte allocation at 0x736ad4afe0
backtrace:
#00 pc 000000000037a090 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckNonHeapValue(char, art::(anonymous namespace)::JniValueType)+448)
#01 pc 0000000000378440 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::(anonymous namespace)::JniValueType)+204)
#02 pc 0000000000377bec /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*)+612)
#03 pc 000000000036dcf4 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*)+708)
#04 pc 000000000000eda4 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (_JNIEnv::NewStringUTF(char const*)+40)
#05 pc 000000000000eab8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+144)
#06 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
...
deallocated by thread 16227:
#00 pc 0000000000048970 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
#01 pc 0000000000048f30 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::deallocate(void*)+184)
#02 pc 000000000000f130 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::_DeallocateCaller::__do_call(void*)+20)
...
#08 pc 000000000000ed6c /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::~basic_string()+100)
#09 pc 000000000000ea90 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+104)
#10 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
...
allocated by thread 16227:
#00 pc 0000000000048970 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
#01 pc 0000000000048e4c /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::allocate(unsigned long)+368)
#02 pc 000000000003b258 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan_malloc(unsigned long)+132)
#03 pc 000000000003bbec /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
#04 pc 0000000000010414 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (operator new(unsigned long)+24)
...
#10 pc 000000000000ea6c /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+68)
#11 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
...
ข้อมูลเพิ่มเติม
ดูรายละเอียดการติดตั้งใช้งาน GWP-ASan เพิ่มเติมได้ในเอกสารประกอบของ LLVM ดูข้อมูลเพิ่มเติมเกี่ยวกับรายงานข้อขัดข้องของระบบ Android ได้ที่การวินิจฉัยข้อขัดข้องของระบบ