Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ea42a51 authored by Victor Hsieh's avatar Victor Hsieh Committed by Eric Biggers
Browse files

Update test expectation in FsVerityHostTest for 16K page

Test: 'atest FsVerityTest' on devices with 4K, 16K,
      and "simulated" 16K page size
Bug: 397562534
Flag: TEST_ONLY
Change-Id: I53662a16415a2471a7ac27559123a0e42f6ea762
parent cfe6940d
Loading
Loading
Loading
Loading
+43 −20
Original line number Diff line number Diff line
@@ -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');
                }
            }
+13 −8
Original line number Diff line number Diff line
@@ -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();
    }