diff --git a/tests/HardeningTest/Android.bp b/tests/HardeningTest/Android.bp index 36afa481b1e00..06af69f0e43ef 100644 --- a/tests/HardeningTest/Android.bp +++ b/tests/HardeningTest/Android.bp @@ -1,3 +1,8 @@ +filegroup { + name: "HardeningTestUtils", + srcs: ["src/grapheneos/hardeningtest/TestUtils.java"], +} + java_test_host { name: "HardeningTest", srcs: [ diff --git a/tests/HardeningTest/src/grapheneos/hardeningtest/SELinuxTest.java b/tests/HardeningTest/src/grapheneos/hardeningtest/SELinuxTest.java index b1e0e1455293d..0e1ef0ac31829 100644 --- a/tests/HardeningTest/src/grapheneos/hardeningtest/SELinuxTest.java +++ b/tests/HardeningTest/src/grapheneos/hardeningtest/SELinuxTest.java @@ -12,8 +12,6 @@ import java.util.function.Consumer; -import static org.junit.Assert.assertEquals; - @RunWith(DeviceJUnit4ClassRunner.class) public class SELinuxTest extends BaseHostJUnit4Test { @@ -36,29 +34,6 @@ private void runDeviceTest(String pkgName, String name) { } } - private void editGosPackageState(String pkgName, int[] addFlags, int[] clearFlags) { - try { - var device = getDevice(); - var cmd = new StringBuilder("pm edit-gos-package-state " + pkgName + " " + device.getCurrentUser()); - for (int flag : addFlags) { - cmd.append(" add-flag ").append(flag); - } - for (int flag : clearFlags) { - cmd.append(" clear-flag ").append(flag); - } - var edRes = device.executeShellV2Command(cmd.toString()); - assertEquals(edRes.toString(), 0L, (long) edRes.getExitCode()); - } catch (DeviceNotAvailableException e) { - throw new IllegalStateException(e); - } - } - - private void setComplexFlagState(String pkgName, int flag, int nonDefaultFlag, boolean isSet) { - int[] addFlags = isSet ? new int[] { nonDefaultFlag, flag } : new int[] { nonDefaultFlag }; - int[] clearFlags = isSet ? new int[0] : new int[] { flag }; - editGosPackageState(pkgName, addFlags, clearFlags); - } - private void forEachPackage(Consumer action) { for (String pkg : new String[] {TEST_PACKAGE_SDK_27, TEST_PACKAGE_SDK_LATEST, TEST_PACKAGE_SDK_LATEST_PREINSTALLED}) { if (pkg == TEST_PACKAGE_SDK_LATEST_PREINSTALLED) { @@ -97,12 +72,12 @@ String testName(String suffix) { public void testDynamicCodeLoadingRestricted() { forEachPackage(pkg -> { for (var t : DclTestType.values()) { - setComplexFlagState(pkg, t.gosPsFlag, t.gosPsNonDefaultFlag, true); + TestUtils.setComplexFlagState(this, pkg, t.gosPsFlag, t.gosPsNonDefaultFlag, true); runDeviceTest(pkg, t.testName("DclRestricted")); if (pkg == TEST_PACKAGE_SDK_LATEST_PREINSTALLED) { // check that DCL is blocked regardless of GosPackageState flags - setComplexFlagState(pkg, t.gosPsFlag, t.gosPsNonDefaultFlag, false); + TestUtils.setComplexFlagState(this, pkg, t.gosPsFlag, t.gosPsNonDefaultFlag, false); runDeviceTest(pkg, t.testName("DclRestricted")); } } @@ -118,7 +93,7 @@ public void testDynamicCodeLoadingAllowed() { } for (var t : DclTestType.values()) { - setComplexFlagState(pkg, t.gosPsFlag, t.gosPsNonDefaultFlag, false); + TestUtils.setComplexFlagState(this, pkg, t.gosPsFlag, t.gosPsNonDefaultFlag, false); runDeviceTest(pkg, t.testName("DclAllowed")); } }); @@ -132,7 +107,7 @@ public void testPtraceAllowed() { return; } - setComplexFlagState(pkg, + TestUtils.setComplexFlagState(this, pkg, GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING, GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING_NON_DEFAULT, false); @@ -143,7 +118,7 @@ public void testPtraceAllowed() { @Test public void testPtraceDenied() { forEachPackage(pkg -> { - setComplexFlagState(pkg, + TestUtils.setComplexFlagState(this, pkg, GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING, GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING_NON_DEFAULT, true); diff --git a/tests/HardeningTest/src/grapheneos/hardeningtest/TestUtils.java b/tests/HardeningTest/src/grapheneos/hardeningtest/TestUtils.java new file mode 100644 index 0000000000000..ba8c131f36ded --- /dev/null +++ b/tests/HardeningTest/src/grapheneos/hardeningtest/TestUtils.java @@ -0,0 +1,37 @@ +package grapheneos.hardeningtest; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; + +import static org.junit.Assert.assertEquals; + +public class TestUtils { + private TestUtils() {} + + public static void editGosPackageState(BaseHostJUnit4Test host, String pkgName, + int[] addFlags, int[] clearFlags) { + try { + var device = host.getDevice(); + var cmd = new StringBuilder("pm edit-gos-package-state " + pkgName + + " " + device.getCurrentUser()); + for (int flag : addFlags) { + cmd.append(" add-flag ").append(flag); + } + for (int flag : clearFlags) { + cmd.append(" clear-flag ").append(flag); + } + var edRes = device.executeShellV2Command(cmd.toString()); + assertEquals(edRes.toString(), 0L, (long) edRes.getExitCode()); + } catch (DeviceNotAvailableException e) { + throw new IllegalStateException(e); + } + } + + public static void setComplexFlagState(BaseHostJUnit4Test host, String pkgName, + int flag, int nonDefaultFlag, boolean isSet) { + int[] addFlags = isSet ? new int[] { nonDefaultFlag, flag } : new int[] { nonDefaultFlag }; + int[] clearFlags = isSet ? new int[0] : new int[] { flag }; + editGosPackageState(host, pkgName, addFlags, clearFlags); + } +} diff --git a/tests/VaSpaceTest/Android.bp b/tests/VaSpaceTest/Android.bp new file mode 100644 index 0000000000000..bc15702292638 --- /dev/null +++ b/tests/VaSpaceTest/Android.bp @@ -0,0 +1,27 @@ +java_test_host { + name: "VaSpaceTest", + srcs: [ + "src/**/*.java", + ":GosPackageStateFlags", + ":HardeningTestUtils", + ], + + libs: [ + "tradefed", + "compatibility-tradefed", + "compatibility-host-util", + ], + + static_libs: [ + "framework-annotations-lib", + "frameworks-base-hostutils", + ], + + test_suites: [ + "general-tests", + ], + + device_common_data: [ + ":VaSpaceTestApp", + ], +} diff --git a/tests/VaSpaceTest/AndroidTest.xml b/tests/VaSpaceTest/AndroidTest.xml new file mode 100644 index 0000000000000..68cbf49c59b1c --- /dev/null +++ b/tests/VaSpaceTest/AndroidTest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/tests/VaSpaceTest/VaSpaceTestApp/Android.bp b/tests/VaSpaceTest/VaSpaceTestApp/Android.bp new file mode 100644 index 0000000000000..75aa224b06ded --- /dev/null +++ b/tests/VaSpaceTest/VaSpaceTestApp/Android.bp @@ -0,0 +1,16 @@ +android_test_helper_app { + name: "VaSpaceTestApp", + + srcs: [ + "src/**/*.kt", + ], + + platform_apis: true, + + optimize: { enabled: false }, + + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + ], +} diff --git a/tests/VaSpaceTest/VaSpaceTestApp/AndroidManifest.xml b/tests/VaSpaceTest/VaSpaceTestApp/AndroidManifest.xml new file mode 100644 index 0000000000000..bba06a277776a --- /dev/null +++ b/tests/VaSpaceTest/VaSpaceTestApp/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/tests/VaSpaceTest/VaSpaceTestApp/src/app/grapheneos/vaspacetest/VaSpaceDeviceTest.kt b/tests/VaSpaceTest/VaSpaceTestApp/src/app/grapheneos/vaspacetest/VaSpaceDeviceTest.kt new file mode 100644 index 0000000000000..1f8ba2eb22489 --- /dev/null +++ b/tests/VaSpaceTest/VaSpaceTestApp/src/app/grapheneos/vaspacetest/VaSpaceDeviceTest.kt @@ -0,0 +1,37 @@ +package app.grapheneos.vaspacetest + +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File + +@RunWith(AndroidJUnit4::class) +class VaSpaceDeviceTest { + + @Test + fun testExtendedVaSpaceEnabled() = assertExtendedVaSpace(enabled = true) + + @Test + fun testExtendedVaSpaceDisabled() = assertExtendedVaSpace(enabled = false) + + private fun assertExtendedVaSpace(enabled: Boolean) { + // 39-bit VA limit: 2^39 = 0x80_0000_0000 + val limit = 1L shl 39 + val maps = File("/proc/self/maps").readLines() + if (enabled) { + val hasHighMapping = maps.any { line -> + line.substringBefore(' ').substringAfter('-').toLong(16) > limit + } + Assert.assertTrue("no mapping above 39-bit; extended VA space may not be active", hasHighMapping) + } else { + for (line in maps) { + val end = line.substringBefore(' ').substringAfter('-') + Assert.assertTrue( + "mapping end 0x$end exceeds 39-bit VA limit (0x${limit.toString(16)})", + end.toLong(16) <= limit, + ) + } + } + } +} diff --git a/tests/VaSpaceTest/src/grapheneos/vaspacetest/VaSpaceTest.java b/tests/VaSpaceTest/src/grapheneos/vaspacetest/VaSpaceTest.java new file mode 100644 index 0000000000000..5230631094b64 --- /dev/null +++ b/tests/VaSpaceTest/src/grapheneos/vaspacetest/VaSpaceTest.java @@ -0,0 +1,54 @@ +package grapheneos.vaspacetest; + +import android.content.pm.GosPackageStateFlag; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; + +import grapheneos.hardeningtest.TestUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class VaSpaceTest extends BaseHostJUnit4Test { + + private static final String TEST_PACKAGE = "app.grapheneos.vaspacetest"; + private static final String DEVICE_TEST_CLASS = TEST_PACKAGE + ".VaSpaceDeviceTest"; + + private void runDeviceTest(String methodName) { + var opts = new DeviceTestRunOptions(TEST_PACKAGE); + opts.setTestClassName(DEVICE_TEST_CLASS); + opts.setTestMethodName(methodName); + try { + runDeviceTests(opts); + } catch (DeviceNotAvailableException e) { + throw new IllegalStateException(e); + } + } + + @Test + public void testExtendedVaSpaceEnabled() { + TestUtils.setComplexFlagState(this, TEST_PACKAGE, + GosPackageStateFlag.USE_EXTENDED_VA_SPACE, + GosPackageStateFlag.USE_EXTENDED_VA_SPACE_NON_DEFAULT, + true); + runDeviceTest("testExtendedVaSpaceEnabled"); + } + + @Test + public void testExtendedVaSpaceDisabled() { + // hardened_malloc forces extended VA space on, disable for testing + TestUtils.setComplexFlagState(this, TEST_PACKAGE, + GosPackageStateFlag.USE_HARDENED_MALLOC, + GosPackageStateFlag.USE_HARDENED_MALLOC_NON_DEFAULT, + false); + TestUtils.setComplexFlagState(this, TEST_PACKAGE, + GosPackageStateFlag.USE_EXTENDED_VA_SPACE, + GosPackageStateFlag.USE_EXTENDED_VA_SPACE_NON_DEFAULT, + false); + runDeviceTest("testExtendedVaSpaceDisabled"); + } +}