Loading services/core/java/com/android/server/pm/ResilientAtomicFile.java 0 → 100644 +265 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.Slog; import com.android.server.security.FileIntegrity; import libcore.io.IoUtils; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; final class ResilientAtomicFile implements Closeable { private static final String LOG_TAG = "ResilientAtomicFile"; private final File mFile; private final File mTemporaryBackup; private final File mReserveCopy; private final int mFileMode; private final String mDebugName; private final ReadEventLogger mReadEventLogger; // Write state. private FileOutputStream mMainOutStream = null; private FileInputStream mMainInStream = null; private FileOutputStream mReserveOutStream = null; private FileInputStream mReserveInStream = null; // Read state. private File mCurrentFile = null; private FileInputStream mCurrentInStream = null; private void finalizeOutStream(FileOutputStream str) throws IOException { // Flash/sync + set permissions. str.flush(); FileUtils.sync(str); FileUtils.setPermissions(str.getFD(), mFileMode, -1, -1); } ResilientAtomicFile(@NonNull File file, @NonNull File temporaryBackup, @NonNull File reserveCopy, int fileMode, String debugName, @Nullable ReadEventLogger readEventLogger) { mFile = file; mTemporaryBackup = temporaryBackup; mReserveCopy = reserveCopy; mFileMode = fileMode; mDebugName = debugName; mReadEventLogger = readEventLogger; } public File getBaseFile() { return mFile; } public FileOutputStream startWrite() throws IOException { if (mMainOutStream != null) { throw new IllegalStateException("Duplicate startWrite call?"); } new File(mFile.getParent()).mkdirs(); if (mFile.exists()) { // Presence of backup settings file indicates that we failed // to persist packages earlier. So preserve the older // backup for future reference since the current packages // might have been corrupted. if (!mTemporaryBackup.exists()) { if (!mFile.renameTo(mTemporaryBackup)) { throw new IOException("Unable to backup " + mDebugName + " file, current changes will be lost at reboot"); } } else { mFile.delete(); Slog.w(LOG_TAG, "Preserving older " + mDebugName + " backup"); } } // Reserve copy is not valid anymore. mReserveCopy.delete(); // In case of MT access, it's possible the files get overwritten during write. // Let's open all FDs we need now. mMainOutStream = new FileOutputStream(mFile); mMainInStream = new FileInputStream(mFile); mReserveOutStream = new FileOutputStream(mReserveCopy); mReserveInStream = new FileInputStream(mReserveCopy); return mMainOutStream; } public void finishWrite(FileOutputStream str) throws IOException { if (mMainOutStream != str) { throw new IllegalStateException("Invalid incoming stream."); } // Flush and set permissions. try (FileOutputStream mainOutStream = mMainOutStream) { mMainOutStream = null; finalizeOutStream(mainOutStream); } // New file successfully written, old one are no longer needed. mTemporaryBackup.delete(); try (FileInputStream mainInStream = mMainInStream; FileInputStream reserveInStream = mReserveInStream) { mMainInStream = null; mReserveInStream = null; // Copy main file to reserve. try (FileOutputStream reserveOutStream = mReserveOutStream) { mReserveOutStream = null; FileUtils.copy(mainInStream, reserveOutStream); finalizeOutStream(reserveOutStream); } // Protect both main and reserve using fs-verity. try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { FileIntegrity.setUpFsVerity(mainPfd); FileIntegrity.setUpFsVerity(copyPfd); } catch (IOException e) { Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); } } catch (IOException e) { Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e); } } public void failWrite(FileOutputStream str) { if (mMainOutStream != str) { throw new IllegalStateException("Invalid incoming stream."); } // Close all FDs. close(); // Clean up partially written files if (mFile.exists()) { if (!mFile.delete()) { Slog.i(LOG_TAG, "Failed to clean up mangled file: " + mFile); } } } public FileInputStream openRead() throws IOException { if (mTemporaryBackup.exists()) { try { mCurrentFile = mTemporaryBackup; mCurrentInStream = new FileInputStream(mCurrentFile); if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.INFO, "Need to read from backup " + mDebugName + " file"); } if (mFile.exists()) { // If both the backup and normal file exist, we // ignore the normal one since it might have been // corrupted. Slog.w(LOG_TAG, "Cleaning up " + mDebugName + " file " + mFile); mFile.delete(); } // Ignore reserve copy as well. mReserveCopy.delete(); } catch (java.io.IOException e) { // We'll try for the normal settings file. } } if (mCurrentInStream != null) { return mCurrentInStream; } if (mFile.exists()) { mCurrentFile = mFile; mCurrentInStream = new FileInputStream(mCurrentFile); } else if (mReserveCopy.exists()) { mCurrentFile = mReserveCopy; mCurrentInStream = new FileInputStream(mCurrentFile); if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.INFO, "Need to read from reserve copy " + mDebugName + " file"); } } if (mCurrentInStream == null) { if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.INFO, "No " + mDebugName + " file"); } } return mCurrentInStream; } public void failRead(FileInputStream str, Exception e) { if (mCurrentInStream != str) { throw new IllegalStateException("Invalid incoming stream."); } mCurrentInStream = null; IoUtils.closeQuietly(str); if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.ERROR, "Error reading " + mDebugName + ", removing " + mCurrentFile + '\n' + Log.getStackTraceString(e)); } mCurrentFile.delete(); mCurrentFile = null; } public void delete() { mFile.delete(); mTemporaryBackup.delete(); mReserveCopy.delete(); } @Override public void close() { IoUtils.closeQuietly(mMainOutStream); IoUtils.closeQuietly(mMainInStream); IoUtils.closeQuietly(mReserveOutStream); IoUtils.closeQuietly(mReserveInStream); IoUtils.closeQuietly(mCurrentInStream); mMainOutStream = null; mMainInStream = null; mReserveOutStream = null; mReserveInStream = null; mCurrentInStream = null; mCurrentFile = null; } public String toString() { return mFile.getPath(); } interface ReadEventLogger { void logEvent(int priority, String msg); } } services/core/java/com/android/server/pm/Settings.java +580 −738 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/pm/ShortcutPackageItem.java +7 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.security.FileIntegrity; import org.json.JSONException; import org.json.JSONObject; Loading Loading @@ -180,6 +181,12 @@ abstract class ShortcutPackageItem { os.flush(); file.finishWrite(os); try { FileIntegrity.setUpFsVerity(path); } catch (IOException e) { Slog.e(TAG, "Failed to verity-protect " + path, e); } } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(os); Loading services/core/java/com/android/server/pm/ShortcutService.java +41 −64 Original line number Diff line number Diff line Loading @@ -119,7 +119,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.security.FileIntegrity; import com.android.server.uri.UriGrantsManagerInternal; import org.json.JSONArray; Loading Loading @@ -1070,29 +1069,26 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting final File getUserFile(@UserIdInt int userId) { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); } @VisibleForTesting final File getReserveCopyUserFile(@UserIdInt int userId) { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY); final ResilientAtomicFile getUserFile(@UserIdInt int userId) { File mainFile = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); File temporaryBackup = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES + ".backup"); File reserveCopy = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY); int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH; return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode, "user shortcut", null); } @GuardedBy("mLock") private void saveUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); try (ResilientAtomicFile file = getUserFile(userId)) { FileOutputStream os = null; try { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Saving to " + path); Slog.d(TAG, "Saving to " + file); } final File reservePath = getReserveCopyUserFile(userId); reservePath.delete(); path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; try { os = file.startWrite(); saveUserInternalLocked(userId, os, /* forBackup= */ false); Loading @@ -1102,25 +1098,9 @@ public class ShortcutService extends IShortcutService.Stub { // Remove all dangling bitmap files. cleanupDanglingBitmapDirectoriesLocked(userId); } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); Slog.e(TAG, "Failed to write to file " + file, e); file.failWrite(os); } // Store the reserve copy of the file. try (FileInputStream in = new FileInputStream(path); FileOutputStream out = new FileOutputStream(reservePath)) { FileUtils.copy(in, out); FileUtils.sync(out); } catch (IOException e) { Slog.e(TAG, "Failed to write reserve copy: " + path, e); } // Protect both primary and reserve copy with fs-verity. try { FileIntegrity.setUpFsVerity(path); FileIntegrity.setUpFsVerity(reservePath); } catch (IOException e) { Slog.e(TAG, "Failed to verity-protect", e); } getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); Loading Loading @@ -1157,29 +1137,26 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable private ShortcutUser loadUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); try (ResilientAtomicFile file = getUserFile(userId)) { FileInputStream in = null; try { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Loading from " + path); Slog.d(TAG, "Loading from " + file); } try (FileInputStream in = new AtomicFile(path).openRead()) { return loadUserInternal(userId, in, /* forBackup= */ false); } catch (FileNotFoundException e) { in = file.openRead(); if (in == null) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Not found " + path); Slog.d(TAG, "Not found " + file); } return null; } } catch (Exception e) { final File reservePath = getReserveCopyUserFile(userId); Slog.e(TAG, "Reading from reserve copy: " + reservePath, e); try (FileInputStream in = new AtomicFile(reservePath).openRead()) { return loadUserInternal(userId, in, /* forBackup= */ false); } catch (Exception exceptionReadingReserveFile) { Slog.e(TAG, "Failed to read reserve copy: " + reservePath, exceptionReadingReserveFile); } catch (Exception e) { // Remove corrupted file and retry. file.failRead(in, e); return loadUserLocked(userId); } Slog.e(TAG, "Failed to read file " + path, e); } return null; } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, Loading services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +24 −0 Original line number Diff line number Diff line Loading @@ -565,6 +565,22 @@ public class PackageManagerSettingsTests { verifyDistractionFlags(settingsUnderTest); } @Test public void testWriteCorruptReadPackageRestrictions() { final Settings settingsUnderTest = makeSettings(); populateDistractionFlags(settingsUnderTest); settingsUnderTest.writePackageRestrictionsLPr(0, /*sync=*/true); // Corrupt primary file. writeCorruptedPackageRestrictions(0); // now read and verify populateDefaultSettings(settingsUnderTest); settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); verifyDistractionFlags(settingsUnderTest); } @Test public void testReadWritePackageRestrictionsAsync() { final Settings settingsWrite = makeSettings(); Loading Loading @@ -1811,6 +1827,14 @@ public class PackageManagerSettingsTests { .getBytes()); } private void writeCorruptedPackageRestrictions(final int userId) { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/" + userId + "/package-restrictions.xml"), ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<package-restrictions>\n" + " <pkg name=\"" + PACKAGE_NAME_1 + "\" ").getBytes()); } private static void writeStoppedPackagesXml() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"), ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" Loading Loading
services/core/java/com/android/server/pm/ResilientAtomicFile.java 0 → 100644 +265 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.Slog; import com.android.server.security.FileIntegrity; import libcore.io.IoUtils; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; final class ResilientAtomicFile implements Closeable { private static final String LOG_TAG = "ResilientAtomicFile"; private final File mFile; private final File mTemporaryBackup; private final File mReserveCopy; private final int mFileMode; private final String mDebugName; private final ReadEventLogger mReadEventLogger; // Write state. private FileOutputStream mMainOutStream = null; private FileInputStream mMainInStream = null; private FileOutputStream mReserveOutStream = null; private FileInputStream mReserveInStream = null; // Read state. private File mCurrentFile = null; private FileInputStream mCurrentInStream = null; private void finalizeOutStream(FileOutputStream str) throws IOException { // Flash/sync + set permissions. str.flush(); FileUtils.sync(str); FileUtils.setPermissions(str.getFD(), mFileMode, -1, -1); } ResilientAtomicFile(@NonNull File file, @NonNull File temporaryBackup, @NonNull File reserveCopy, int fileMode, String debugName, @Nullable ReadEventLogger readEventLogger) { mFile = file; mTemporaryBackup = temporaryBackup; mReserveCopy = reserveCopy; mFileMode = fileMode; mDebugName = debugName; mReadEventLogger = readEventLogger; } public File getBaseFile() { return mFile; } public FileOutputStream startWrite() throws IOException { if (mMainOutStream != null) { throw new IllegalStateException("Duplicate startWrite call?"); } new File(mFile.getParent()).mkdirs(); if (mFile.exists()) { // Presence of backup settings file indicates that we failed // to persist packages earlier. So preserve the older // backup for future reference since the current packages // might have been corrupted. if (!mTemporaryBackup.exists()) { if (!mFile.renameTo(mTemporaryBackup)) { throw new IOException("Unable to backup " + mDebugName + " file, current changes will be lost at reboot"); } } else { mFile.delete(); Slog.w(LOG_TAG, "Preserving older " + mDebugName + " backup"); } } // Reserve copy is not valid anymore. mReserveCopy.delete(); // In case of MT access, it's possible the files get overwritten during write. // Let's open all FDs we need now. mMainOutStream = new FileOutputStream(mFile); mMainInStream = new FileInputStream(mFile); mReserveOutStream = new FileOutputStream(mReserveCopy); mReserveInStream = new FileInputStream(mReserveCopy); return mMainOutStream; } public void finishWrite(FileOutputStream str) throws IOException { if (mMainOutStream != str) { throw new IllegalStateException("Invalid incoming stream."); } // Flush and set permissions. try (FileOutputStream mainOutStream = mMainOutStream) { mMainOutStream = null; finalizeOutStream(mainOutStream); } // New file successfully written, old one are no longer needed. mTemporaryBackup.delete(); try (FileInputStream mainInStream = mMainInStream; FileInputStream reserveInStream = mReserveInStream) { mMainInStream = null; mReserveInStream = null; // Copy main file to reserve. try (FileOutputStream reserveOutStream = mReserveOutStream) { mReserveOutStream = null; FileUtils.copy(mainInStream, reserveOutStream); finalizeOutStream(reserveOutStream); } // Protect both main and reserve using fs-verity. try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { FileIntegrity.setUpFsVerity(mainPfd); FileIntegrity.setUpFsVerity(copyPfd); } catch (IOException e) { Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); } } catch (IOException e) { Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e); } } public void failWrite(FileOutputStream str) { if (mMainOutStream != str) { throw new IllegalStateException("Invalid incoming stream."); } // Close all FDs. close(); // Clean up partially written files if (mFile.exists()) { if (!mFile.delete()) { Slog.i(LOG_TAG, "Failed to clean up mangled file: " + mFile); } } } public FileInputStream openRead() throws IOException { if (mTemporaryBackup.exists()) { try { mCurrentFile = mTemporaryBackup; mCurrentInStream = new FileInputStream(mCurrentFile); if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.INFO, "Need to read from backup " + mDebugName + " file"); } if (mFile.exists()) { // If both the backup and normal file exist, we // ignore the normal one since it might have been // corrupted. Slog.w(LOG_TAG, "Cleaning up " + mDebugName + " file " + mFile); mFile.delete(); } // Ignore reserve copy as well. mReserveCopy.delete(); } catch (java.io.IOException e) { // We'll try for the normal settings file. } } if (mCurrentInStream != null) { return mCurrentInStream; } if (mFile.exists()) { mCurrentFile = mFile; mCurrentInStream = new FileInputStream(mCurrentFile); } else if (mReserveCopy.exists()) { mCurrentFile = mReserveCopy; mCurrentInStream = new FileInputStream(mCurrentFile); if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.INFO, "Need to read from reserve copy " + mDebugName + " file"); } } if (mCurrentInStream == null) { if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.INFO, "No " + mDebugName + " file"); } } return mCurrentInStream; } public void failRead(FileInputStream str, Exception e) { if (mCurrentInStream != str) { throw new IllegalStateException("Invalid incoming stream."); } mCurrentInStream = null; IoUtils.closeQuietly(str); if (mReadEventLogger != null) { mReadEventLogger.logEvent(Log.ERROR, "Error reading " + mDebugName + ", removing " + mCurrentFile + '\n' + Log.getStackTraceString(e)); } mCurrentFile.delete(); mCurrentFile = null; } public void delete() { mFile.delete(); mTemporaryBackup.delete(); mReserveCopy.delete(); } @Override public void close() { IoUtils.closeQuietly(mMainOutStream); IoUtils.closeQuietly(mMainInStream); IoUtils.closeQuietly(mReserveOutStream); IoUtils.closeQuietly(mReserveInStream); IoUtils.closeQuietly(mCurrentInStream); mMainOutStream = null; mMainInStream = null; mReserveOutStream = null; mReserveInStream = null; mCurrentInStream = null; mCurrentFile = null; } public String toString() { return mFile.getPath(); } interface ReadEventLogger { void logEvent(int priority, String msg); } }
services/core/java/com/android/server/pm/Settings.java +580 −738 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/pm/ShortcutPackageItem.java +7 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.security.FileIntegrity; import org.json.JSONException; import org.json.JSONObject; Loading Loading @@ -180,6 +181,12 @@ abstract class ShortcutPackageItem { os.flush(); file.finishWrite(os); try { FileIntegrity.setUpFsVerity(path); } catch (IOException e) { Slog.e(TAG, "Failed to verity-protect " + path, e); } } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(os); Loading
services/core/java/com/android/server/pm/ShortcutService.java +41 −64 Original line number Diff line number Diff line Loading @@ -119,7 +119,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.security.FileIntegrity; import com.android.server.uri.UriGrantsManagerInternal; import org.json.JSONArray; Loading Loading @@ -1070,29 +1069,26 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting final File getUserFile(@UserIdInt int userId) { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); } @VisibleForTesting final File getReserveCopyUserFile(@UserIdInt int userId) { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY); final ResilientAtomicFile getUserFile(@UserIdInt int userId) { File mainFile = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); File temporaryBackup = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES + ".backup"); File reserveCopy = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY); int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH; return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode, "user shortcut", null); } @GuardedBy("mLock") private void saveUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); try (ResilientAtomicFile file = getUserFile(userId)) { FileOutputStream os = null; try { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Saving to " + path); Slog.d(TAG, "Saving to " + file); } final File reservePath = getReserveCopyUserFile(userId); reservePath.delete(); path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; try { os = file.startWrite(); saveUserInternalLocked(userId, os, /* forBackup= */ false); Loading @@ -1102,25 +1098,9 @@ public class ShortcutService extends IShortcutService.Stub { // Remove all dangling bitmap files. cleanupDanglingBitmapDirectoriesLocked(userId); } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); Slog.e(TAG, "Failed to write to file " + file, e); file.failWrite(os); } // Store the reserve copy of the file. try (FileInputStream in = new FileInputStream(path); FileOutputStream out = new FileOutputStream(reservePath)) { FileUtils.copy(in, out); FileUtils.sync(out); } catch (IOException e) { Slog.e(TAG, "Failed to write reserve copy: " + path, e); } // Protect both primary and reserve copy with fs-verity. try { FileIntegrity.setUpFsVerity(path); FileIntegrity.setUpFsVerity(reservePath); } catch (IOException e) { Slog.e(TAG, "Failed to verity-protect", e); } getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); Loading Loading @@ -1157,29 +1137,26 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable private ShortcutUser loadUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); try (ResilientAtomicFile file = getUserFile(userId)) { FileInputStream in = null; try { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Loading from " + path); Slog.d(TAG, "Loading from " + file); } try (FileInputStream in = new AtomicFile(path).openRead()) { return loadUserInternal(userId, in, /* forBackup= */ false); } catch (FileNotFoundException e) { in = file.openRead(); if (in == null) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Not found " + path); Slog.d(TAG, "Not found " + file); } return null; } } catch (Exception e) { final File reservePath = getReserveCopyUserFile(userId); Slog.e(TAG, "Reading from reserve copy: " + reservePath, e); try (FileInputStream in = new AtomicFile(reservePath).openRead()) { return loadUserInternal(userId, in, /* forBackup= */ false); } catch (Exception exceptionReadingReserveFile) { Slog.e(TAG, "Failed to read reserve copy: " + reservePath, exceptionReadingReserveFile); } catch (Exception e) { // Remove corrupted file and retry. file.failRead(in, e); return loadUserLocked(userId); } Slog.e(TAG, "Failed to read file " + path, e); } return null; } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, Loading
services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +24 −0 Original line number Diff line number Diff line Loading @@ -565,6 +565,22 @@ public class PackageManagerSettingsTests { verifyDistractionFlags(settingsUnderTest); } @Test public void testWriteCorruptReadPackageRestrictions() { final Settings settingsUnderTest = makeSettings(); populateDistractionFlags(settingsUnderTest); settingsUnderTest.writePackageRestrictionsLPr(0, /*sync=*/true); // Corrupt primary file. writeCorruptedPackageRestrictions(0); // now read and verify populateDefaultSettings(settingsUnderTest); settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); verifyDistractionFlags(settingsUnderTest); } @Test public void testReadWritePackageRestrictionsAsync() { final Settings settingsWrite = makeSettings(); Loading Loading @@ -1811,6 +1827,14 @@ public class PackageManagerSettingsTests { .getBytes()); } private void writeCorruptedPackageRestrictions(final int userId) { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/" + userId + "/package-restrictions.xml"), ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<package-restrictions>\n" + " <pkg name=\"" + PACKAGE_NAME_1 + "\" ").getBytes()); } private static void writeStoppedPackagesXml() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"), ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" Loading