summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alex Buynytskyy <alexbuy@google.com> 2022-12-23 20:52:48 -0800
committer Alex Buynytskyy <alexbuy@google.com> 2023-01-31 01:49:41 +0000
commit06a1c00dd3bebea4e1c601ee7a5323268ae38f9d (patch)
treef9a845106123d2cbb07e2d7eb8202523ca009d76
parent258ed5b6a9380265caf2459de31994c280439d2d (diff)
Store reserve copy of runtime permissions and roles.
To be used in case the primary copy is missing or corrupted. go/protected-packages-xml Bug: 253568736 Bug: 196909329 Test: atest RuntimePermissionsPersistenceTest RolesPersistenceTest Change-Id: Ib26676baf67b32d9f7cdfceb388b420ebadbf424
-rw-r--r--service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java44
-rw-r--r--service/java/com/android/role/persistence/RolesPersistenceImpl.java41
-rw-r--r--tests/apex/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt35
-rw-r--r--tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt25
4 files changed, 124 insertions, 21 deletions
diff --git a/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
index 9ba37af45..c1f9299c2 100644
--- a/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
+++ b/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
@@ -20,12 +20,15 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ApexEnvironment;
import android.content.pm.PackageManager;
+import android.os.FileUtils;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -53,6 +56,8 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers
private static final String APEX_MODULE_NAME = "com.android.permission";
private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";
+ private static final String RUNTIME_PERMISSIONS_RESERVE_COPY_FILE_NAME =
+ RUNTIME_PERMISSIONS_FILE_NAME + ".reservecopy";
private static final String TAG_PACKAGE = "package";
private static final String TAG_PERMISSION = "permission";
@@ -76,8 +81,20 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers
} catch (FileNotFoundException e) {
Log.i(LOG_TAG, "runtime-permissions.xml not found");
return null;
- } catch (XmlPullParserException | IOException e) {
- throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e);
+ } catch (Exception e) {
+ File reserveFile = getReserveCopyFile(user);
+ Log.wtf(LOG_TAG, "Reading from reserve copy: " + reserveFile, e);
+ try (FileInputStream inputStream = new AtomicFile(reserveFile).openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(inputStream, null);
+ return parseXml(parser);
+ } catch (Exception exceptionReadingReserveFile) {
+ Log.e(LOG_TAG, "Failed to read reserve copy: " + reserveFile,
+ exceptionReadingReserveFile);
+ // Reserve copy failed, rethrow the original exception wrapped as runtime.
+ throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file,
+ e);
+ }
}
}
@@ -174,6 +191,9 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers
@Override
public void writeForUser(@NonNull RuntimePermissionsState runtimePermissions,
@NonNull UserHandle user) {
+ File reserveFile = getReserveCopyFile(user);
+ reserveFile.delete();
+
File file = getFile(user);
AtomicFile atomicFile = new AtomicFile(file);
FileOutputStream outputStream = null;
@@ -192,9 +212,18 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers
Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file,
e);
atomicFile.failWrite(outputStream);
+ return;
} finally {
IoUtils.closeQuietly(outputStream);
}
+
+ try (FileInputStream in = new FileInputStream(file);
+ FileOutputStream out = new FileOutputStream(reserveFile)) {
+ FileUtils.copy(in, out);
+ out.getFD().sync();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to write reserve copy: " + reserveFile, e);
+ }
}
private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer,
@@ -253,12 +282,21 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers
@Override
public void deleteForUser(@NonNull UserHandle user) {
getFile(user).delete();
+ getReserveCopyFile(user).delete();
}
+ @VisibleForTesting
@NonNull
- private static File getFile(@NonNull UserHandle user) {
+ static File getFile(@NonNull UserHandle user) {
ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);
File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);
return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME);
}
+
+ @NonNull
+ private static File getReserveCopyFile(@NonNull UserHandle user) {
+ ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);
+ File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);
+ return new File(dataDirectory, RUNTIME_PERMISSIONS_RESERVE_COPY_FILE_NAME);
+ }
}
diff --git a/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/service/java/com/android/role/persistence/RolesPersistenceImpl.java
index f66257f13..944036aeb 100644
--- a/service/java/com/android/role/persistence/RolesPersistenceImpl.java
+++ b/service/java/com/android/role/persistence/RolesPersistenceImpl.java
@@ -19,6 +19,7 @@ package com.android.role.persistence;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ApexEnvironment;
+import android.os.FileUtils;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -26,6 +27,7 @@ import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.permission.persistence.IoUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -54,6 +56,7 @@ public class RolesPersistenceImpl implements RolesPersistence {
private static final String APEX_MODULE_NAME = "com.android.permission";
private static final String ROLES_FILE_NAME = "roles.xml";
+ private static final String ROLES_RESERVE_COPY_FILE_NAME = ROLES_FILE_NAME + ".reservecopy";
private static final String TAG_ROLES = "roles";
private static final String TAG_ROLE = "role";
@@ -74,8 +77,19 @@ public class RolesPersistenceImpl implements RolesPersistence {
} catch (FileNotFoundException e) {
Log.i(LOG_TAG, "roles.xml not found");
return null;
- } catch (XmlPullParserException | IOException e) {
- throw new IllegalStateException("Failed to read roles.xml: " + file , e);
+ } catch (Exception e) {
+ File reserveFile = getReserveCopyFile(user);
+ Log.wtf(LOG_TAG, "Reading from reserve copy: " + reserveFile, e);
+ try (FileInputStream inputStream = new AtomicFile(reserveFile).openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(inputStream, null);
+ return parseXml(parser);
+ } catch (Exception exceptionReadingReserveFile) {
+ Log.e(LOG_TAG, "Failed to read reserve copy: " + reserveFile,
+ exceptionReadingReserveFile);
+ // Reserve copy failed, rethrow the original exception wrapped as runtime.
+ throw new IllegalStateException("Failed to read roles.xml: " + file , e);
+ }
}
}
@@ -147,6 +161,9 @@ public class RolesPersistenceImpl implements RolesPersistence {
@Override
public void writeForUser(@NonNull RolesState roles, @NonNull UserHandle user) {
+ File reserveFile = getReserveCopyFile(user);
+ reserveFile.delete();
+
File file = getFile(user);
AtomicFile atomicFile = new AtomicFile(file);
FileOutputStream outputStream = null;
@@ -166,9 +183,18 @@ public class RolesPersistenceImpl implements RolesPersistence {
Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file,
e);
atomicFile.failWrite(outputStream);
+ return;
} finally {
IoUtils.closeQuietly(outputStream);
}
+
+ try (FileInputStream in = new FileInputStream(file);
+ FileOutputStream out = new FileOutputStream(reserveFile)) {
+ FileUtils.copy(in, out);
+ out.getFD().sync();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to write reserve copy: " + reserveFile, e);
+ }
}
private static void serializeRoles(@NonNull XmlSerializer serializer,
@@ -207,12 +233,21 @@ public class RolesPersistenceImpl implements RolesPersistence {
@Override
public void deleteForUser(@NonNull UserHandle user) {
getFile(user).delete();
+ getReserveCopyFile(user).delete();
}
+ @VisibleForTesting
@NonNull
- private static File getFile(@NonNull UserHandle user) {
+ static File getFile(@NonNull UserHandle user) {
ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);
File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);
return new File(dataDirectory, ROLES_FILE_NAME);
}
+
+ @NonNull
+ private static File getReserveCopyFile(@NonNull UserHandle user) {
+ ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);
+ File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);
+ return new File(dataDirectory, ROLES_RESERVE_COPY_FILE_NAME);
+ }
}
diff --git a/tests/apex/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt b/tests/apex/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt
index 2987da087..961606d13 100644
--- a/tests/apex/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt
+++ b/tests/apex/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt
@@ -80,19 +80,22 @@ class RuntimePermissionsPersistenceTest {
}
@Test
- fun testReadWrite() {
+ fun testWriteRead() {
persistence.writeForUser(state, user)
val persistedState = persistence.readForUser(user)
- assertThat(persistedState).isEqualTo(state)
- assertThat(persistedState!!.version).isEqualTo(state.version)
- assertThat(persistedState.fingerprint).isEqualTo(state.fingerprint)
- assertThat(persistedState.packagePermissions).isEqualTo(state.packagePermissions)
- val persistedPermissionState = persistedState.packagePermissions.values.first().first()
- assertThat(persistedPermissionState.name).isEqualTo(permissionState.name)
- assertThat(persistedPermissionState.isGranted).isEqualTo(permissionState.isGranted)
- assertThat(persistedPermissionState.flags).isEqualTo(permissionState.flags)
- assertThat(persistedState.sharedUserPermissions).isEqualTo(state.sharedUserPermissions)
+ checkPersistedState(persistedState!!)
+ }
+
+ @Test
+ fun testWriteCorruptReadFromReserveCopy() {
+ persistence.writeForUser(state, user)
+ // Corrupt the primary file.
+ RuntimePermissionsPersistenceImpl.getFile(user).writeText(
+ "<runtime-permissions version=\"10\"><package name=\"com.foo.bar\"><permission")
+ val persistedState = persistence.readForUser(user)
+
+ checkPersistedState(persistedState!!)
}
@Test
@@ -104,6 +107,18 @@ class RuntimePermissionsPersistenceTest {
assertThat(persistedState).isNull()
}
+ private fun checkPersistedState(persistedState: RuntimePermissionsState) {
+ assertThat(persistedState).isEqualTo(state)
+ assertThat(persistedState.version).isEqualTo(state.version)
+ assertThat(persistedState.fingerprint).isEqualTo(state.fingerprint)
+ assertThat(persistedState.packagePermissions).isEqualTo(state.packagePermissions)
+ val persistedPermissionState = persistedState.packagePermissions.values.first().first()
+ assertThat(persistedPermissionState.name).isEqualTo(permissionState.name)
+ assertThat(persistedPermissionState.isGranted).isEqualTo(permissionState.isGranted)
+ assertThat(persistedPermissionState.flags).isEqualTo(permissionState.flags)
+ assertThat(persistedState.sharedUserPermissions).isEqualTo(state.sharedUserPermissions)
+ }
+
companion object {
private const val APEX_MODULE_NAME = "com.android.permission"
}
diff --git a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
index f9d9d5afb..68249e6f1 100644
--- a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
+++ b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
@@ -76,14 +76,22 @@ class RolesPersistenceTest {
}
@Test
- fun testReadWrite() {
+ fun testWriteRead() {
persistence.writeForUser(state, user)
val persistedState = persistence.readForUser(user)
- assertThat(persistedState).isEqualTo(state)
- assertThat(persistedState!!.version).isEqualTo(state.version)
- assertThat(persistedState.packagesHash).isEqualTo(state.packagesHash)
- assertThat(persistedState.roles).isEqualTo(state.roles)
+ checkPersistedState(persistedState)
+ }
+
+ @Test
+ fun testWriteCorruptReadFromReserveCopy() {
+ persistence.writeForUser(state, user)
+ // Corrupt the primary file.
+ RolesPersistenceImpl.getFile(user).writeText(
+ "<roles version=\"-1\"><role name=\"com.foo.bar\"><holder")
+ val persistedState = persistence.readForUser(user)
+
+ checkPersistedState(persistedState!!)
}
@Test
@@ -95,6 +103,13 @@ class RolesPersistenceTest {
assertThat(persistedState).isNull()
}
+ private fun checkPersistedState(persistedState: RolesState) {
+ assertThat(persistedState).isEqualTo(state)
+ assertThat(persistedState.version).isEqualTo(state.version)
+ assertThat(persistedState.packagesHash).isEqualTo(state.packagesHash)
+ assertThat(persistedState.roles).isEqualTo(state.roles)
+ }
+
companion object {
private const val APEX_MODULE_NAME = "com.android.permission"
}