diff options
| author | 2021-02-02 04:44:46 +0000 | |
|---|---|---|
| committer | 2021-02-02 04:44:46 +0000 | |
| commit | bf01409e589888957a54b0c00650ea683662a10d (patch) | |
| tree | 552aa56927d57b1776867ccd7dc57e99b303f44b | |
| parent | 981c7e047f744a4db29d177c919ea4e6d1624b12 (diff) | |
| parent | de417e735ede24b0a3513772b1653996d0c05575 (diff) | |
Merge "Add crash detection and recovery." into sc-dev
5 files changed, 232 insertions, 14 deletions
diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java new file mode 100644 index 000000000000..b082b25aea02 --- /dev/null +++ b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java @@ -0,0 +1,92 @@ +/* + * 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(); + } + } +} diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 7461405c9a5f..8e5215b49f16 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -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."); } - mUpdatableFontDir.installFontFile(fd, pkcs7Signature); - // Create updated font map in the next getSerializedSystemFontMap() call. - mSerializedFontMap = null; + try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { + mUpdatableFontDir.installFontFile(fd, pkcs7Signature); + mSerializedFontMap = buildNewSerializedFontMap(); + } } } @@ -246,7 +272,12 @@ public final class FontManagerService extends IFontManager.Stub { FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED, "The font updater is disabled."); } - mUpdatableFontDir.clearUpdates(); + synchronized (FontManagerService.this) { + try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { + mUpdatableFontDir.clearUpdates(); + mSerializedFontMap = buildNewSerializedFontMap(); + } + } } /* package */ Map<String, File> getFontFileMap() { diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index b0bc65bc7787..0cb704507f7a 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -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; diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java new file mode 100644 index 000000000000..275e7c7fec04 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java @@ -0,0 +1,77 @@ +/* + * 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(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index d06779048837..833103142ccf 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -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); |