New integration test for fs-verity install and on-access verification

There are two categories of tests:

1. Package installation with .fsv_sig
  * .apk, .dm, including the split ones should all or none be installed
     with their corresponding .fsv_sig files

2. End-to-end fs-verity test of on-access verification
  * When fs-verity is enabled to a file, if the on-disk content is
    changed, the read should fail.

See class comment in ApkVerityTest.java for the test details.

Brief directory layout overview:
* src/
  - Actual test
* ApkVerityTestApp/
  - Dummy app for testing, including a split
* testdata/
  - Some artifacts, signing key and fs-verity signatures
* block_device_writer/
  - Helper binary for write a file directly on disk

Test: atest
Bug: 112039386
Change-Id: I3b8229037db682f36fda9d5cafd14caf6b39501d
This commit is contained in:
Victor Hsieh
2019-02-06 09:56:58 -08:00
parent a620de30f4
commit e82b9fb584
18 changed files with 1223 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2019 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.
java_test_host {
name: "ApkVerityTests",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
test_suites: ["general-tests"],
target_required: [
"block_device_writer_module",
"ApkVerityTestApp",
"ApkVerityTestAppSplit",
],
data: [
":ApkVerityTestCertDer",
":ApkVerityTestAppFsvSig",
":ApkVerityTestAppDm",
":ApkVerityTestAppDmFsvSig",
":ApkVerityTestAppSplitFsvSig",
":ApkVerityTestAppSplitDm",
":ApkVerityTestAppSplitDmFsvSig",
],
}

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<configuration description="APK fs-verity integration/regression test">
<option name="test-suite-tag" value="apct" />
<!-- This test requires root to write against block device. -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- Disable package verifier prevents it holding the target APK's fd that prevents cache
eviction. -->
<option name="set-global-setting" key="package_verifier_enable" value="0" />
<option name="restore-settings" value="true" />
<!-- Skip in order to prevent reboot that confuses the test flow. -->
<option name="force-skip-system-props" value="true" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
<option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="ApkVerityTests.jar" />
</test>
</configuration>

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2019 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.
android_test_helper_app {
name: "ApkVerityTestApp",
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.java"],
}
android_test_helper_app {
name: "ApkVerityTestAppSplit",
manifest: "feature_split/AndroidManifest.xml",
srcs: ["src/**/*.java"],
aaptflags: [
"--custom-package com.android.apkverity.feature_x",
"--package-id 0x80",
],
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2019 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.apkverity">
<application>
<activity android:name=".DummyActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2019 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.apkverity"
android:isFeatureSplit="true"
split="feature_x">
<application>
<activity android:name=".feature_x.DummyActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 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.apkverity.feature_x;
import android.app.Activity;
/** Dummy class just to generate some dex */
public class DummyActivity extends Activity {}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 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.apkverity;
import android.app.Activity;
/** Dummy class just to generate some dex */
public class DummyActivity extends Activity {}

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2019 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.
// This is a cc_test just because it supports test_suites. This should be converted to something
// like cc_binary_test_helper once supported.
cc_test {
// Depending on how the test runs, the executable may be uploaded to different location.
// Before the bug in the file pusher is fixed, workaround by making the name unique.
// See b/124718249#comment12.
name: "block_device_writer_module",
stem: "block_device_writer",
srcs: ["block_device_writer.cpp"],
cflags: ["-Wall", "-Werror", "-Wextra", "-g"],
shared_libs: ["libbase", "libutils"],
test_suites: ["general-tests"],
gtest: false,
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (C) 2019 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.
*/
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <errno.h>
#include <fcntl.h>
#include <linux/fiemap.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/unique_fd.h>
// This program modifies a file at given offset, but directly against the block
// device, purposely to bypass the filesystem. Note that the change on block
// device may not reflect the same way when read from filesystem, for example,
// when the file is encrypted on disk.
//
// Only one byte is supported for now just so that we don't need to handle the
// case when the range crosses different "extents".
//
// References:
// https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
// https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
ssize_t get_logical_block_size(const char* block_device) {
android::base::unique_fd fd(open(block_device, O_RDONLY));
if (fd.get() < 0) {
fprintf(stderr, "open %s failed\n", block_device);
return -1;
}
int size;
if (ioctl(fd, BLKSSZGET, &size) < 0) {
fprintf(stderr, "ioctl(BLKSSZGET) failed: %s\n", strerror(errno));
return -1;
}
return size;
}
int64_t get_physical_offset(const char* file_name, uint64_t byte_offset) {
android::base::unique_fd fd(open(file_name, O_RDONLY));
if (fd.get() < 0) {
fprintf(stderr, "open %s failed\n", file_name);
return -1;
}
const int map_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
char fiemap_buffer[map_size] = {0};
struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(&fiemap_buffer);
fiemap->fm_flags = FIEMAP_FLAG_SYNC;
fiemap->fm_start = byte_offset;
fiemap->fm_length = 1;
fiemap->fm_extent_count = 1;
int ret = ioctl(fd.get(), FS_IOC_FIEMAP, fiemap);
if (ret < 0) {
fprintf(stderr, "ioctl(FS_IOC_FIEMAP) failed: %s\n", strerror(errno));
return -1;
}
if (fiemap->fm_mapped_extents != 1) {
fprintf(stderr, "fm_mapped_extents != 1 (is %d)\n",
fiemap->fm_mapped_extents);
return -1;
}
struct fiemap_extent* extent = &fiemap->fm_extents[0];
printf(
"logical offset: %llu, physical offset: %llu, length: %llu, "
"flags: %x\n",
extent->fe_logical, extent->fe_physical, extent->fe_length,
extent->fe_flags);
if (extent->fe_flags & (FIEMAP_EXTENT_UNKNOWN |
FIEMAP_EXTENT_UNWRITTEN)) {
fprintf(stderr, "Failed to locate physical offset safely\n");
return -1;
}
return extent->fe_physical + (byte_offset - extent->fe_logical);
}
int read_block_from_device(const char* device_path, uint64_t block_offset,
ssize_t block_size, char* block_buffer) {
assert(block_offset % block_size == 0);
android::base::unique_fd fd(open(device_path, O_RDONLY | O_DIRECT));
if (fd.get() < 0) {
fprintf(stderr, "open %s failed\n", device_path);
return -1;
}
ssize_t retval =
TEMP_FAILURE_RETRY(pread(fd, block_buffer, block_size, block_offset));
if (retval != block_size) {
fprintf(stderr, "read returns error or incomplete result (%zu): %s\n",
retval, strerror(errno));
return -1;
}
return 0;
}
int write_block_to_device(const char* device_path, uint64_t block_offset,
ssize_t block_size, char* block_buffer) {
assert(block_offset % block_size == 0);
android::base::unique_fd fd(open(device_path, O_WRONLY | O_DIRECT));
if (fd.get() < 0) {
fprintf(stderr, "open %s failed\n", device_path);
return -1;
}
ssize_t retval = TEMP_FAILURE_RETRY(
pwrite(fd.get(), block_buffer, block_size, block_offset));
if (retval != block_size) {
fprintf(stderr, "write returns error or incomplete result (%zu): %s\n",
retval, strerror(errno));
return -1;
}
return 0;
}
int main(int argc, const char** argv) {
if (argc != 4) {
fprintf(stderr,
"Usage: %s block_dev filename byte_offset\n"
"\n"
"This program bypasses filesystem and damages the specified byte\n"
"at the physical position on <block_dev> corresponding to the\n"
"logical byte location in <filename>.\n",
argv[0]);
return -1;
}
const char* block_device = argv[1];
const char* file_name = argv[2];
uint64_t byte_offset = strtoull(argv[3], nullptr, 10);
ssize_t block_size = get_logical_block_size(block_device);
if (block_size < 0) {
return -1;
}
int64_t physical_offset_signed = get_physical_offset(file_name, byte_offset);
if (physical_offset_signed < 0) {
return -1;
}
uint64_t physical_offset = static_cast<uint64_t>(physical_offset_signed);
uint64_t offset_within_block = physical_offset % block_size;
uint64_t physical_block_offset = physical_offset - offset_within_block;
// Direct I/O requires aligned buffer
std::unique_ptr<char> buf(static_cast<char*>(
aligned_alloc(block_size /* alignment */, block_size /* size */)));
if (read_block_from_device(block_device, physical_block_offset, block_size,
buf.get()) < 0) {
return -1;
}
char* p = buf.get() + offset_within_block;
printf("before: %hhx\n", *p);
*p ^= 0xff;
printf("after: %hhx\n", *p);
if (write_block_to_device(block_device, physical_block_offset, block_size,
buf.get()) < 0) {
return -1;
}
return 0;
}

View File

@@ -0,0 +1,496 @@
/*
* Copyright (C) 2019 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.apkverity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.RootPermissionTest;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.HashSet;
/**
* This test makes sure app installs with fs-verity signature, and on-access verification works.
*
* <p>When an app is installed, all or none of the files should have their corresponding .fsv_sig
* signature file. Otherwise, install will fail.
*
* <p>Once installed, file protected by fs-verity is verified by kernel every time a block is loaded
* from disk to memory. The file is immutable by design, enforced by filesystem.
*
* <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>Requirements to run this test:
* <ul>
* <li>Device is rootable</li>
* <li>The filesystem supports fs-verity</li>
* <li>The feature flag is enabled</li>
* </ul>
*/
@RootPermissionTest
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApkVerityTest extends BaseHostJUnit4Test {
private static final String TARGET_PACKAGE = "com.android.apkverity";
private static final String BASE_APK = "ApkVerityTestApp.apk";
private static final String BASE_APK_DM = "ApkVerityTestApp.dm";
private static final String SPLIT_APK = "ApkVerityTestAppSplit.apk";
private static final String SPLIT_APK_DM = "ApkVerityTestAppSplit.dm";
private static final String INSTALLED_BASE_APK = "base.apk";
private static final String INSTALLED_BASE_DM = "base.dm";
private static final String INSTALLED_SPLIT_APK = "split_feature_x.apk";
private static final String INSTALLED_SPLIT_DM = "split_feature_x.dm";
private static final String INSTALLED_BASE_APK_FSV_SIG = "base.apk.fsv_sig";
private static final String INSTALLED_BASE_DM_FSV_SIG = "base.dm.fsv_sig";
private static final String INSTALLED_SPLIT_APK_FSV_SIG = "split_feature_x.apk.fsv_sig";
private static final String INSTALLED_SPLIT_DM_FSV_SIG = "split_feature_x.dm.fsv_sig";
private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
private static final String APK_VERITY_STANDARD_MODE = "2";
/** Only 4K page is supported by fs-verity currently. */
private static final int FSVERITY_PAGE_SIZE = 4096;
private ITestDevice mDevice;
private String mKeyId;
@Before
public void setUp() throws DeviceNotAvailableException {
mDevice = getDevice();
String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode");
assumeTrue(APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
mKeyId = expectRemoteCommandToSucceed(
"mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim();
if (!mKeyId.matches("^\\d+$")) {
String keyId = mKeyId;
mKeyId = null;
fail("Key ID is not decimal: " + keyId);
}
uninstallPackage(TARGET_PACKAGE);
}
@After
public void tearDown() throws DeviceNotAvailableException {
uninstallPackage(TARGET_PACKAGE);
if (mKeyId != null) {
expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
}
}
@Test
public void testFsverityKernelSupports() throws DeviceNotAvailableException {
ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
expectRemoteCommandToSucceed("test -f /sys/fs/" + mountPoint.type + "/features/verity");
}
@Test
public void testInstallBase() throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG);
verifyInstalledFilesHaveFsverity();
}
@Test
public void testInstallBaseWithWrongSignature()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
.addFile(SPLIT_APK_DM + ".fsv_sig",
BASE_APK + ".fsv_sig")
.runExpectingFailure();
}
@Test
public void testInstallBaseWithSplit()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.addFileAndSignature(SPLIT_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG,
INSTALLED_SPLIT_APK,
INSTALLED_SPLIT_APK_FSV_SIG);
verifyInstalledFilesHaveFsverity();
}
@Test
public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.addFileAndSignature(BASE_APK_DM)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG,
INSTALLED_BASE_DM,
INSTALLED_BASE_DM_FSV_SIG);
verifyInstalledFilesHaveFsverity();
}
@Test
public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.addFileAndSignature(BASE_APK_DM)
.addFileAndSignature(SPLIT_APK)
.addFileAndSignature(SPLIT_APK_DM)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG,
INSTALLED_BASE_DM,
INSTALLED_BASE_DM_FSV_SIG,
INSTALLED_SPLIT_APK,
INSTALLED_SPLIT_APK_FSV_SIG,
INSTALLED_SPLIT_DM,
INSTALLED_SPLIT_DM_FSV_SIG);
verifyInstalledFilesHaveFsverity();
}
@Test
public void testInstallSplitOnly()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG);
new InstallMultiple()
.inheritFrom(TARGET_PACKAGE)
.addFileAndSignature(SPLIT_APK)
.run();
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG,
INSTALLED_SPLIT_APK,
INSTALLED_SPLIT_APK_FSV_SIG);
verifyInstalledFilesHaveFsverity();
}
@Test
public void testInstallSplitOnlyMissingSignature()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG);
new InstallMultiple()
.inheritFrom(TARGET_PACKAGE)
.addFile(SPLIT_APK)
.runExpectingFailure();
}
@Test
public void testInstallSplitOnlyWithoutBaseSignature()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(INSTALLED_BASE_APK);
new InstallMultiple()
.inheritFrom(TARGET_PACKAGE)
.addFileAndSignature(SPLIT_APK)
.run();
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_SPLIT_APK,
INSTALLED_SPLIT_APK_FSV_SIG);
}
@Test
public void testInstallOnlyBaseHasFsvSig()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.addFile(BASE_APK_DM)
.addFile(SPLIT_APK)
.addFile(SPLIT_APK_DM)
.runExpectingFailure();
}
@Test
public void testInstallOnlyDmHasFsvSig()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
.addFileAndSignature(BASE_APK_DM)
.addFile(SPLIT_APK)
.addFile(SPLIT_APK_DM)
.runExpectingFailure();
}
@Test
public void testInstallOnlySplitHasFsvSig()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
.addFile(BASE_APK_DM)
.addFileAndSignature(SPLIT_APK)
.addFile(SPLIT_APK_DM)
.runExpectingFailure();
}
@Test
public void testInstallBaseWithFsvSigThenSplitWithout()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFileAndSignature(BASE_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(
INSTALLED_BASE_APK,
INSTALLED_BASE_APK_FSV_SIG);
new InstallMultiple()
.addFile(SPLIT_APK)
.runExpectingFailure();
}
@Test
public void testInstallBaseWithoutFsvSigThenSplitWith()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
.run();
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
verifyInstalledFiles(INSTALLED_BASE_APK);
new InstallMultiple()
.addFileAndSignature(SPLIT_APK)
.runExpectingFailure();
}
@Test
public void testFsverityFileIsImmutableAndReadable() throws DeviceNotAvailableException {
new InstallMultiple().addFileAndSignature(BASE_APK).run();
String apkPath = getApkPath(TARGET_PACKAGE);
assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
expectRemoteCommandToFail("echo -n '' >> " + apkPath);
expectRemoteCommandToSucceed("cat " + apkPath + " > /dev/null");
}
@Test
public void testFsverityFailToReadModifiedBlockAtFront() throws DeviceNotAvailableException {
new InstallMultiple().addFileAndSignature(BASE_APK).run();
String apkPath = getApkPath(TARGET_PACKAGE);
long apkSize = getFileSizeInBytes(apkPath);
long offsetFirstByte = 0;
// The first two pages should be both readable at first.
assertTrue(canReadByte(apkPath, offsetFirstByte));
if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
assertTrue(canReadByte(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));
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));
}
}
@Test
public void testFsverityFailToReadModifiedBlockAtBack() throws DeviceNotAvailableException {
new InstallMultiple().addFileAndSignature(BASE_APK).run();
String apkPath = getApkPath(TARGET_PACKAGE);
long apkSize = getFileSizeInBytes(apkPath);
long offsetOfLastByte = apkSize - 1;
// The first two pages should be both readable at first.
assertTrue(canReadByte(apkPath, offsetOfLastByte));
if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
assertTrue(canReadByte(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));
if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
}
}
private void verifyInstalledFilesHaveFsverity() throws DeviceNotAvailableException {
// Verify that all files are protected by fs-verity
String apkPath = getApkPath(TARGET_PACKAGE);
String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
long kTargetOffset = 0;
for (String basename : expectRemoteCommandToSucceed("ls " + appDir).split("\n")) {
if (basename.endsWith(".apk") || basename.endsWith(".dm")) {
String path = appDir + "/" + basename;
damageFileAgainstBlockDevice(path, kTargetOffset);
// Retry is sometimes needed to pass the test. Package manager may have FD leaks
// (see b/122744005 as example) that prevents the file in question to be evicted
// from filesystem cache. Forcing GC workarounds the problem.
int retry = 5;
for (; retry > 0; retry--) {
dropCaches();
if (!canReadByte(path, kTargetOffset)) {
break;
}
try {
Thread.sleep(1000);
String pid = expectRemoteCommandToSucceed("pidof system_server");
mDevice.executeShellV2Command("kill -10 " + pid); // force GC
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
assertTrue("Read from " + path + " should fail", retry > 0);
}
}
}
private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
String apkPath = getApkPath(TARGET_PACKAGE);
String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
assertTrue(actualFiles.remove("lib"));
assertTrue(actualFiles.remove("oat"));
HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
assertEquals(expectedFiles, actualFiles);
}
private void damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte)
throws DeviceNotAvailableException {
assertTrue(path.startsWith("/data/"));
ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
expectRemoteCommandToSucceed(String.join(" ", DAMAGING_EXECUTABLE,
mountPoint.filesystem, path, Long.toString(offsetOfTargetingByte)));
}
private String getApkPath(String packageName) throws DeviceNotAvailableException {
String line = expectRemoteCommandToSucceed("pm path " + packageName + " | grep base.apk");
int index = line.trim().indexOf(":");
assertTrue(index >= 0);
return line.substring(index + 1);
}
private long getFileSizeInBytes(String packageName) throws DeviceNotAvailableException {
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,
result.getStatus());
return result.getStdout();
}
private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException {
CommandResult result = mDevice.executeShellV2Command(cmd);
assertTrue("Unexpected success from `" + cmd + "`: " + result.getStderr(),
result.getStatus() != CommandStatus.SUCCESS);
}
private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
InstallMultiple() {
super(getDevice(), getBuild());
}
InstallMultiple addFileAndSignature(String filename) {
try {
addFile(filename);
addFile(filename + ".fsv_sig");
} catch (FileNotFoundException e) {
fail("Missing test file: " + e);
}
return this;
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2019 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.apkverity;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import junit.framework.TestCase;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
*
* <code> private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; { public
* InstallMultiple() { super(getDevice(), null); } } </code>
*/
/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
private final ITestDevice mDevice;
private final IBuildInfo mBuild;
private final List<String> mArgs = new ArrayList<>();
private final Map<File, String> mFileToRemoteMap = new HashMap<>();
/*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
mDevice = device;
mBuild = buildInfo;
addArg("-g");
}
T addArg(String arg) {
mArgs.add(arg);
return (T) this;
}
T addFile(String filename) throws FileNotFoundException {
return addFile(filename, filename);
}
T addFile(String filename, String remoteName) throws FileNotFoundException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
return (T) this;
}
T inheritFrom(String packageName) {
addArg("-r");
addArg("-p " + packageName);
return (T) this;
}
void run() throws DeviceNotAvailableException {
run(true);
}
void runExpectingFailure() throws DeviceNotAvailableException {
run(false);
}
private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
final ITestDevice device = mDevice;
// Create an install session
final StringBuilder cmd = new StringBuilder();
cmd.append("pm install-create");
for (String arg : mArgs) {
cmd.append(' ').append(arg);
}
String result = device.executeShellCommand(cmd.toString());
TestCase.assertTrue(result, result.startsWith("Success"));
final int start = result.lastIndexOf("[");
final int end = result.lastIndexOf("]");
int sessionId = -1;
try {
if (start != -1 && end != -1 && start < end) {
sessionId = Integer.parseInt(result.substring(start + 1, end));
}
} catch (NumberFormatException e) {
throw new IllegalStateException("Failed to parse install session: " + result);
}
if (sessionId == -1) {
throw new IllegalStateException("Failed to create install session: " + result);
}
// Push our files into session. Ideally we'd use stdin streaming,
// but ddmlib doesn't support it yet.
for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
final File file = entry.getKey();
final String remoteName = entry.getValue();
final String remotePath = "/data/local/tmp/" + file.getName();
if (!device.pushFile(file, remotePath)) {
throw new IllegalStateException("Failed to push " + file);
}
cmd.setLength(0);
cmd.append("pm install-write");
cmd.append(' ').append(sessionId);
cmd.append(' ').append(remoteName);
cmd.append(' ').append(remotePath);
result = device.executeShellCommand(cmd.toString());
TestCase.assertTrue(result, result.startsWith("Success"));
}
// Everything staged; let's pull trigger
cmd.setLength(0);
cmd.append("pm install-commit");
cmd.append(' ').append(sessionId);
result = device.executeShellCommand(cmd.toString());
if (expectingSuccess) {
TestCase.assertTrue(result, result.contains("Success"));
} else {
TestCase.assertFalse(result, result.contains("Success"));
}
}
}

77
tests/ApkVerityTest/testdata/Android.bp vendored Normal file
View File

@@ -0,0 +1,77 @@
// Copyright (C) 2019 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.
filegroup {
name: "ApkVerityTestKeyPem",
srcs: ["ApkVerityTestKey.pem"],
}
filegroup {
name: "ApkVerityTestCertPem",
srcs: ["ApkVerityTestCert.pem"],
}
filegroup {
name: "ApkVerityTestCertDer",
srcs: ["ApkVerityTestCert.der"],
}
filegroup {
name: "ApkVerityTestAppDm",
srcs: ["ApkVerityTestApp.dm"],
}
filegroup {
name: "ApkVerityTestAppSplitDm",
srcs: ["ApkVerityTestAppSplit.dm"],
}
genrule_defaults {
name: "apk_verity_sig_gen_default",
tools: ["fsverity"],
tool_files: [":ApkVerityTestKeyPem", ":ApkVerityTestCertPem"],
cmd: "$(location fsverity) sign $(in) $(out) " +
"--key=$(location :ApkVerityTestKeyPem) " +
"--cert=$(location :ApkVerityTestCertPem) " +
"> /dev/null",
}
genrule {
name: "ApkVerityTestAppFsvSig",
defaults: ["apk_verity_sig_gen_default"],
srcs: [":ApkVerityTestApp"],
out: ["ApkVerityTestApp.apk.fsv_sig"],
}
genrule {
name: "ApkVerityTestAppDmFsvSig",
defaults: ["apk_verity_sig_gen_default"],
srcs: [":ApkVerityTestAppDm"],
out: ["ApkVerityTestApp.dm.fsv_sig"],
}
genrule {
name: "ApkVerityTestAppSplitFsvSig",
defaults: ["apk_verity_sig_gen_default"],
srcs: [":ApkVerityTestAppSplit"],
out: ["ApkVerityTestAppSplit.apk.fsv_sig"],
}
genrule {
name: "ApkVerityTestAppSplitDmFsvSig",
defaults: ["apk_verity_sig_gen_default"],
srcs: [":ApkVerityTestAppSplitDm"],
out: ["ApkVerityTestAppSplit.dm.fsv_sig"],
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFLjCCAxagAwIBAgIJAKZbtMlZZwtdMA0GCSqGSIb3DQEBCwUAMCwxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHQW5kcm9pZDAeFw0xODEyMTky
MTA5MzVaFw0xOTAxMTgyMTA5MzVaMCwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
QTEQMA4GA1UECgwHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAKnrw4WiFgFBq6vXqcLc97iwvcYPZmeIjQqYRF+CHwXBXx8IyDlMfPrgyIYo
ZLkosnUK/Exuypdu6UEtdqtYPknC6w9z4YkxqsKtyxyB1b13ptcTHh3bf2N8bqGr
8gWWLxj0QjumCtFi7Z/TCwB5t3b3gtC+0jVfABSWrm5PNkgk7jIP+4KeYLDCDfiJ
XH3uHu6OASiSHTOnrmLWSaSw0y6G4OFthHqQnMywasly0r6m+Mif+K0ZUV7hBRi/
SfqcJ1HTCXTJMskEyV6Qx2sHF/VbK2gdUv56z6OVRNSs/FxPBiWVMuZZKh1FpBVI
gbGxusf2Awwtc+Soxr4/P1YFcrwfA/ff9FK3Yg/Cd3ZMGbzUkbEMEkE5BW7Gbjmx
wz3mYTiRfa2L/Bl4MiMqNi0tfORLkmg+V/EItzfhZ/HsXMOCBsnuj4KnFslmbamz
t9opypj2JLGk+lXhZ5gFNFw8tYH1AnG1AIXe5u+6Fq2nQ1y/ncGUTR5Sw4de/Gee
C0UgR+KiFEdKupMKbXgSKl+0QPz/i2eSpcDOKMwZ4WiNrkbccbCyr38so+j5DfWF
IeZA9a/IlysA6G8yU2TfXBc65VCIEQRJOQdUOZFDO8OSoqGP+fbA6edpmovGw+TH
sM/NkmpEXpQm7BVOI4oVjdf4pKPp0zaW2YcaA3xU2w6eF17pAgMBAAGjUzBRMB0G
A1UdDgQWBBRGpHYy7yiLEYalGuF1va6zJKGD/zAfBgNVHSMEGDAWgBRGpHYy7yiL
EYalGuF1va6zJKGD/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC
AQAao6ZBM122F0pYb2QLahIyyGEr3LfSdBGID4068pVik4ncIefFz36Xf9AFxRQd
KHmwRYNPHiLRIEGdtqplC5pZDeHz41txIArNIZKzDWOYtdcFyCz8umuj912BmsoM
YUQhT6F1sX53SWcKxEP/aJ2kltSlPFX99e3Vx9eRkceV1oe2NM6ZG8hnYCfCAMeJ
jRTpbqCGaAsEHFtIx6wt3zEtUXIVg4aYFQs/qjTjeP8ByIj0b4lZrceEoTeRimuj
+4aAI+jBxLkwaN3hseQHzRNpgPehIVV/0RU92yzOD/WN4YwE6rwjKEI1lihHNBDa
+DwGtGbHmIUzjW1qArig+mzUIhfYIJAxrx20ynPz/Q+C7+iXhTDAYQlxTle0pX8m
yM2DUdPo97eLOzQ4JDHxtcN3ntTEJKKvrmzKvWuxy/yoLwS7MtLH6RETTHabH3Qd
CP83X7z8zTyxgPxHdfHo9sgR/4C9RHGJx4OpBTQaiqfjSpDqJSIQdbrHGOQDgYwL
KQyiQuhukmNgRCB6dJoZJ/MyaNuMsXV9QobsDHW1oSuCvPAihVoWHJxt8m4Ma0jJ
EIbEPT2Umw1F/P+CeXnVQwhPvzQKHCa+6cC/YdjTqIKLmQV8X3HUBUIMhP2JGDic
MnUipTm/RwWZVOjCJaFqk5sVq3L0Lyd0XVUWSK1a4IcrsA==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
p2aUlQOLXU9SjQTirZbdCZP0XHtFsg==
-----END PRIVATE KEY-----

13
tests/ApkVerityTest/testdata/README.md vendored Normal file
View File

@@ -0,0 +1,13 @@
This test only runs on rooted / debuggable device.
The test tries to install subsets of base.{apk,dm}, split.{apk,dm} and their
corresponding .fsv_sig files (generated by build rule). If installed, the
tests also tries to tamper with the file at absolute disk offset to verify
if fs-verity is effective.
How to generate dex metadata (.dm)
==================================
adb shell profman --generate-test-profile=/data/local/tmp/primary.prof
adb pull /data/local/tmp/primary.prof
zip foo.dm primary.prof