ไบนารีตัวช่วยของ OpenVPN ล้มเหลวบน Android 10: สิทธิ์ถูกปฏิเสธ
Android 10 ปฏิเสธที่จะรันตัวช่วยที่แอปเขียนลงในที่เก็บของตัวเอง อ่าน logcat ส่งตัวช่วยเป็นไลบรารีเนทีฟ และแก้ไขบิลด์ bundle
แอปทำงานได้บน Android 9 และตายบนโทรศัพท์เครื่องเดิมหลังอัปเดตเป็น Android 10 โปรไฟล์ของคุณไม่ถูกแตะต้อง ใบรับรองยังเหมือนเดิม และการแครชเกิดก่อนทราฟฟิกการยืนยันตัวตนใด ๆ logcat แสดงพอยน์เตอร์ว่างก่อน และไม่กี่บรรทัดเหนือขึ้นไปมี Cannot run program ...: error=13, Permission denied ข้อความนั้นพูดถึงการรันไฟล์ ไม่ใช่การอ่านคอนฟิกของคุณ
สิ่งที่ข้อผิดพลาด 13 ปฏิเสธจริง ๆ
Android 10 ระดับ API 29 หยุดไม่ให้แอปรันไฟล์ที่ตัวเองเขียนลงไดเรกทอรีข้อมูลของตัวเอง กฎนี้เล็งไปที่ที่เก็บที่เขียนได้แล้วจึงรันได้ ซึ่งเป็นสิ่งที่ตัวเรียกที่คัดลอกจาก assets สร้างขึ้นพอดี บิลด์เก่าของแกน ics-openvpn จะแตกไฟล์ที่รันได้ openvpn ออกจาก APK ในการรันครั้งแรกและเริ่มมันด้วย ProcessBuilder การแตกไฟล์ยังสำเร็จอยู่ สิ่งที่ถูกปฏิเสธคือการรัน แอปที่เล็ง API 29 สืบทอดกฎนี้แม้จะทำงานบนอุปกรณ์ที่ออกมาพร้อมเวอร์ชันเก่ากว่า จึงทำให้การอัปเดตหนึ่งครั้งทำลายบิลด์ที่ไม่มีใครแตะต้องได้
ไม่มีอะไรในการปฏิเสธที่เฉพาะเจาะจงกับชื่อโฟลเดอร์ใดโฟลเดอร์หนึ่ง ไฟล์ที่แอปสร้างขึ้นใต้ไดเรกทอรีข้อมูลของมันถูกถือว่ารันไม่ได้ไม่ว่าเส้นทางจะเป็นอย่างไร ดังนั้น getFilesDir(), getCacheDir() และไดเรกทอรีย่อยที่กำหนดเองก็ล้มเหลวเหมือนกันทั้งหมด
แยกการแครชของตัวช่วยออกจากปัญหาโปรไฟล์
อ่านล็อกรอบ ๆ การเริ่มโปรเซสแทนการเลื่อนจากด้านบน ลายเซ็นที่ต้องค้นหาคือบรรทัด error=13 และสแต็กเทรซด้านบนมักระบุชื่อเมธอดของตัวเรียกที่เรียก ProcessBuilder:
adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"ถ้าตัวช่วยไม่เคยเริ่มเลย คุณจะไม่เห็นเอาต์พุตล็อกของ OpenVPN เลย ไม่มีการเจรจาไซเฟอร์ ไม่มีเส้นทางที่ถูกดัน ไม่มีการตั้งค่า DNS ถ้าบรรทัดเหล่านั้นปรากฏและความล้มเหลวมาทีหลัง ไบนารีทำงานแล้วและปัญหาอยู่ที่โปรไฟล์ เซิร์ฟเวอร์ หรือชั้นการกำหนดเส้นทาง ความแตกต่างเพียงข้อเดียวนี้ประหยัดเวลามหาศาลที่จะเสียไปกับการแก้ไขไฟล์ .ovpn ที่ไม่เคยเป็นความผิดเลย มันยังบอกด้วยว่าต้องการการแก้ไขใดจากสองข้อด้านล่างจริง ๆ
ตรวจสอบระดับ API บนอุปกรณ์ที่คุณทดสอบด้วย ไม่ใช่ระดับในคอนฟิกบิลด์ของคุณ อิมเมจอีมูเลเตอร์บน API 28 ไม่จำลองอะไรเลย เพราะข้อจำกัดนี้กัดตั้งแต่ 29 ขึ้นไปเท่านั้น ตรวจสอบ SDK เป้าหมายในไฟล์บิลด์ของคุณพร้อมกัน การยกระดับมันคือสิ่งที่ดึงกฎนี้เข้ามา
adb shell getprop ro.build.version.sdkทำไมการคัดลอกไบนารีไปที่อื่นจึงไม่ช่วย
ทางอ้อมที่เห็นได้ชัด คือการเขียนตัวช่วยลงไดเรกทอรีอื่นที่เขียนได้ ล้มเหลวด้วยเหตุผลเดียวกับต้นฉบับ Android ยังอนุญาตให้รันไลบรารีที่ใช้ร่วมกันซึ่งติดตั้งไว้ในไดเรกทอรีไลบรารีเนทีฟของแอป เพราะตัวติดตั้งเป็นผู้แพ็กเกจ ไม่ใช่เขียนตอนรัน ไดเรกทอรีนั้นคือที่เดียวที่ตัวเรียกควรชี้ไป สิ่งใดก็ตามที่คุณแตกไฟล์เองตอนรัน ไม่ว่าจะเรียกชื่ออะไร ก็ยังอยู่ในหมวดที่ถูกปฏิเสธ
ที่เก็บภายนอกแย่กว่า เพราะเมานต์ด้วย noexec และตัวช่วยที่วางไว้ที่นั่นไม่สามารถเริ่มได้โดยแอปใด ๆ การล้างข้อมูลแอปก็ไม่คืนพฤติกรรมเดิม เพราะการรันครั้งถัดไปเพียงสร้างสำเนาที่รันไม่ได้เดิมขึ้นใหม่
ชี้ตัวเรียกไปที่ไลบรารีที่แพ็กเกจแล้ว
เปลี่ยนชื่อตัวช่วยเป็นชื่อไลบรารีที่ใช้ร่วมกันและส่งมอบภายใต้ jniLibs สำหรับทุก ABI ที่คุณรองรับ จากนั้นให้ตัวเรียกคืนเส้นทางของไลบรารีที่ติดตั้งตั้งแต่ API 28 เป็นต้นไป:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new File(context.getApplicationInfo().nativeLibraryDir,
"libovpnexec.so").getPath();
}ไลบรารีต้องถูกแตกออกตอนติดตั้งเพื่อให้เส้นทางนั้นมีไฟล์จริง ดังนั้นตั้งค่า android:extractNativeLibs="true" บนอิลิเมนต์ <application> ถ้าตัวรวมแมนิเฟสต์นำค่าอื่นเข้ามา ให้เพิ่ม tools:replace="android:extractNativeLibs" ข้าง ๆ มัน เอกสารอ้างอิงอิลิเมนต์ application อธิบายว่าแอตทริบิวต์นี้ควบคุมอะไร
ระวังตัวแปรโลคัลที่ซ้ำกันเมื่อคุณแพตช์ตัวเรียกด้วยมือ ซอร์สเก่าประกาศ nativeAPI ในภายหลังในเมธอดเดียวกัน และการประกาศครั้งที่สองเป็นข้อผิดพลาดตอนคอมไพล์ไม่ใช่ตอนรัน บิลด์จึงล้มเหลวอย่างรวดเร็วและเห็นข้อผิดพลาดได้ง่าย รักษารายการ ABI ในบล็อก Gradle ให้ตรงกับโฟลเดอร์ที่คุณเพิ่ม
ทำให้อาร์ติแฟกต์รีลีสตรงกับบิลด์ทดสอบ
APK ดีบักสามารถยกอุโมงค์ได้ในขณะที่ bundle รีลีสยังแครชอยู่ เพราะ bundle แพ็กไลบรารีเนทีฟในรูปแบบที่ต่างออกไปและอาจเสิร์ฟแบบบีบอัดจากอาร์ไคฟ์ ถ้าเฉพาะบิลด์ที่มาจาก bundle ล้มเหลว ให้เพิ่มสิ่งนี้ใน gradle.properties แล้วบิลด์ใหม่:
android.bundle.enableUncompressedNativeLibs=falseจากนั้นยืนยันว่าแพ็กเกจที่ติดตั้งรายงาน ABI ที่คุณคาดไว้ และทำการตรวจซ้ำบนอุปกรณ์ 32 บิตและ 64 บิตหากคุณส่งทั้งสองแบบ ตัวจัดการแพ็กเกจระบุ ABI ที่มันเลือก ดังนั้นอุปกรณ์ที่ถอยกลับไป 32 บิตจะปรากฏที่นี่ไม่ใช่ในรายงานการแครช:
adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbiไลบรารีที่หายไปสำหรับ ABI หนึ่งจะสร้างข้อผิดพลาดสิทธิ์เดิมขึ้นใหม่เฉพาะบนอุปกรณ์นั้น ดังนั้นโทรศัพท์ทดสอบที่สำเร็จเครื่องเดียวจึงไม่ใช่หลักฐาน การส่งแฟล็กแมนิเฟสต์ ชื่อไลบรารี และการตั้งค่า bundle ไปด้วยกัน การเปลี่ยนอย่างหนึ่งแล้วลืมอย่างอื่นจะพาคุณกลับไปยังการแครชเดิม การแก้ไขที่ยั่งยืนคือย้ายไปยังรีลีสแกนที่ได้รับการดูแลแทนการแบกแพตช์ไปตลอดกาล