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

Commit 9afa1eb9 authored by Kohsuke Yatoh's avatar Kohsuke Yatoh Committed by Automerger Merge Worker
Browse files

Merge "Add crash detection and recovery." into sc-dev am: bf01409e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13431546

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I42699191bf2b62000de552186a32db49ebc06717
parents 34320f1a bf01409e
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.graphics.fonts;

import android.annotation.NonNull;
import android.util.Slog;

import java.io.File;
import java.io.IOException;

/**
 * A class to detect font-related native crash.
 *
 * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected,
 * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them,
 * we use a marker file to detect crash.
 * <ol>
 *     <li>Create a marker file before reading fs-verity protected font files.
 *     <li>Delete the marker file after reading font files successfully.
 *     <li>If the marker file is found in the next process startup, it means that the process
 *         crashed before. We will delete font files to prevent crash loop.
 * </ol>
 *
 * <p>Example usage:
 * <pre>
 *     FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file"));
 *     if (detector.hasCrashed()) {
 *         // Do cleanup
 *     }
 *     try (FontCrashDetector.MonitoredBlock b = detector.start()) {
 *         // Read files
 *     }
 * </pre>
 *
 * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring
 * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight.
 * Please use this class sparingly with caution.
 */
/* package */ final class FontCrashDetector {

    private static final String TAG = "FontCrashDetector";

    @NonNull
    private final File mMarkerFile;

    /* package */ FontCrashDetector(@NonNull File markerFile) {
        mMarkerFile = markerFile;
    }

    /* package */ boolean hasCrashed() {
        return mMarkerFile.exists();
    }

    /* package */ void clear() {
        if (!mMarkerFile.delete()) {
            Slog.e(TAG, "Could not delete marker file: " + mMarkerFile);
        }
    }

    /** Starts crash monitoring. */
    /* package */ MonitoredBlock start() {
        try {
            mMarkerFile.createNewFile();
        } catch (IOException e) {
            Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e);
        }
        return new MonitoredBlock();
    }

    /** A helper class to monitor crash with try-with-resources syntax. */
    /* package */ class MonitoredBlock implements AutoCloseable {
        /** Ends crash monitoring. */
        @Override
        public void close() {
            clear();
        }
    }
}
+42 −11
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ public final class FontManagerService extends IFontManager.Stub {
    private static final String TAG = "FontManagerService";

    private static final String FONT_FILES_DIR = "/data/fonts/files";
    private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt";

    @Override
    public FontConfig getFontConfig() throws RemoteException {
@@ -179,6 +180,13 @@ public final class FontManagerService extends IFontManager.Stub {
        }
    }

    @NonNull
    private final Context mContext;

    @GuardedBy("FontManagerService.this")
    @NonNull
    private final FontCrashDetector mFontCrashDetector;

    @Nullable
    private final UpdatableFontDir mUpdatableFontDir;

@@ -188,7 +196,9 @@ public final class FontManagerService extends IFontManager.Stub {

    private FontManagerService(Context context) {
        mContext = context;
        mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE));
        mUpdatableFontDir = createUpdatableFontDir();
        initialize();
    }

    @Nullable
@@ -201,20 +211,35 @@ public final class FontManagerService extends IFontManager.Stub {
                new OtfFontFileParser(), new FsverityUtilImpl());
    }


    @NonNull
    private final Context mContext;
    private void initialize() {
        synchronized (FontManagerService.this) {
            if (mUpdatableFontDir == null) {
                mSerializedFontMap = buildNewSerializedFontMap();
                return;
            }
            if (mFontCrashDetector.hasCrashed()) {
                Slog.i(TAG, "Crash detected. Clearing font updates.");
                try {
                    mUpdatableFontDir.clearUpdates();
                } catch (SystemFontException e) {
                    Slog.e(TAG, "Failed to clear updates.", e);
                }
                mFontCrashDetector.clear();
            }
            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
                mUpdatableFontDir.loadFontFileMap();
                mSerializedFontMap = buildNewSerializedFontMap();
            }
        }
    }

    @NonNull
    public Context getContext() {
        return mContext;
    }

    @NonNull /* package */ SharedMemory getCurrentFontMap() {
    @Nullable /* package */ SharedMemory getCurrentFontMap() {
        synchronized (FontManagerService.this) {
            if (mSerializedFontMap == null) {
                mSerializedFontMap = buildNewSerializedFontMap();
            }
            return mSerializedFontMap;
        }
    }
@@ -234,9 +259,10 @@ public final class FontManagerService extends IFontManager.Stub {
                        FontManager.RESULT_ERROR_VERSION_MISMATCH,
                        "The base config version is older than current.");
            }
            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
                mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
            // Create updated font map in the next getSerializedSystemFontMap() call.
            mSerializedFontMap = null;
                mSerializedFontMap = buildNewSerializedFontMap();
            }
        }
    }

@@ -246,7 +272,12 @@ public final class FontManagerService extends IFontManager.Stub {
                    FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
                    "The font updater is disabled.");
        }
        synchronized (FontManagerService.this) {
            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
                mUpdatableFontDir.clearUpdates();
                mSerializedFontMap = buildNewSerializedFontMap();
            }
        }
    }

    /* package */ Map<String, File> getFontFileMap() {
+1 −3
Original line number Diff line number Diff line
@@ -142,11 +142,9 @@ final class UpdatableFontDir {
        mFsverityUtil = fsverityUtil;
        mConfigFile = configFile;
        mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
        loadFontFileMap();
    }

    private void loadFontFileMap() {
        // TODO: SIGBUS crash protection
    /* package */ void loadFontFileMap() {
        synchronized (UpdatableFontDir.this) {
            boolean success = false;

+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.graphics.fonts;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.os.FileUtils;
import android.platform.test.annotations.Presubmit;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.File;

@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class FontCrashDetectorTest {

    private File mCacheDir;

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @Before
    public void setUp() {
        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest");
        FileUtils.deleteContentsAndDir(mCacheDir);
        mCacheDir.mkdirs();
    }

    @Test
    public void detectCrash() throws Exception {
        // Prepare a marker file.
        File file = new File(mCacheDir, "detectCrash");
        assertThat(file.createNewFile()).isTrue();

        FontCrashDetector detector = new FontCrashDetector(file);
        assertThat(detector.hasCrashed()).isTrue();

        detector.clear();
        assertThat(detector.hasCrashed()).isFalse();
        assertThat(file.exists()).isFalse();
    }

    @Test
    public void monitorCrash() {
        File file = new File(mCacheDir, "monitorCrash");
        FontCrashDetector detector = new FontCrashDetector(file);
        assertThat(detector.hasCrashed()).isFalse();

        FontCrashDetector.MonitoredBlock block = detector.start();
        assertThat(file.exists()).isTrue();

        block.close();
        assertThat(file.exists()).isFalse();
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -147,6 +147,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dirForPreparation.loadFontFileMap();
        assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
                .isEqualTo(expectedModifiedDate);
        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
@@ -162,6 +163,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();
        assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
        assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
        assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -177,6 +179,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();
        assertThat(dir.getFontFileMap()).isEmpty();
    }

@@ -187,6 +190,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dirForPreparation.loadFontFileMap();
        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -199,6 +203,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();
        assertThat(dir.getFontFileMap()).isEmpty();
        // All font dirs (including dir for "bar.ttf") should be deleted.
        assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -211,6 +216,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dirForPreparation.loadFontFileMap();
        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -224,6 +230,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();
        assertThat(dir.getFontFileMap()).isEmpty();
        // All font dirs (including dir for "bar.ttf") should be deleted.
        assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -236,6 +243,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dirForPreparation.loadFontFileMap();
        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -250,6 +258,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();
        // For foo.ttf, preinstalled font (revision 5) should be used.
        assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
        // For bar.ttf, updated font (revision 4) should be used.
@@ -268,6 +277,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                new File("/dev/null"));
        dir.loadFontFileMap();
        assertThat(dir.getFontFileMap()).isEmpty();
    }

@@ -278,6 +288,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        installFontFile(dir, "test,1", GOOD_SIGNATURE);
        assertThat(dir.getFontFileMap()).containsKey("test.ttf");
@@ -295,6 +306,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        installFontFile(dir, "test,1", GOOD_SIGNATURE);
        Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
@@ -313,6 +325,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        installFontFile(dir, "test,2", GOOD_SIGNATURE);
        try {
@@ -333,6 +346,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        installFontFile(dir, "foo,1", GOOD_SIGNATURE);
        installFontFile(dir, "bar,2", GOOD_SIGNATURE);
@@ -349,6 +363,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        try {
            installFontFile(dir, "test,1", "Invalid signature");
@@ -368,6 +383,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        try {
            installFontFile(dir, "test,1", GOOD_SIGNATURE);
@@ -398,6 +414,7 @@ public final class UpdatableFontDirTest {
            UpdatableFontDir dir = new UpdatableFontDir(
                    mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                    readonlyFile);
            dir.loadFontFileMap();

            try {
                installFontFile(dir, "test,2", GOOD_SIGNATURE);
@@ -429,6 +446,7 @@ public final class UpdatableFontDirTest {
                        return 0;
                    }
                }, fakeFsverityUtil, mConfigFile);
        dir.loadFontFileMap();

        try {
            installFontFile(dir, "foo,1", GOOD_SIGNATURE);
@@ -456,6 +474,7 @@ public final class UpdatableFontDirTest {
                        return 0;
                    }
                }, fakeFsverityUtil, mConfigFile);
        dir.loadFontFileMap();

        try {
            installFontFile(dir, "foo,1", GOOD_SIGNATURE);
@@ -491,6 +510,7 @@ public final class UpdatableFontDirTest {
        UpdatableFontDir dir = new UpdatableFontDir(
                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                mConfigFile);
        dir.loadFontFileMap();

        try {
            installFontFile(dir, "foo,1", GOOD_SIGNATURE);