GWP-ASan

GWP-ASan เป็นฟีเจอร์การจัดสรรหน่วยความจำของระบบที่จะช่วยค้นหาข้อบกพร่องการใช้งานหลังช่วงใช้ฟรี และบัฟเฟอร์ล้นฮีป ชื่อไม่เป็นทางการของเครื่องมือนี้คือตัวย่อแบบซ้ำซ้อน "GWP-ASan Will Provide Allocation SANity" GWP-ASan ไม่จำเป็นต้องใช้ซอร์สโค้ดหรือคอมไพล์อีกครั้ง (กล่าวคือ ทำงานกับรุ่นที่คอมไพล์ไว้ล่วงหน้า) และทำงานได้ทั้งในกระบวนการแบบ 32 บิตและ 64 บิต (แม้ว่าข้อขัดข้องแบบ 32 บิตจะมีข้อมูลการแก้ไขข้อบกพร่องน้อยกว่า) ซึ่งแตกต่างจาก HWASan หรือ Malloc Debug หัวข้อนี้จะอธิบายการดำเนินการที่คุณต้องทำเพื่อเปิดใช้ฟีเจอร์นี้ในแอป GWP-ASan พร้อมใช้งานในแอปที่กําหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ขึ้นไป

ภาพรวม

GWP-ASan จะเปิดใช้ในแอปพลิเคชันระบบและไฟล์ปฏิบัติการของแพลตฟอร์มบางรายการที่ระบบเลือกแบบสุ่มเมื่อเริ่มกระบวนการ (หรือเมื่อ zygote แยกตัว) เปิดใช้ GWP-ASan ในแอปของคุณเองเพื่อช่วยค้นหาข้อบกพร่องที่เกี่ยวข้องกับหน่วยความจำ และเตรียมแอปให้พร้อมรองรับ Memory Tagging Extension (MTE) ของ ARM กลไกการสุ่มตัวอย่างการจัดสรรยังเพิ่มความน่าเชื่อถือให้กับการค้นหาการเสียชีวิตด้วย

เมื่อเปิดใช้แล้ว 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 ในแอป ซึ่งรวมถึงการดำเนินการต่อไปนี้

    1. ระบบปฏิบัติการจะจอง RAM ไว้จำนวนหนึ่งสำหรับการดำเนินการ GWP-ASan ประมาณ 70 KiB สำหรับแต่ละกระบวนการที่ได้รับผลกระทบ (เปิดใช้ GWP-ASan หากแอปของคุณไม่ไวต่อการใช้งานหน่วยความจําที่เพิ่มขึ้นอย่างร้ายแรง)

    2. GWP-ASan จะขัดจังหวะการจัดสรรฮีปชุดย่อยที่เลือกแบบสุ่มและวางไว้ในภูมิภาคพิเศษที่ตรวจจับการละเมิดความปลอดภัยของหน่วยความจำได้อย่างน่าเชื่อถือ

    3. เมื่อมีการละเมิดความปลอดภัยของหน่วยความจำเกิดขึ้นในภูมิภาคพิเศษ GWP-ASan จะสิ้นสุดกระบวนการ

    4. 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 พื้นฐานดังนี้

  1. GWP-ASan แบบกู้คืนได้จะเปิดใช้เฉพาะในการเปิดแอปประมาณ 1% เท่านั้น ไม่ใช่การเปิดแอปพลิเคชันทุกครั้ง
  2. เมื่อตรวจพบข้อบกพร่อง heap-use-after-free หรือ heap-buffer-overflow ข้อบกพร่องนี้จะปรากฏในรายงานข้อขัดข้อง (Tombstone) รายงานข้อขัดข้องนี้พร้อมใช้งานผ่าน ActivityManager#getHistoricalProcessExitReasons API เช่นเดียวกับ GWP-ASan เวอร์ชันเดิม
  3. GWP-ASan แบบกู้คืนได้จะไม่ออกจากระบบหลังจากถ่ายโอนข้อมูลรายงานข้อขัดข้อง แต่จะให้หน่วยความจำเสียหายและแอปทำงานต่อไป แม้ว่ากระบวนการจะดำเนินต่อไปตามปกติ แต่ลักษณะการทํางานของแอปจะไม่ได้รับการระบุอีกต่อไป เนื่องจากการเสียหายของหน่วยความจํา แอปอาจขัดข้อง ณ จุดใดก็ได้ในอนาคต หรืออาจทํางานต่อไปโดยไม่ส่งผลกระทบต่อผู้ใช้
  4. GWP-ASan ที่กู้คืนได้จะปิดใช้งานหลังจากมีการถ่ายโอนข้อมูลรายงานข้อขัดข้อง ดังนั้น แอปจะได้รับรายงาน GWP-ASan ที่กู้คืนได้เพียง 1 ฉบับต่อการเปิดแอป 1 ครั้ง
  5. หากติดตั้งตัวแฮนเดิลสัญญาณที่กำหนดเองในแอป ระบบจะไม่เรียกใช้ตัวแฮนเดิลสัญญาณดังกล่าวสำหรับสัญญาณ SIGSEGV ที่บ่งบอกถึงข้อบกพร่อง GWP-ASan ที่แก้ไขได้

เนื่องจากข้อขัดข้องของ GWP-ASan ที่กู้คืนได้บ่งบอกถึงอินสแตนซ์จริงของหน่วยความจําที่เสียหายในอุปกรณ์ของผู้ใช้ปลายทาง เราจึงขอแนะนําอย่างยิ่งให้จัดลําดับความสําคัญและแก้ไขข้อบกพร่องที่ GWP-ASan ที่กู้คืนได้ระบุไว้

การสนับสนุนสำหรับนักพัฒนาซอฟต์แวร์

ส่วนเหล่านี้จะระบุปัญหาที่อาจเกิดขึ้นเมื่อใช้ GWP-ASan และวิธีจัดการปัญหา

ไม่มีร่องรอยการจัดสรร/ยกเลิกการจัดสรร

หากคุณกำลังวิเคราะห์ข้อขัดข้องแบบเนทีฟที่ดูเหมือนจะไม่มีเฟรมการจัดสรร/การจัดสรรใหม่ แสดงว่าแอปพลิเคชันของคุณอาจไม่มีเคอร์เซอร์เฟรม GWP-ASan ใช้เคอร์เซอร์เฟรมเพื่อบันทึกการติดตามการจัดสรรและการยกเลิกการจัดสรรเพื่อเหตุผลด้านประสิทธิภาพ และไม่สามารถยกเลิกการติดตามสแต็กหากไม่มีเคอร์เซอร์เฟรม

ตัวชี้เฟรมจะเปิดอยู่โดยค่าเริ่มต้นสำหรับอุปกรณ์ arm64 และปิดอยู่โดยค่าเริ่มต้นสำหรับอุปกรณ์ arm32 เนื่องจากแอปพลิเคชันไม่สามารถควบคุม libc ได้ GWP-ASan จึงไม่สามารถรวบรวมร่องรอยการจัดสรร/การจัดสรรหน่วยความจำคืนสำหรับไฟล์ปฏิบัติการหรือแอป 32 บิตได้ (โดยทั่วไป) แอปพลิเคชัน 64 บิตควรไม่สร้างขึ้นด้วย -fomit-frame-pointer เพื่อให้ GWP-ASan รวบรวมสแต็กเทรซการจัดสรรและการยกเลิกการจัดสรรได้

การจำลองการละเมิดความปลอดภัย

GWP-ASan ออกแบบมาเพื่อตรวจจับการละเมิดความปลอดภัยของหน่วยความจำฮีปในอุปกรณ์ของผู้ใช้ GWP-ASan จะระบุบริบทเกี่ยวกับข้อขัดข้องให้มากที่สุดเท่าที่จะทำได้ (การติดตามการเข้าถึงการละเมิด, สตริงสาเหตุ และการติดตามการจัดสรร/การจัดสรรหน่วยความจำใหม่) แต่อาจยังสรุปได้ยากว่าการละเมิดเกิดขึ้นได้อย่างไร ขออภัย เนื่องจากการตรวจหาข้อบกพร่องเป็นแบบสุ่ม รายงาน GWP-ASan จึงมักทำให้เกิดปัญหาในการจำลองบนอุปกรณ์เครื่องอื่น

ในกรณีเหล่านี้ หากข้อบกพร่องส่งผลต่ออุปกรณ์ 64 บิต คุณควรใช้ HWAddressSanitizer (HWASan) HWASan ตรวจหาการละเมิดความปลอดภัยของหน่วยความจำอย่างน่าเชื่อถือในสแต็ก กอง และตัวแปรส่วนกลาง การเรียกใช้แอปพลิเคชันด้วย GWASan อาจทําให้เกิดผลลัพธ์เดียวกันกับที่ 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 ได้ปรับปรุงรายงานโดยอัตโนมัติโดยให้ข้อมูลเกี่ยวกับประเภทข้อขัดข้อง ข้อมูลเมตาการจัดสรร และร่องรอยการติดตามการจัดสรรและการยกเลิกการจัดสรรที่เกี่ยวข้อง

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 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 ได้ที่การวินิจฉัยข้อขัดข้องของระบบ