Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d072c872 authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Store a reserve copy of packages.xml and use if necessary.

Bug: 253568736
Test: atest PackageManagerSettingsTests android.content.pm.cts.PackageManagerTest
Change-Id: I3351301adc446eab2e290044bd60fb29d65d71d8
parent e190822d
Loading
Loading
Loading
Loading
+94 −35
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -370,6 +371,8 @@ public final class Settings implements Watchable, Snappable {

    // Current settings file.
    private final File mSettingsFilename;
    // Reserve copy of the current settings file.
    private final File mSettingsReserveCopyFilename;
    // Previous settings file.
    // Removed when the current settings file successfully stored.
    private final File mPreviousSettingsFilename;
@@ -640,6 +643,7 @@ public final class Settings implements Watchable, Snappable {
        mRuntimePermissionsPersistence = null;
        mPermissionDataProvider = null;
        mSettingsFilename = null;
        mSettingsReserveCopyFilename = null;
        mPreviousSettingsFilename = null;
        mPackageListFilename = null;
        mStoppedPackagesFilename = null;
@@ -711,6 +715,7 @@ public final class Settings implements Watchable, Snappable {
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
        mSettingsFilename = new File(mSystemDir, "packages.xml");
        mSettingsReserveCopyFilename = new File(mSystemDir, "packages.xml.reservecopy");
        mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
        mPackageListFilename = new File(mSystemDir, "packages.list");
        FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -752,6 +757,7 @@ public final class Settings implements Watchable, Snappable {
        mLock = null;
        mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
        mSettingsFilename = null;
        mSettingsReserveCopyFilename = null;
        mPreviousSettingsFilename = null;
        mPackageListFilename = null;
        mStoppedPackagesFilename = null;
@@ -2681,12 +2687,25 @@ public final class Settings implements Watchable, Snappable {

            // New settings successfully written, old ones are no longer needed.
            mPreviousSettingsFilename.delete();
            mSettingsReserveCopyFilename.delete();

            FileUtils.setPermissions(mSettingsFilename.toString(),
                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                    -1, -1);

            try {
                FileUtils.copy(mSettingsFilename, mSettingsReserveCopyFilename);
            } catch (IOException e) {
                Slog.e(TAG, "Failed to backup settings", e);
            }

            try {
                VerityUtils.setUpFsverity(mSettingsFilename.getAbsolutePath());
                VerityUtils.setUpFsverity(mSettingsReserveCopyFilename.getAbsolutePath());
            } catch (IOException e) {
                Slog.e(TAG, "Failed to verity-protect settings", e);
            }

            writeKernelMappingLPr();
            writePackageListLPr();
            writeAllUsersPackageRestrictionsLPr(sync);
@@ -3117,11 +3136,23 @@ public final class Settings implements Watchable, Snappable {
        }
    }

    boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
    boolean readSettingsLPw(@NonNull Computer computer, @NonNull List<UserInfo> users,
            ArrayMap<String, Long> originalFirstInstallTimes) {
        mPendingPackages.clear();
        mPastSignatures.clear();
        mKeySetRefs.clear();
        mInstallerPackages.clear();
        originalFirstInstallTimes.clear();

        File file = null;
        FileInputStream str = null;

        try {
            // Check if the previous write was incomplete.
            if (mPreviousSettingsFilename.exists()) {
                try {
                str = new FileInputStream(mPreviousSettingsFilename);
                    file = mPreviousSettingsFilename;
                    str = new FileInputStream(file);
                    mReadMessages.append("Reading from backup settings file\n");
                    PackageManagerService.reportSettingsProblem(Log.INFO,
                            "Need to read from backup settings file");
@@ -3132,35 +3163,36 @@ public final class Settings implements Watchable, Snappable {
                                + mSettingsFilename);
                        mSettingsFilename.delete();
                    }
                    // Ignore reserve copy as well.
                    mSettingsReserveCopyFilename.delete();
                } catch (java.io.IOException e) {
                    // We'll try for the normal settings file.
                }
            }

        mPendingPackages.clear();
        mPastSignatures.clear();
        mKeySetRefs.clear();
        mInstallerPackages.clear();

        // If any user state doesn't have a first install time, e.g., after an OTA,
        // use the pre OTA firstInstallTime timestamp. This is because we migrated from per package
        // firstInstallTime to per user-state. Without this, OTA can cause this info to be lost.
        final ArrayMap<String, Long> originalFirstInstallTimes = new ArrayMap<>();

        try {
            if (str == null) {
                if (!mSettingsFilename.exists()) {
                if (mSettingsFilename.exists()) {
                    // Using packages.xml.
                    file = mSettingsFilename;
                    str = new FileInputStream(file);
                } else if (mSettingsReserveCopyFilename.exists()) {
                    // Using reserve copy.
                    file = mSettingsReserveCopyFilename;
                    str = new FileInputStream(file);
                    mReadMessages.append("Reading from reserve copy settings file\n");
                    PackageManagerService.reportSettingsProblem(Log.INFO,
                            "Need to read from reserve copy settings file");
                }
            }
            if (str == null) {
                // No available data sources.
                mReadMessages.append("No settings file found\n");
                PackageManagerService.reportSettingsProblem(Log.INFO,
                        "No settings file; creating initial state");
                    // It's enough to just touch version details to create them
                    // with default values
                // Not necessary, but will avoid wtf-s in the "finally" section.
                findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
                findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
                return false;
            }
                str = new FileInputStream(mSettingsFilename);
            }
            final TypedXmlPullParser parser = Xml.resolvePullParser(str);

            int type;
@@ -3280,6 +3312,33 @@ public final class Settings implements Watchable, Snappable {
            mReadMessages.append("Error reading: " + e.toString());
            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
            Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);

            // Remove corrupted file and retry.
            Slog.e(TAG,
                    "Error reading package manager settings, removing " + file + " and retrying.",
                    e);
            file.delete();

            // Ignore the result to not mark this as a "first boot".
            readSettingsLPw(computer, users, originalFirstInstallTimes);
        }

        return true;
    }

    /**
     * @return false if settings file is missing (i.e. during first boot), true otherwise
     */
    boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
        // If any user state doesn't have a first install time, e.g., after an OTA,
        // use the pre OTA firstInstallTime timestamp. This is because we migrated from per package
        // firstInstallTime to per user-state. Without this, OTA can cause this info to be lost.
        final ArrayMap<String, Long> originalFirstInstallTimes = new ArrayMap<>();

        try {
            if (!readSettingsLPw(computer, users, originalFirstInstallTimes)) {
                return false;
            }
        } finally {
            if (!mVersion.containsKey(StorageManager.UUID_PRIVATE_INTERNAL)) {
                Slog.wtf(PackageManagerService.TAG,
+79 −4
Original line number Diff line number Diff line
@@ -92,8 +92,11 @@ import org.mockito.MockitoAnnotations;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
@@ -141,8 +144,7 @@ public class PackageManagerSettingsTests {

    /** make sure our initialized KeySetManagerService metadata matches packages.xml */
    @Test
    public void testReadKeySetSettings()
            throws ReflectiveOperationException, IllegalAccessException {
    public void testReadKeySetSettings() throws Exception {
        /* write out files and read */
        writeOldFiles();
        Settings settings = makeSettings();
@@ -150,6 +152,29 @@ public class PackageManagerSettingsTests {
        verifyKeySetMetaData(settings);
    }

    // Same as above but use the reserve copy.
    @Test
    public void testReadReserveCopyKeySetSettings() throws Exception {
        /* write out files and read */
        writeReserveCopyOldFiles();
        Settings settings = makeSettings();
        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
        verifyKeySetMetaData(settings);
    }

    // Same as above but packages.xml is malformed.
    @Test
    public void testReadMalformedPackagesXmlKeySetSettings() throws Exception {
        // write out files
        writeReserveCopyOldFiles();
        // write corrupted packages.xml
        writeCorruptedPackagesXml();

        Settings settings = makeSettings();
        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
        verifyKeySetMetaData(settings);
    }

    /** read in data, write it out, and read it back in.  Verify same. */
    @Test
    public void testWriteKeySetSettings()
@@ -165,6 +190,39 @@ public class PackageManagerSettingsTests {
        verifyKeySetMetaData(settings);
    }

    // Same as above, but corrupt the primary.xml in process.
    @Test
    public void testWriteCorruptReadKeySetSettings() throws Exception {
        // write out files and read
        writeOldFiles();
        Settings settings = makeSettings();
        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));

        // write out
        settings.writeLPr(computer, /*sync=*/true);

        File filesDir = InstrumentationRegistry.getContext().getFilesDir();
        File packageXml = new File(filesDir, "system/packages.xml");
        File packagesReserveCopyXml = new File(filesDir, "system/packages.xml.reservecopy");
        // Primary.
        assertTrue(packageXml.exists());
        // Reserve copy.
        assertTrue(packagesReserveCopyXml.exists());
        // Temporary backup.
        assertFalse(new File(filesDir, "packages-backup.xml").exists());

        // compare two copies, make sure they are the same
        assertTrue(Arrays.equals(Files.readAllBytes(Path.of(packageXml.getAbsolutePath())),
                Files.readAllBytes(Path.of(packagesReserveCopyXml.getAbsolutePath()))));

        // write corrupted packages.xml
        writeCorruptedPackagesXml();

        // read back in and verify the same
        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
        verifyKeySetMetaData(settings);
    }

    @Test
    public void testSettingsReadOld() {
        // Write delegateshellthe package files and make sure they're parsed properly the first time
@@ -1572,8 +1630,18 @@ public class PackageManagerSettingsTests {
        }
    }

    private void writePackagesXml() {
    private void writeCorruptedPackagesXml() {
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.xml"),
                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                        + "<packages>"
                        + "<last-platform-version internal=\"15\" external=\"0\" />"
                        + "<permission-trees>"
                        + "<item name=\"com.google.android.permtree\""
                ).getBytes());
    }

    private void writePackagesXml(String fileName) {
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), fileName),
                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                + "<packages>"
                + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />"
@@ -1715,7 +1783,14 @@ public class PackageManagerSettingsTests {

    private void writeOldFiles() {
        deleteSystemFolder();
        writePackagesXml();
        writePackagesXml("system/packages.xml");
        writeStoppedPackagesXml();
        writePackagesList();
    }

    private void writeReserveCopyOldFiles() {
        deleteSystemFolder();
        writePackagesXml("system/packages.xml.reservecopy");
        writeStoppedPackagesXml();
        writePackagesList();
    }