Всем привет! Как многие знают, с Android 10 (Target sdk 29) google ввели новую политику безопасности. Новая политика SELinux звучит просто: «Нельзя исполнять файлы из той директории, в которую можно записывать». Всё это очень хорошо, но многие проекты сломались (В том числе и мой). Termux из google play УМЕЕТ запускать бинарные файлы на target sdk 29+. Я решил поделится, как выполнить бинарный файл из data/data/com.ваш.пакет/files на новых версиях sdk БЕЗ полного клонирования Termux и БЕЗ С/C++ части. Сам метод запуска будет именно на java. Репозиторий termux, откуда был взят способ: https://github.com/termux‑play‑store/termux‑apps.
В чём суть: любой бинарный файл, который вы запускаете, имеет свой контекст. Если вы запускаете через нативную директорию (data/app/и так далее), перед этим положив бинарные файлы в jniLins — контекст у такого бинарного файла будет правильным, и SELinux даст разрешение на запуск (Granted), но в случае с data/data другой случай, оттуда SELinux УЖЕ ОТКАЖЕТСЯ запускать бинарный файл (Denied). Разрешил SELinux запуск или отклонил — можно смотреть в logcat. Однако, в системе существует системная утилита, которая может запустить бинарник, а самое главное — SELinux РАЗРЕШИТ ей запуститься, так как она системная. Её имя linker или linker64 (Зависит от разрядности, 32 бита или 64).
Запустив линкер и передав ему наш бинарный файл из data/data — SELinux разрешит ему выполниться и сразу разрешит исполнение нашего бинарного файла. Тут сразу возникает вопрос: а если бинарный файл попробует подключить so библиотеку? Ей будет отказано? Здесь тоже есть решение: существует termux‑exec. Это бинарный файл, который перехватывает попытку подключения чего‑либо и выполняет трюк с линкером. (linker или linker64 определяет автоматически). Вы можете собрать его из исходников (https://github.com/termux‑play‑store/termux‑exec), но лично я полностью скопировал среду (Где этот уже собранный файл идёт в usr/lib) termux, так как мне нужно было запускать OpenJDK 17 под termux.
Пишем сам метод.
// Устаналиваем пути
File appFilesDir = context.getFilesDir(); // Путь к data/data/пакет/files/, нужен контекст, передайте его
File usrDir = new File(appFilesDir, "usr"); // Путь к data/data/пакет/files/usr/
File javaExecutable = new File(appFilesDir, "usr/bin/java"); // ЗАМЕНИТЕ НА ВАШ БИНАРНИК
// Создаём окружение чтобы бинарник для термукса чувствовал себя как дома
Map<String, String> envMap = new HashMap<>();
// Сам дом
envMap.put("HOME", new File(appFilesDir, "home").getAbsolutePath());
// Папка пользователя
envMap.put("PREFIX", usrDir.getAbsolutePath());
// Временнаяя папка
envMap.put("TMPDIR", new File(appFilesDir, "tmp").getAbsolutePath());
// Путь к бинарникам
envMap.put("PATH", new File(usrDir, "bin").getAbsolutePath() + ":" + System.getenv("PATH"));
// Папка для динамичкских библиотек
File libDir = new File(usrDir, "lib");
envMap.put("LD_LIBRARY_PATH", libDir.getAbsolutePath());
// Наш перехватчик (Его нужно собрать либо взять пряом из термукса)
File termuxExecSo = new File(libDir, "libtermux-exec.so");
envMap.put("LD_PRELOAD", termuxExecSo.getAbsolutePath());
// Системные переменные которые ВОЗМОЖНО будут нужны бинарнику, лучше их задать
String[] systemVarsToCopy = {
"ANDROID_ART_ROOT", "ANDROID_ASSETS", "ANDROID_DATA", "ANDROID_I18N_ROOT",
"ANDROID_ROOT", "ANDROID_RUNTIME_ROOT", "ANDROID_STORAGE", "ANDROID_TZDATA_ROOT",
"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "EXTERNAL_STORAGE", "SYSTEMSERVERCLASSPATH"
};
for (String var : systemVarsToCopy) {
String value = System.getenv(var);
if (value != null) {
envMap.put(var, value);
}
}
// Формируем команду
List<String> commandList = new ArrayList<>();
// Определяем, нам нужен linker или linker64 (Это зависит от архитектуры процессора)
String linkerPath = "/system/bin/linker" + (android.os.Process.is64Bit() ? "64" : "");
// Заполняем саму команду
commandList.add(linkerPath); // Запускаем линкер, а не бинарник. Это самый важный этап
commandList.add(javaExecutable.getAbsolutePath()); // Ваш бинарник, у меня это java из OpenJDK
commandList.add("-version"); // Ваши аргументы, у меня всего 1 аргумент чтобы узнать версию
Всё! Мы построили команду для запуска на новых targert sdk. Теперь осталось запустить этот процесс
Пример запуска (В try‑catch), если у вас простой бинарник — вам достаточно этого. Для более сложных сделайте получение вывода. Информация об этом есть в интернете.
// Создаём ProcessBuilder с нашей командой для линкера
ProcessBuilder pb = new ProcessBuilder(commandList);
// Устаналиваем окружение
pb.environment().putAll(envMap);
// Запускаем и ждём завершения
pb.start().waitFor();
Дальше идёт обычная работа с процессами, никакой «Магии» дальше нет. Вывод вы можете организовать самостоятельно, главное — проверить, что бинарный файл запущен.
Вот, что мы видим в logcat:

Когда я добавил вывод — я получил вывод версии OpenJDK. Пользуйтесь. Работает даже на target sdk 36. Сам вывод никак не относится к теме статьи, она рассказывает про запуск бинарных файлов из директорий, куда можно записывать на новых версиях sdk.
Urub
Хорошо бы к статьям о программировании делать минимально рабочий git репозитарий с демонстрацией решаемой проблемы