Skip to content

Commit

Permalink
[Settings] Add a fallback server runner command from DE storage
Browse files Browse the repository at this point in the history
Added a fallback server runner command in case SD card is inaccessible from
the UID from which the script is being run. This leverages the device
encrypted storage (data_de) by making certain folder and files accessibly
globally.

Even this workaround may not work in some devices, especially Android 12+
ROM where a better protection has been introduced.

Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
  • Loading branch information
MuntashirAkon committed Apr 22, 2024
1 parent 6e7f32d commit 4f97928
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.text.TextUtils;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;

import org.jetbrains.annotations.NotNull;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
Expand All @@ -18,14 +18,10 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.SecureRandom;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.server.common.ConfigParams;
import io.github.muntashirakon.AppManager.server.common.Constants;
import io.github.muntashirakon.AppManager.utils.ContextUtils;
import io.github.muntashirakon.AppManager.utils.FileUtils;
import io.github.muntashirakon.io.IoUtils;

// Copyright 2016 Zheng Li
Expand Down Expand Up @@ -57,66 +53,47 @@ public static void copyFile(@NonNull Context context, String fileName, File dest
fos.flush();
fos.getFD().sync();
}
FileUtils.chmod644(destFile);
}
}

@WorkerThread
static void writeScript(@NonNull Context context) throws IOException {
try (AssetFileDescriptor openFd = context.getAssets().openFd(ServerConfig.EXECUTABLE_FILE_NAME);
FileInputStream fdInputStream = openFd.createInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(fdInputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
File destFile = ServerConfig.getExecPath();
static void writeServerExecScript(@NonNull Context context, @NonNull File destFile, @NonNull String classPath) throws IOException {
try (AssetFileDescriptor openFd = context.getAssets().openFd(ServerConfig.SERVER_RUNNER_EXEC_NAME);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(openFd.createInputStream()))) {
if (destFile.exists()) {
destFile.delete();
}
StringBuilder sb = new StringBuilder();
sb.append(',').append(ConfigParams.PARAM_APP).append(':').append(BuildConfig.APPLICATION_ID);

if (ServerConfig.getAllowBgRunning()) {
sb.append(',').append(ConfigParams.PARAM_RUN_IN_BACKGROUND).append(':').append(1);
}
if (BuildConfig.DEBUG) {
sb.append(',').append(ConfigParams.PARAM_DEBUG).append(':').append(1);
}

String classpath = ServerConfig.getClassPath();
String args = sb.toString();

try (BufferedWriter bw = new BufferedWriter(new FileWriter(destFile, false))) {
// Set variables
StringBuilder script = new StringBuilder();
script.append("SERVER_NAME=").append(Constants.SERVER_NAME).append("\n")
.append("JAR_NAME=").append(Constants.JAR_NAME).append("\n")
.append("JAR_PATH=").append(classpath).append("\n")
.append("ARGS=").append(args).append("\n");
String line = bufferedReader.readLine();
while (line != null) {
.append("JAR_PATH=").append(classPath).append("\n")
.append("ARGS=").append(getServerArgs()).append("\n");
String line;
while ((line = bufferedReader.readLine()) != null) {
String wl;
if ("%ENV_VARS%".equals(line.trim())) {
wl = script.toString();
} else wl = line;
bw.write(wl);
bw.newLine();
line = bufferedReader.readLine();
}
bw.flush();
}
FileUtils.chmod644(destFile);
}
}

@AnyThread
@NonNull
static String generateToken() {
Context context = ContextUtils.getContext();
String[] wordList = context.getResources().getStringArray(R.array.word_list);
SecureRandom secureRandom = new SecureRandom();
String[] tokenItems = new String[3 + secureRandom.nextInt(3)];
for (int i = 0; i < tokenItems.length; ++i) {
tokenItems[i] = wordList[secureRandom.nextInt(wordList.length)];
@NotNull
private static String getServerArgs() {
StringBuilder argsBuilder = new StringBuilder();
argsBuilder.append(',').append(ConfigParams.PARAM_APP).append(':').append(BuildConfig.APPLICATION_ID);
if (ServerConfig.getAllowBgRunning()) {
argsBuilder.append(',').append(ConfigParams.PARAM_RUN_IN_BACKGROUND).append(':').append(1);
}
if (BuildConfig.DEBUG) {
argsBuilder.append(',').append(ConfigParams.PARAM_DEBUG).append(':').append(1);
}
return TextUtils.join("-", tokenItems);
return argsBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import java.net.ServerSocket;
import java.net.SocketTimeoutException;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.logs.Log;
import io.github.muntashirakon.AppManager.misc.NoOps;
import io.github.muntashirakon.AppManager.server.common.Caller;
Expand Down Expand Up @@ -89,8 +88,6 @@ private LocalServer() throws IOException, AdbPairingRequiredException {
mLocalServerManager = LocalServerManager.getInstance(mContext);
// Initialise necessary files and permissions
ServerConfig.init(mContext, UserHandleHidden.myUserId());
// Check if am.jar is in the right place
checkFile();
// Start server if not already
checkConnect();
}
Expand Down Expand Up @@ -168,13 +165,6 @@ public void closeBgServer() {
mLocalServerManager.stop();
}

@WorkerThread
@NoOps
private void checkFile() throws IOException {
AssetsUtils.copyFile(mContext, ServerConfig.JAR_NAME, ServerConfig.getDestJarFile(), BuildConfig.DEBUG);
AssetsUtils.writeScript(mContext);
}

@WorkerThread
@NoOps(used = true)
public static void restart() throws IOException, AdbPairingRequiredException {
Expand All @@ -187,11 +177,4 @@ public static void restart() throws IOException, AdbPairingRequiredException {
getInstance();
}
}

@WorkerThread
@NonNull
public static String getExecCommand(@NonNull Context context) throws IOException {
AssetsUtils.writeScript(context);
return "sh " + ServerConfig.getExecPath() + " " + ServerConfig.getLocalServerPort() + " " + ServerConfig.getLocalToken();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,6 @@ void closeBgServer() {
}
}

@WorkerThread
@NonNull
private String getExecCommand() throws IOException {
Log.e(TAG, "classpath --> %s", ServerConfig.getClassPath());
Log.e(TAG, "exec path --> %s", ServerConfig.getExecPath());
return LocalServer.getExecCommand(mContext);
}

@Nullable
private volatile AdbStream mAdbStream;
private volatile CountDownLatch mAdbConnectionWatcher = new CountDownLatch(1);
Expand Down Expand Up @@ -227,7 +219,8 @@ private void useAdbStartServer() throws Exception {

try (OutputStream os = Objects.requireNonNull(mAdbStream).openOutputStream()) {
os.write("id\n".getBytes());
String command = getExecCommand();
// ADB may require a fallback method
String command = ServerConfig.getServerRunnerAdbCommand();
Log.d(TAG, "useAdbStartServer: %s", command);
os.write((command + "\n").getBytes());
}
Expand All @@ -243,7 +236,8 @@ private void useRootStartServer() throws Exception {
if (!Ops.hasRoot()) {
throw new Exception("Root access denied");
}
String command = getExecCommand(); // + "\n" + "supolicy --live 'allow qti_init_shell zygote_exec file execute'";
String command = ServerConfig.getServerRunnerCommand(0);
// + "\n" + "supolicy --live 'allow qti_init_shell zygote_exec file execute'";
Log.d(TAG, "useRootStartServer: %s", command);
Runner.Result result = Runner.runCommand(command);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
Expand All @@ -19,25 +18,29 @@
import java.io.File;
import java.io.IOException;
import java.net.Inet4Address;
import java.security.SecureRandom;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.logs.Log;
import io.github.muntashirakon.AppManager.misc.NoOps;
import io.github.muntashirakon.AppManager.server.common.Constants;
import io.github.muntashirakon.AppManager.utils.ContextUtils;
import io.github.muntashirakon.AppManager.utils.FileUtils;

// Copyright 2016 Zheng Li
public final class ServerConfig {
public static final String TAG = ServerConfig.class.getSimpleName();

public static final int DEFAULT_ADB_PORT = 5555;
static String SOCKET_PATH = "am_socket";
private static int DEFAULT_LOCAL_SERVER_PORT = 60001;
static final String SERVER_RUNNER_EXEC_NAME = "run_server.sh";
private static final int DEFAULT_LOCAL_SERVER_PORT = 60001;
private static final String LOCAL_TOKEN = "l_token";

static final String JAR_NAME = "am.jar";
static final String EXECUTABLE_FILE_NAME = "run_server.sh";

private static File sDestJarFile;
private static File sDestExecFile;
private static final File[] SERVER_RUNNER_EXEC = new File[2];
private static final File[] SERVER_RUNNER_JAR = new File[2];
private static final SharedPreferences sPreferences = ContextUtils.getContext()
.getSharedPreferences("server_config", Context.MODE_PRIVATE);
private static int sServerPort = DEFAULT_LOCAL_SERVER_PORT;
private static volatile boolean sInitialised = false;

@WorkerThread
Expand All @@ -47,13 +50,27 @@ public static void init(@NonNull Context context, @UserIdInt int userHandle) thr
return;
}

File externalStorage = FileUtils.getExternalCachePath(context);
sDestJarFile = new File(externalStorage, JAR_NAME);
sDestExecFile = new File(externalStorage, EXECUTABLE_FILE_NAME);

// Setup paths
File externalCachePath = FileUtils.getExternalCachePath(context);
File externalMediaPath = FileUtils.getExternalMediaPath(context);
File deStorage = ContextUtils.getDeContext(context).getCacheDir();
SERVER_RUNNER_EXEC[0] = new File(externalCachePath, SERVER_RUNNER_EXEC_NAME);
SERVER_RUNNER_EXEC[1] = new File(deStorage, SERVER_RUNNER_EXEC_NAME);
SERVER_RUNNER_JAR[0] = new File(externalCachePath, Constants.JAR_NAME);
SERVER_RUNNER_JAR[1] = new File(deStorage, Constants.JAR_NAME);
// Copy JAR
boolean force = BuildConfig.DEBUG;
AssetsUtils.copyFile(context, Constants.JAR_NAME, SERVER_RUNNER_JAR[0], force);
AssetsUtils.copyFile(context, Constants.JAR_NAME, SERVER_RUNNER_JAR[1], force);
// Write script
AssetsUtils.writeServerExecScript(context, SERVER_RUNNER_EXEC[0], SERVER_RUNNER_JAR[0].getAbsolutePath());
AssetsUtils.writeServerExecScript(context, SERVER_RUNNER_EXEC[1], SERVER_RUNNER_JAR[1].getAbsolutePath());
// Update permission
FileUtils.chmod711(deStorage);
FileUtils.chmod644(SERVER_RUNNER_JAR[1]);
FileUtils.chmod644(SERVER_RUNNER_EXEC[1]);
if (userHandle != 0) {
SOCKET_PATH += userHandle;
DEFAULT_LOCAL_SERVER_PORT += userHandle;
sServerPort += userHandle;
}

sInitialised = true;
Expand All @@ -62,19 +79,22 @@ public static void init(@NonNull Context context, @UserIdInt int userHandle) thr
@AnyThread
@NonNull
public static File getDestJarFile() {
return sDestJarFile;
// For compatibility only
return SERVER_RUNNER_JAR[0];
}

@AnyThread
@NonNull
static String getClassPath() {
return sDestJarFile.getAbsolutePath();
public static String getServerRunnerCommand(int index) throws IndexOutOfBoundsException {
Log.e(TAG, "Classpath: %s", SERVER_RUNNER_JAR[index]);
Log.e(TAG, "Exec path: %s", SERVER_RUNNER_EXEC[index]);
return "sh " + SERVER_RUNNER_EXEC[index] + " " + getLocalServerPort() + " " + getLocalToken();
}

@AnyThread
@NonNull
public static File getExecPath() {
return sDestExecFile;
public static String getServerRunnerAdbCommand() throws IndexOutOfBoundsException {
return getServerRunnerCommand(0) + " || " + getServerRunnerCommand(1);
}

/**
Expand All @@ -84,17 +104,17 @@ public static File getExecPath() {
*/
@AnyThread
@NonNull
static String getLocalToken() {
public static String getLocalToken() {
String token = sPreferences.getString(LOCAL_TOKEN, null);
if (TextUtils.isEmpty(token)) {
token = AssetsUtils.generateToken();
token = generateToken();
sPreferences.edit().putString(LOCAL_TOKEN, token).apply();
}
return token;
}

@AnyThread
static boolean getAllowBgRunning() {
public static boolean getAllowBgRunning() {
return sPreferences.getBoolean("allow_bg_running", true);
}

Expand All @@ -112,8 +132,8 @@ public static void setAdbPort(@IntRange(from = 0, to = 65535) int port) {
}

@AnyThread
static int getLocalServerPort() {
return DEFAULT_LOCAL_SERVER_PORT;
public static int getLocalServerPort() {
return sServerPort;
}

@WorkerThread
Expand All @@ -125,12 +145,6 @@ public static String getAdbHost(Context context) {
@WorkerThread
@NonNull
public static String getLocalServerHost(Context context) {
return getHostIpAddress();
}

@WorkerThread
@NonNull
private static String getHostIpAddress() {
String ipAddress = Inet4Address.getLoopbackAddress().getHostAddress();
if (ipAddress == null || ipAddress.equals("::1")) return "127.0.0.1";
return ipAddress;
Expand Down Expand Up @@ -158,4 +172,18 @@ private static boolean isEmulator(@NonNull Context context) {
|| Build.HARDWARE.contains(RANCHU)
|| androidId == null;
}


@AnyThread
@NonNull
private static String generateToken() {
Context context = ContextUtils.getContext();
String[] wordList = context.getResources().getStringArray(R.array.word_list);
SecureRandom secureRandom = new SecureRandom();
String[] tokenItems = new String[3 + secureRandom.nextInt(3)];
for (int i = 0; i < tokenItems.length; ++i) {
tokenItems[i] = wordList[secureRandom.nextInt(wordList.length)];
}
return TextUtils.join("-", tokenItems);
}
}

0 comments on commit 4f97928

Please sign in to comment.