Tệp nhị phân trợ giúp của OpenVPN hỏng trên Android 10: quyền bị từ chối
Android 10 từ chối chạy một trình trợ giúp mà ứng dụng đã ghi vào kho của chính nó. Đọc logcat, phân phối trình trợ giúp như thư viện nguyên bản và sửa các bản dựng bundle.
Ứng dụng chạy được trên Android 9 và chết trên cùng chiếc điện thoại sau bản cập nhật lên Android 10. Hồ sơ của bạn còn nguyên, chứng chỉ vẫn vậy, và sự cố đến trước mọi lưu lượng xác thực. Logcat trước tiên hiển thị một con trỏ null, và vài dòng phía trên là Cannot run program ...: error=13, Permission denied. Thông báo đó nói về việc chạy một tệp, không phải về việc đọc cấu hình của bạn.
Lỗi 13 thực sự từ chối điều gì
Android 10, cấp API 29, đã ngăn các ứng dụng chạy những tệp chúng ghi vào thư mục dữ liệu của chính mình. Quy tắc nhắm vào vùng lưu trữ ghi được trước rồi mới thực thi được, và đó chính là thứ mà một trình khởi chạy sao chép từ assets tạo ra. Các bản dựng cũ của nhân ics-openvpn giải nén một tệp thực thi openvpn từ APK ở lần chạy đầu và khởi động nó bằng ProcessBuilder. Việc giải nén vẫn thành công; thứ bị từ chối là việc thực thi. Một ứng dụng nhắm cấp API 29 vẫn thừa hưởng quy tắc này kể cả khi chạy trên thiết bị ra mắt với phiên bản cũ hơn, nên một bản cập nhật có thể làm hỏng bản dựng mà không ai đụng tới.
Không có gì trong việc từ chối là riêng cho một tên thư mục. Một tệp ứng dụng tạo dưới thư mục dữ liệu của nó bị coi là không thể thực thi bất kể đường dẫn, vì vậy getFilesDir(), getCacheDir() và một thư mục con tùy chỉnh đều hỏng theo cùng một cách.
Tách sự cố của trình trợ giúp khỏi vấn đề hồ sơ
Đọc nhật ký quanh thời điểm tiến trình khởi động thay vì cuộn từ trên xuống. Chữ ký cần tìm là dòng error=13, và dấu vết ngăn xếp phía trên thường nêu tên phương thức của trình khởi chạy đã gọi ProcessBuilder:
adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"Nếu trình trợ giúp chưa từng khởi động, bạn sẽ không thấy chút đầu ra nhật ký nào của OpenVPN: không thương lượng mật mã, không tuyến bị đẩy, không cấu hình DNS. Nếu những dòng đó vẫn xuất hiện và lỗi đến sau, tệp nhị phân đã chạy và vấn đề nằm ở hồ sơ, máy chủ hoặc lớp định tuyến. Chính sự phân biệt đó tiết kiệm rất nhiều thời gian mà nếu không sẽ dành cho việc sửa những tệp .ovpn vốn không hề có lỗi. Nó cũng cho bạn biết bản sửa nào trong hai bản dưới đây là thứ bạn thực sự cần.
Cũng hãy kiểm tra cấp API trên thiết bị bạn đang thử, không phải cấp trong cấu hình bản dựng. Một ảnh giả lập ở API 28 không tái hiện được gì, vì hạn chế chỉ cắn từ 29 trở lên. Đồng thời kiểm tra SDK đích trong tệp dựng: chính việc nâng nó lên là thứ kéo quy tắc này vào.
adb shell getprop ro.build.version.sdkVì sao chép tệp nhị phân đi nơi khác không giúp gì
Cách lách hiển nhiên, ghi trình trợ giúp vào một thư mục ghi được khác, hỏng vì cùng lý do bản gốc hỏng. Android vẫn cho phép thực thi các thư viện chia sẻ được cài vào thư mục thư viện nguyên bản của ứng dụng, bởi trình cài đặt đóng gói chúng chứ chúng không được ghi lúc chạy. Chính thư mục đó là nơi duy nhất trình khởi chạy nên trỏ tới. Bất cứ thứ gì bạn tự giải nén lúc chạy, dù gọi tên gì, vẫn nằm trong nhóm bị từ chối.
Bộ nhớ ngoài còn tệ hơn: nó được gắn với noexec, và một trình trợ giúp đặt ở đó không ứng dụng nào khởi động được. Xóa dữ liệu ứng dụng cũng không khôi phục hành vi cũ, vì lần chạy sau chỉ tạo lại đúng bản sao không thể thực thi đó.
Trỏ trình khởi chạy tới một thư viện đã đóng gói
Đổi tên trình trợ giúp thành tên thư viện chia sẻ và phân phối nó dưới jniLibs cho mọi ABI bạn hỗ trợ, rồi để trình khởi chạy trả về đường dẫn thư viện đã cài từ API 28 trở đi:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new File(context.getApplicationInfo().nativeLibraryDir,
"libovpnexec.so").getPath();
}Thư viện phải được giải nén lúc cài để đường dẫn đó chứa một tệp thật, nên đặt android:extractNativeLibs="true" trên phần tử <application>. Nếu bộ hợp nhất manifest mang vào giá trị khác, thêm tools:replace="android:extractNativeLibs" bên cạnh. tài liệu tham chiếu phần tử application ghi lại thuộc tính này điều khiển điều gì.
Coi chừng một biến cục bộ bị khai báo trùng khi bạn vá trình khởi chạy bằng tay. Mã nguồn cũ khai báo nativeAPI ở phần sau của cùng phương thức, và lần khai báo thứ hai là lỗi biên dịch chứ không phải lỗi lúc chạy, nên bản dựng hỏng nhanh và dễ thấy lỗi. Giữ danh sách ABI trong khối Gradle khớp với các thư mục bạn thêm vào.
Làm cho sản phẩm phát hành khớp với bản dựng thử
Một APK gỡ lỗi có thể dựng đường hầm trong khi bundle phát hành vẫn sụp, vì bundle đóng gói thư viện nguyên bản theo hình thức khác và có thể phục vụ chúng ở dạng nén từ kho lưu trữ. Nếu chỉ các bản dựng sinh ra từ bundle hỏng, hãy thêm dòng này vào gradle.properties và dựng lại:
android.bundle.enableUncompressedNativeLibs=falseSau đó xác nhận gói đã cài báo cáo đúng ABI bạn mong đợi, và lặp lại kiểm tra trên thiết bị 32 bit và 64 bit nếu bạn phát hành cả hai. Trình quản lý gói nêu tên ABI mà nó chọn, nên một thiết bị bị rơi về 32 bit xuất hiện ở đây chứ không phải trong báo cáo sự cố:
adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbiMột thư viện thiếu cho một ABI tái hiện lỗi quyền ban đầu chỉ trên thiết bị đó, vì thế một chiếc điện thoại thử thành công không phải bằng chứng. Phát hành cờ manifest, tên thư viện và thiết lập bundle cùng nhau; đổi một thứ và quên những thứ khác sẽ đưa bạn trở lại đúng sự cố đó. Cách sửa lâu dài là chuyển sang một bản phát hành nhân được duy trì thay vì mang bản vá mãi mãi.