diff options
-rw-r--r-- | tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java | 63 | ||||
-rw-r--r-- | tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java | 21 |
2 files changed, 56 insertions, 28 deletions
diff --git a/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java b/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java index c52be7c2b0c6..8e012598c9eb 100644 --- a/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java +++ b/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java @@ -21,7 +21,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.content.Context; +import android.os.Bundle; import android.security.FileIntegrityManager; +import android.system.Os; +import android.system.OsConstants; import android.util.Log; import androidx.test.core.app.ApplicationProvider; @@ -35,8 +38,9 @@ import org.junit.Test; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; -import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; /** * Test helper that works with the host-side test to set up a test file, and to verify fs-verity @@ -47,8 +51,6 @@ public class Helper { private static final String FILENAME = "test.file"; - private static final long BLOCK_SIZE = 4096; - @Rule public final AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( @@ -58,7 +60,7 @@ public class Helper { @Test public void prepareTest() throws Exception { Context context = ApplicationProvider.getApplicationContext(); - android.os.Bundle testArgs = InstrumentationRegistry.getArguments(); + Bundle testArgs = InstrumentationRegistry.getArguments(); String basename = testArgs.getString("basename"); context.deleteFile(basename); @@ -84,31 +86,52 @@ public class Helper { fim.setupFsVerity(context.getFileStreamPath(basename)); } + private static long getPageSize() { + String arch = System.getProperty("os.arch"); + Log.d(TAG, "os.arch=" + arch); + if ("x86_64".equals(arch)) { + // Override the fake 16K page size from cf_x86_64_pgagnostic. The real page size on + // x86_64 is always 4K. This test needs the real page size because it is testing I/O + // error reporting behavior that is dependent on the real page size. + return 4096; + } + return Os.sysconf(OsConstants._SC_PAGE_SIZE); + } + @Test public void verifyFileRead() throws Exception { Context context = ApplicationProvider.getApplicationContext(); - // Collect indices that the backing blocks are supposed to be corrupted. - android.os.Bundle testArgs = InstrumentationRegistry.getArguments(); + Bundle testArgs = InstrumentationRegistry.getArguments(); assertThat(testArgs).isNotNull(); String filePath = testArgs.getString("filePath"); - String csv = testArgs.getString("brokenBlockIndicesCsv"); - Log.d(TAG, "brokenBlockIndicesCsv: " + csv); - String[] strings = csv.split(","); - var corrupted = new ArrayList(strings.length); - for (int i = 0; i < strings.length; i++) { - corrupted.add(Integer.parseInt(strings[i])); + String csv = testArgs.getString("brokenByteIndicesCsv"); + Log.d(TAG, "brokenByteIndicesCsv: " + csv); + + // Build the set of pages that contain a corrupted byte. + final long pageSize = getPageSize(); + Set<Long> corruptedPageIndices = new HashSet(); + for (String s : csv.split(",")) { + long byteIndex = Long.parseLong(s); + long pageIndex = byteIndex / pageSize; + corruptedPageIndices.add(pageIndex); } - - // Expect the read to succeed or fail per the prior. - try (var file = new RandomAccessFile(filePath, "r")) { - long total_blocks = (file.length() + BLOCK_SIZE - 1) / BLOCK_SIZE; - for (int i = 0; i < (int) total_blocks; i++) { - file.seek(i * BLOCK_SIZE); - if (corrupted.contains(i)) { - Log.d(TAG, "Expecting read at block #" + i + " to fail"); + Log.d(TAG, "corruptedPageIndices=" + corruptedPageIndices); + + // Read bytes from the file and verify the expected result based on the containing page. + // (The kernel reports fs-verity errors at page granularity.) + final long stride = 1024; + // Using a stride that is a divisor of the page size ensures that the last page is tested. + assertThat(pageSize % stride).isEqualTo(0); + try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { + for (long byteIndex = 0; byteIndex < file.length(); byteIndex += stride) { + file.seek(byteIndex); + long pageIndex = byteIndex / pageSize; + if (corruptedPageIndices.contains(pageIndex)) { + Log.d(TAG, "Expecting read at byte #" + byteIndex + " to fail"); assertThrows(IOException.class, () -> file.read()); } else { + Log.d(TAG, "Expecting read at byte #" + byteIndex + " to succeed"); assertThat(file.readByte()).isEqualTo('1'); } } diff --git a/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java index b1d6e96dca9b..88d9e9e08dce 100644 --- a/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java +++ b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java @@ -33,13 +33,12 @@ import org.junit.runner.RunWith; /** * This test verifies fs-verity works end-to-end. There is a corresponding helper app. * - * <p>The helper app uses a FileIntegrityManager API to enable fs-verity to a file. The host test - * here * tampers with the file's backing storage, then tells the helper app to read and expect + * <p>The helper app uses a FileIntegrityManager API to enable fs-verity on a file. The host test + * here tampers with the file's backing storage, then tells the helper app to read and expect * success/failure on read. * - * <p>In order to make sure a block of the file is readable only if the underlying block on disk - * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical - * address against the block device. + * <p>Since the filesystem by design provides no way to corrupt fs-verity files itself, the test + * needs to bypass the filesystem and write directly to the block device to corrupt the files. */ @RootPermissionTest @RunWith(DeviceJUnit4ClassRunner.class) @@ -57,7 +56,7 @@ public class FsVerityHostTest extends BaseHostJUnit4Test { BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 8192); BlockDeviceWriter.dropCaches(device); - verifyRead(getTargetFilePath(), "0,2"); + verifyRead(getTargetFilePath(), "0,8192"); } @Test @@ -70,7 +69,7 @@ public class FsVerityHostTest extends BaseHostJUnit4Test { BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 128 * 4096 + 1); BlockDeviceWriter.dropCaches(device); - verifyRead(getTargetFilePath(), "1,100,128"); + verifyRead(getTargetFilePath(), "4096,409600,524289"); } private String getTargetFilePath() throws DeviceNotAvailableException { @@ -87,11 +86,17 @@ public class FsVerityHostTest extends BaseHostJUnit4Test { assertThat(runDeviceTests(options)).isTrue(); } + /** + * Verifies the read success/failure expectation given the corrupted byte indices in the file. + * + * @param path the remote file path to read. + * @param indicesCsv a comma-separated list of indices of bytes that were corrupted. + */ private void verifyRead(String path, String indicesCsv) throws Exception { DeviceTestRunOptions options = new DeviceTestRunOptions(TARGET_PACKAGE); options.setTestClassName(TARGET_PACKAGE + ".Helper"); options.setTestMethodName("verifyFileRead"); - options.addInstrumentationArg("brokenBlockIndicesCsv", indicesCsv); + options.addInstrumentationArg("brokenByteIndicesCsv", indicesCsv); options.addInstrumentationArg("filePath", getTargetFilePath()); assertThat(runDeviceTests(options)).isTrue(); } |