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

Commit 27f2db93 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update test expectation in FsVerityHostTest for 16K page" into main

parents 0658db60 7ea42a51
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();
    }