Merge "Add crash detection and recovery." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
bf01409e58
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user