From 69f0bb4fbcf98aa0f5ec254fe5dda513527e0063 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 4 Dec 2022 16:55:19 +0100 Subject: [PATCH] Request root access from separate thread and don't use global Shell This should help prevent some of the ANR's reported through Google Play --- .../aegis/AegisApplicationBase.java | 4 +- .../aegis/importers/AuthyImporter.java | 4 +- .../aegis/importers/DatabaseImporter.java | 5 ++- .../aegis/importers/GoogleAuthImporter.java | 8 +++- .../importers/MicrosoftAuthImporter.java | 5 ++- .../aegis/ui/ImportEntriesActivity.java | 41 ++++++++++++------- .../aegis/ui/tasks/RootShellTask.java | 31 ++++++++++++++ app/src/main/res/values/strings.xml | 3 +- 8 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/tasks/RootShellTask.java diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java b/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java index 836f41ec..f7ba8ce4 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java @@ -39,8 +39,8 @@ public abstract class AegisApplicationBase extends Application { private VaultManager _vaultManager; static { - // to access other app's internal storage directory, run libsu commands inside the global mount namespace - Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER)); + // Enable verbose libsu logging in debug builds + Shell.enableVerboseLogging = BuildConfig.DEBUG; } @Override diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java index 111d0daf..6a75b7f0 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java @@ -16,6 +16,7 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.util.JsonUtils; import com.beemdevelopment.aegis.util.PreferenceParser; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFileInputStream; @@ -67,8 +68,9 @@ public class AuthyImporter extends DatabaseImporter { } @Override - public State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException { + public State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException { SuFile path = getAppPath(); + path.setShell(shell); JSONArray array; JSONArray authyArray; diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java index daf8d416..494ab42f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -8,6 +8,7 @@ import androidx.annotation.StringRes; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFileInputStream; @@ -70,8 +71,10 @@ public abstract class DatabaseImporter { return read(stream, false); } - public State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException { + public State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException { SuFile file = getAppPath(); + file.setShell(shell); + try (InputStream stream = SuFileInputStream.open(file)) { return read(stream, true); } catch (IOException e) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java index 4070bcbb..267a73a9 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java @@ -13,6 +13,7 @@ import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.io.SuFile; import java.io.InputStream; @@ -31,7 +32,8 @@ public class GoogleAuthImporter extends DatabaseImporter { @Override protected SuFile getAppPath() throws PackageManager.NameNotFoundException { - return getAppPath(_pkgName, _subPath); + SuFile file = getAppPath(_pkgName, _subPath); + return file; } @Override @@ -55,8 +57,10 @@ public class GoogleAuthImporter extends DatabaseImporter { } @Override - public DatabaseImporter.State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException { + public DatabaseImporter.State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException { SuFile path = getAppPath(); + path.setShell(shell); + final Context context = requireContext(); SqlImporterHelper helper = new SqlImporterHelper(context); List entries = helper.read(Entry.class, path, "accounts"); diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java index 319c0e69..4e842844 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java @@ -11,6 +11,7 @@ import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.io.SuFile; import java.io.InputStream; @@ -40,8 +41,10 @@ public class MicrosoftAuthImporter extends DatabaseImporter { } @Override - public DatabaseImporter.State readFromApp() throws PackageManager.NameNotFoundException, DatabaseImporterException { + public DatabaseImporter.State readFromApp(Shell shell) throws PackageManager.NameNotFoundException, DatabaseImporterException { SuFile path = getAppPath(); + path.setShell(shell); + SqlImporterHelper helper = new SqlImporterHelper(requireContext()); List entries = helper.read(Entry.class, path, "accounts"); return new State(entries); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java index 87e7bb87..8787c419 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -20,12 +20,12 @@ import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException; import com.beemdevelopment.aegis.importers.DatabaseImporterException; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.models.ImportEntry; +import com.beemdevelopment.aegis.ui.tasks.RootShellTask; import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter; import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultRepository; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.topjohnwu.superuser.Shell; import java.io.File; import java.io.FileInputStream; @@ -118,25 +118,36 @@ public class ImportEntriesActivity extends AegisActivity { } private void startImportApp(@NonNull DatabaseImporter importer) { - // obtain the global root shell and close it immediately after we're done - // TODO: find a way to use SuFileInputStream with Shell.newInstance() - try (Shell shell = Shell.getShell()) { - if (!shell.isRoot()) { + RootShellTask task = new RootShellTask(this, shell -> { + if (isFinishing()) { + return; + } + + if (shell == null || !shell.isRoot()) { Toast.makeText(this, R.string.root_error, Toast.LENGTH_SHORT).show(); finish(); return; } - DatabaseImporter.State state = importer.readFromApp(); - processImporterState(state); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - Toast.makeText(this, R.string.app_lookup_error, Toast.LENGTH_SHORT).show(); - finish(); - } catch (IOException | DatabaseImporterException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(this, R.string.reading_file_error, e, (dialog, which) -> finish()); - } + try { + DatabaseImporter.State state = importer.readFromApp(shell); + processImporterState(state); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + Toast.makeText(this, R.string.app_lookup_error, Toast.LENGTH_SHORT).show(); + finish(); + } catch (DatabaseImporterException e) { + e.printStackTrace(); + Dialogs.showErrorDialog(this, R.string.reading_file_error, e, (dialog, which) -> finish()); + } finally { + try { + shell.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + task.execute(this); } private void processImporterState(DatabaseImporter.State state) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/tasks/RootShellTask.java b/app/src/main/java/com/beemdevelopment/aegis/ui/tasks/RootShellTask.java new file mode 100644 index 00000000..3412844e --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/tasks/RootShellTask.java @@ -0,0 +1,31 @@ +package com.beemdevelopment.aegis.ui.tasks; + +import android.content.Context; + +import com.beemdevelopment.aegis.R; +import com.topjohnwu.superuser.Shell; + +public class RootShellTask extends ProgressDialogTask { + private final Callback _cb; + + public RootShellTask(Context context, Callback cb) { + super(context, context.getString(R.string.requesting_root_access)); + _cb = cb; + } + + @Override + protected Shell doInBackground(Object... params) { + // To access other app's internal storage directory, run libsu commands inside the global mount namespace + return Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER).build(); + } + + @Override + protected void onPostExecute(Shell shell) { + super.onPostExecute(shell); + _cb.onTaskFinished(shell); + } + + public interface Callback { + void onTaskFinished(Shell shell); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a98eba4f..ca6618d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -171,6 +171,7 @@ Encrypting the vault Exporting the vault Reading file + Requesting root access Analyzing QR code Analyzing QR code %d/%d (%s) @@ -237,7 +238,7 @@ An error occurred while trying to read the file Error: App is not installed The version of %s that\'s installed is not supported. Recent versions have started encrypting some of the files in the internal storage directory, making Aegis unable to access the information it needs for the import. Attempting to import is likely to result in an error. Would you like to continue anyway? - Error: unable to obtain root access + Error: Unable to obtain root access Imported %d entry Imported %d entries