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

Commit 00700fce authored by Alex Buynytskyy's avatar Alex Buynytskyy Committed by Android (Google) Code Review
Browse files

Merge "Protect shortcut files from corruption."

parents d96a716f 91e73702
Loading
Loading
Loading
Loading
+43 −16
Original line number Diff line number Diff line
@@ -119,10 +119,9 @@ 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 libcore.io.IoUtils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -207,6 +206,10 @@ public class ShortcutService extends IShortcutService.Stub {
    @VisibleForTesting
    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";

    @VisibleForTesting
    static final String FILENAME_USER_PACKAGES_RESERVE_COPY =
            FILENAME_USER_PACKAGES + ".reservecopy";

    static final String DIRECTORY_BITMAPS = "bitmaps";

    private static final String TAG_ROOT = "root";
@@ -1055,6 +1058,11 @@ public class ShortcutService extends IShortcutService.Stub {
        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);
    }

    @GuardedBy("mLock")
    private void saveUserLocked(@UserIdInt int userId) {
        final File path = getUserFile(userId);
@@ -1062,6 +1070,9 @@ public class ShortcutService extends IShortcutService.Stub {
            Slog.d(TAG, "Saving to " + path);
        }

        final File reservePath = getReserveCopyUserFile(userId);
        reservePath.delete();

        path.getParentFile().mkdirs();
        final AtomicFile file = new AtomicFile(path);
        FileOutputStream os = null;
@@ -1079,6 +1090,23 @@ public class ShortcutService extends IShortcutService.Stub {
            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);
    }

@@ -1117,26 +1145,25 @@ public class ShortcutService extends IShortcutService.Stub {
        if (DEBUG || DEBUG_REBOOT) {
            Slog.d(TAG, "Loading from " + path);
        }
        final AtomicFile file = new AtomicFile(path);

        final FileInputStream in;
        try {
            in = file.openRead();
        try (FileInputStream in = new AtomicFile(path).openRead()) {
            return loadUserInternal(userId, in, /* forBackup= */ false);
        } catch (FileNotFoundException e) {
            if (DEBUG || DEBUG_REBOOT) {
                Slog.d(TAG, "Not found " + path);
            }
            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);
            }
        try {
            final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
            return ret;
        } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
            return null;
        } finally {
            IoUtils.closeQuietly(in);
            Slog.e(TAG, "Failed to read file " + path, e);
        }
        return null;
    }

    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
+152 −0
Original line number Diff line number Diff line
@@ -118,10 +118,12 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -4005,6 +4007,156 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
        // TODO Check all other fields
    }

    public void testSaveCorruptAndLoadUser() throws Exception {
        // First, create some shortcuts and save.
        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                    getTestContext().getResources(), R.drawable.icon2));

            final ShortcutInfo si1 = makeShortcut(
                    "s1",
                    "title1-1",
                    makeComponent(ShortcutActivity.class),
                    icon1,
                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
                            "key1", "val1", "nest", makeBundle("key", 123)),
                    /* weight */ 10);

            final ShortcutInfo si2 = makeShortcut(
                    "s2",
                    "title1-2",
                    /* activity */ null,
                    icon2,
                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                    /* weight */ 12);

            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));

            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
            assertEquals(2, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64);
            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                    getTestContext().getResources(), R.drawable.icon2));

            final ShortcutInfo si1 = makeShortcut(
                    "s1",
                    "title2-1",
                    makeComponent(ShortcutActivity.class),
                    icon1,
                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
                            "key1", "val1", "nest", makeBundle("key", 123)),
                    /* weight */ 10);

            final ShortcutInfo si2 = makeShortcut(
                    "s2",
                    "title2-2",
                    /* activity */ null,
                    icon2,
                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                    /* weight */ 12);

            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));

            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
            assertEquals(2, mManager.getRemainingCallCount());
        });

        mRunningUsers.put(USER_10, true);

        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                    getTestContext().getResources(), R.drawable.icon2));

            final ShortcutInfo si1 = makeShortcut(
                    "s1",
                    "title10-1-1",
                    makeComponent(ShortcutActivity.class),
                    icon1,
                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
                            "key1", "val1", "nest", makeBundle("key", 123)),
                    /* weight */ 10);

            final ShortcutInfo si2 = makeShortcut(
                    "s2",
                    "title10-1-2",
                    /* activity */ null,
                    icon2,
                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                    /* weight */ 12);

            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));

            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
            assertEquals(2, mManager.getRemainingCallCount());
        });

        // Save and corrupt the primary files.
        mService.saveDirtyInfo();
        try (Writer os = new FileWriter(mService.getUserFile(UserHandle.USER_SYSTEM))) {
            os.write("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<user locales=\"en\" last-app-scan-time2=\"14400000");
        }
        try (Writer os = new FileWriter(mService.getUserFile(USER_10))) {
            os.write("<?xml version='1.0' encoding='utf");
        }

        // Restore.
        initService();

        // Before the load, the map should be empty.
        assertEquals(0, mService.getShortcutsForTest().size());

        // this will pre-load the per-user info.
        mService.handleUnlockUser(UserHandle.USER_SYSTEM);

        // Now it's loaded.
        assertEquals(1, mService.getShortcutsForTest().size());

        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
                    mManager.getDynamicShortcuts()))), "s1", "s2");
            assertEquals(2, mManager.getRemainingCallCount());

            assertEquals("title1-1", getCallerShortcut("s1").getTitle());
            assertEquals("title1-2", getCallerShortcut("s2").getTitle());
        });
        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
                    mManager.getDynamicShortcuts()))), "s1", "s2");
            assertEquals(2, mManager.getRemainingCallCount());

            assertEquals("title2-1", getCallerShortcut("s1").getTitle());
            assertEquals("title2-2", getCallerShortcut("s2").getTitle());
        });

        // Start another user
        mService.handleUnlockUser(USER_10);

        // Now the size is 2.
        assertEquals(2, mService.getShortcutsForTest().size());

        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
                    mManager.getDynamicShortcuts()))), "s1", "s2");
            assertEquals(2, mManager.getRemainingCallCount());

            assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
            assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
        });

        // Try stopping the user
        mService.handleStopUser(USER_10);

        // Now it's unloaded.
        assertEquals(1, mService.getShortcutsForTest().size());

        // TODO Check all other fields
    }

    public void testCleanupPackage() {
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertTrue(mManager.setDynamicShortcuts(list(