diff options
author | 2022-12-23 20:52:48 -0800 | |
---|---|---|
committer | 2023-01-31 01:49:41 +0000 | |
commit | 06a1c00dd3bebea4e1c601ee7a5323268ae38f9d (patch) | |
tree | f9a845106123d2cbb07e2d7eb8202523ca009d76 | |
parent | 258ed5b6a9380265caf2459de31994c280439d2d (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
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" } |