Add test for font crash protection.
This test modifies the installed font file with block_device_writer.
FontManagerService should detect it and remove the modified file.
Bug: 176939176
Test: atest ApkVerityTest
Test: atest UpdatableSystemFontTest
Change-Id: I7da3f2911459619d5d56a94e091b912d67cb27d3
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 39dc9c2..e2d2eca 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,7 +16,10 @@
name: "ApkVerityTest",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
- static_libs: ["frameworks-base-hostutils"],
+ static_libs: [
+ "block_device_writer_jar",
+ "frameworks-base-hostutils",
+ ],
test_suites: ["general-tests", "vts"],
target_required: [
"block_device_writer_module",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 37fbc29..8f2d4bc 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -51,3 +51,9 @@
test_suites: ["general-tests", "pts", "vts"],
gtest: false,
}
+
+java_library_host {
+ name: "block_device_writer_jar",
+ srcs: ["src/**/*.java"],
+ libs: ["tradefed", "junit"],
+}
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
new file mode 100644
index 0000000..5c2c15b
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.blockdevicewriter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper for block_device_writer command.
+ *
+ * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
+ * 1. In Android.bp, add:
+ * <pre>
+ * target_required: ["block_device_writer_module"],
+ * </pre>
+ * 2. In AndroidText.xml, add:
+ * <pre>
+ * <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ * <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+ * </target_preparer>
+ * </pre>
+ */
+public final class BlockDeviceWriter {
+ private static final String EXECUTABLE = "/data/local/tmp/block_device_writer";
+
+ /**
+ * Modifies a byte of the file directly against the backing block storage.
+ *
+ * The effect can only be observed when the page cache is read from disk again. See
+ * {@link #dropCaches} for details.
+ */
+ public static void damageFileAgainstBlockDevice(ITestDevice device, String path,
+ long offsetOfTargetingByte)
+ throws DeviceNotAvailableException {
+ assertThat(path).startsWith("/data/");
+ ITestDevice.MountPointInfo mountPoint = device.getMountPointInfo("/data");
+ ArrayList<String> args = new ArrayList<>();
+ args.add(EXECUTABLE);
+ if ("f2fs".equals(mountPoint.type)) {
+ args.add("--use-f2fs-pinning");
+ }
+ args.add(mountPoint.filesystem);
+ args.add(path);
+ args.add(Long.toString(offsetOfTargetingByte));
+ CommandResult result = device.executeShellV2Command(String.join(" ", args));
+ assertWithMessage(
+ String.format("stdout=%s\nstderr=%s", result.getStdout(), result.getStderr()))
+ .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+ }
+
+ /**
+ * Drops file caches so that the result of {@link #damageFileAgainstBlockDevice} can be
+ * observed. If a process has an open FD or memory map of the damaged file, cache eviction won't
+ * happen and the damage cannot be observed.
+ */
+ public static void dropCaches(ITestDevice device) throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(
+ "sync && echo 1 > /proc/sys/vm/drop_caches");
+ assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+ }
+
+ public static void assertFileNotOpen(ITestDevice device, String path)
+ throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command("lsof " + path);
+ assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+ assertThat(result.getStdout()).isEmpty();
+ }
+
+ /**
+ * Checks if the give offset of a file can be read.
+ * This method will return false if the file has fs-verity enabled and is damaged at the offset.
+ */
+ public static boolean canReadByte(ITestDevice device, String filePath, long offset)
+ throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(
+ "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
+ return result.getStatus() == CommandStatus.SUCCESS;
+ }
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index d0eb9be..ab3572b 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -24,6 +24,7 @@
import android.platform.test.annotations.RootPermissionTest;
+import com.android.blockdevicewriter.BlockDeviceWriter;
import com.android.fsverity.AddFsVerityCertRule;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -334,22 +335,23 @@
long offsetFirstByte = 0;
// The first two pages should be both readable at first.
- assertTrue(canReadByte(apkPath, offsetFirstByte));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
- assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+ offsetFirstByte + FSVERITY_PAGE_SIZE));
}
// Damage the file directly against the block device.
damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
// Expect actual read from disk to fail but only at damaged page.
- dropCaches();
- assertFalse(canReadByte(apkPath, offsetFirstByte));
+ BlockDeviceWriter.dropCaches(mDevice);
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
long lastByteOfTheSamePage =
offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
- assertFalse(canReadByte(apkPath, lastByteOfTheSamePage));
- assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1));
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage + 1));
}
}
@@ -362,21 +364,22 @@
long offsetOfLastByte = apkSize - 1;
// The first two pages should be both readable at first.
- assertTrue(canReadByte(apkPath, offsetOfLastByte));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
- assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+ offsetOfLastByte - FSVERITY_PAGE_SIZE));
}
// Damage the file directly against the block device.
damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
// Expect actual read from disk to fail but only at damaged page.
- dropCaches();
- assertFalse(canReadByte(apkPath, offsetOfLastByte));
+ BlockDeviceWriter.dropCaches(mDevice);
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
- assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
- assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage - 1));
}
}
@@ -395,8 +398,8 @@
// from filesystem cache. Forcing GC workarounds the problem.
int retry = 5;
for (; retry > 0; retry--) {
- dropCaches();
- if (!canReadByte(path, kTargetOffset)) {
+ BlockDeviceWriter.dropCaches(mDevice);
+ if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
break;
}
try {
@@ -451,16 +454,6 @@
return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
}
- private void dropCaches() throws DeviceNotAvailableException {
- expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches");
- }
-
- private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException {
- CommandResult result = mDevice.executeShellV2Command(
- "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
- return result.getStatus() == CommandStatus.SUCCESS;
- }
-
private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
CommandResult result = mDevice.executeShellV2Command(cmd);
assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,