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

This commit is contained in:
Kohsuke Yatoh
2021-02-02 04:44:46 +00:00
committed by Android (Google) Code Review
5 changed files with 232 additions and 14 deletions

View File

@@ -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();
}
}
}

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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);