/* * Copyright (c) 2022, Google Inc. All rights reserved * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Tests for ARM64 FEAT_Puth (Pointer Authentication Codes) * This is a CPU feature at ARM-A v8.3 * Since this is an ARM64 feature, this should not be built for other arches. */ #ifndef ARCH_ARM64 #error PAC is an ARM64 feature #endif #include "pactest.h" #include #include #include #include #include #include #include #define MASK(bits) ((1ull << (bits)) - 1) /* * Test addresses for each translation table (TT). * Address bit 55 is used to select between page translation tables to use. * TT0 is used for user addresses, while TT1 is kernel addresses. */ #define PACTEST_TT0_ADDRESS \ (0x1234567890abcdefu & MASK(MMU_USER_SIZE_SHIFT)) & ~(1ull << 55) #define PACTEST_TT1_ADDRESS \ (0x1234567890abcdefu | (~0ull << MMU_KERNEL_SIZE_SHIFT)) | (1ull << 55) #define PACTEST_MODIFIER 0xfedcba0987654321u /* Helper function for parameterized calling of specific PAC instructions */ static uint64_t pacxx(bool instr_not_data, bool key_a_not_b, uint64_t address, uint64_t modifier) { if (key_a_not_b) { if (instr_not_data) { __asm__(".arch_extension pauth\n" "\tPACIA %0, %1" : "+r"(address) : "r"(modifier)); } else { __asm__(".arch_extension pauth\n" "\tPACDA %0, %1" : "+r"(address) : "r"(modifier)); } } else { if (instr_not_data) { __asm__(".arch_extension pauth\n" "\tPACIB %0, %1" : "+r"(address) : "r"(modifier)); } else { __asm__(".arch_extension pauth\n" "\tPACDB %0, %1" : "+r"(address) : "r"(modifier)); } } return address; } /* * Helper function for parameterized calling of specific PAC instructions. * The instructions are implemented in assembly as they may generate exceptions * (FEAT_FPAC) which need catching. */ static int autxx(bool instr_not_data, bool key_a_not_b, uint64_t address, uint64_t modifier, uint64_t* result) { if (key_a_not_b) { if (instr_not_data) { return pactest_autia(address, modifier, result); } else { return pactest_autda(address, modifier, result); } } else { if (instr_not_data) { return pactest_autib(address, modifier, result); } else { return pactest_autdb(address, modifier, result); } } } static uint8_t get_nibble(uint64_t reg, uint8_t shift) { return (reg >> shift) & 0xf; } static const char* get_pac_features(uint8_t nibble) { switch (nibble) { case 0b0001: return "PAuth"; case 0b0010: return "PAuth + EPAC"; case 0b0011: return "PAuth + PAuth2"; case 0b0100: return "Pauth + Pauth2 + FPAC"; case 0b0101: return "Pauth + Pauth2 + FPAC + FPACCOMBINE"; } return ""; } TEST(pactest, pauth_supported) { if (!arch_pac_address_supported()) { trusty_unittest_printf("[ INFO ] PAuth is not supported\n"); GTEST_SKIP(); } const uint64_t isar1 = ARM64_READ_SYSREG(id_aa64isar1_el1); const uint64_t isar2 = ARM64_READ_SYSREG(id_aa64isar2_el1); const uint8_t pacfrac = get_nibble(isar2, ID_AA64ISAR2_EL1_PAC_FRAC_SHIFT); const uint8_t apa3 = get_nibble(isar2, ID_AA64ISAR2_EL1_APA3_SHIFT); const uint8_t apa = get_nibble(isar1, ID_AA64ISAR1_EL1_APA_SHIFT); const uint8_t api = get_nibble(isar1, ID_AA64ISAR1_EL1_API_SHIFT); const char *algo = "none", *features = "", *cpf = ""; if (apa3) { algo = "QARMA3"; features = get_pac_features(apa3); EXPECT_EQ(apa, 0); EXPECT_EQ(api, 0); } else if (apa) { algo = "QARMA5"; features = get_pac_features(apa); EXPECT_EQ(api, 0); } else if (api) { algo = "implementation defined"; features = get_pac_features(api); } if (pacfrac == 1) { cpf = ", CONSTPACFIELD"; } else { EXPECT_EQ(pacfrac, 0); } /* Log the support in case later trying to debug a test */ trusty_unittest_printf("[ INFO ] algo: %s\n", algo); trusty_unittest_printf("[ INFO ] feat: %s%s\n", features, cpf); test_abort:; } TEST(pactest, fpac_supported) { uint64_t val; int rc; if (!arch_pac_exception_supported()) { trusty_unittest_printf("[ INFO ] FPAC is not supported\n"); GTEST_SKIP(); } rc = pactest_autia(PACTEST_TT0_ADDRESS, PACTEST_MODIFIER, &val); EXPECT_EQ(rc, ERR_FAULT); test_abort:; } TEST(pactest, enabled) { const uint64_t sctlr_el1 = ARM64_READ_SYSREG(SCTLR_EL1); /* Check the expected keys are enabled */ EXPECT_EQ(sctlr_el1 & SCTLR_EL1_ENIA, arch_pac_address_supported() ? SCTLR_EL1_ENIA : 0); EXPECT_EQ(sctlr_el1 & SCTLR_EL1_ENIB, 0); EXPECT_EQ(sctlr_el1 & SCTLR_EL1_ENDA, 0); EXPECT_EQ(sctlr_el1 & SCTLR_EL1_ENDB, 0); } TEST(pactest, keys) { if (!arch_pac_address_supported()) { GTEST_SKIP(); } const struct packeys* thread_keys = &get_current_thread()->arch.packeys; const uint64_t keyi_lo = ARM64_READ_SYSREG(APIAKeyLo_EL1); const uint64_t keyi_hi = ARM64_READ_SYSREG(APIAKeyHi_EL1); EXPECT_EQ(thread_keys->apia[0], keyi_lo); EXPECT_EQ(thread_keys->apia[1], keyi_hi); /* * Check the keys are neither all 0's of all 1's. * While these values are valid, it may indicate incorrect initialisation. */ EXPECT_NE(UINT64_MAX, keyi_lo); EXPECT_NE(UINT64_MAX, keyi_hi); EXPECT_NE(0, keyi_lo); EXPECT_NE(0, keyi_hi); test_abort:; } typedef struct { bool translation_table; bool instr_not_data; bool key_a_not_b; bool key_enabled; } pactest_t; static void get_params(pactest_t* p) { const bool* const* params = GetParam(); p->translation_table = *params[0]; /* Invert for more logical test ordering: AI, AD, BI, BD */ p->instr_not_data = !*params[1]; p->key_a_not_b = !*params[2]; } TEST_F_SETUP(pactest) { uint64_t key_enabled_bit; get_params(_state); if (_state->instr_not_data) { key_enabled_bit = _state->key_a_not_b ? SCTLR_EL1_ENIA : SCTLR_EL1_ENIB; } else { key_enabled_bit = _state->key_a_not_b ? SCTLR_EL1_ENDA : SCTLR_EL1_ENDB; } _state->key_enabled = ARM64_READ_SYSREG(SCTLR_EL1) & key_enabled_bit; } TEST_F_TEARDOWN(pactest) {} static void user_param_to_string(const void* param, char* buf, size_t buf_size) { pactest_t p; get_params(&p); snprintf(buf, buf_size, "TT%u/%s%s", p.translation_table ? 1 : 0, p.instr_not_data ? "PACI" : "PACD", p.key_a_not_b ? "A" : "B"); } INSTANTIATE_TEST_SUITE_P(pac, pactest, testing_Combine(testing_Bool(), testing_Bool(), testing_Bool()), user_param_to_string); TEST_P(pactest, instr) { if (!arch_pac_address_supported()) { GTEST_SKIP(); } const uint64_t test_address = _state->translation_table ? PACTEST_TT1_ADDRESS : PACTEST_TT0_ADDRESS; uint64_t address = test_address; int rc; if (_state->key_enabled) { /* Test instruction adds a PAC */ address = pacxx(_state->instr_not_data, _state->key_a_not_b, address, PACTEST_MODIFIER); /* Address should have been modified to include PAC */ EXPECT_NE(test_address, address); uint64_t pac_address = address; /* Check AUT returns the original pointer */ rc = autxx(_state->instr_not_data, _state->key_a_not_b, address, PACTEST_MODIFIER, &address); EXPECT_EQ(rc, 0) EXPECT_EQ(test_address, address); /* Check the pointer is invalidated when the modifier is changed */ rc = autxx(_state->instr_not_data, _state->key_a_not_b, pac_address, ~PACTEST_MODIFIER, &address); if (arch_pac_exception_supported()) { EXPECT_EQ(rc, ERR_FAULT); } else { /* Address should have been invalidated */ EXPECT_EQ(rc, 0); EXPECT_NE(address, test_address); } } else { /* Key disabled */ address = pacxx(_state->instr_not_data, _state->key_a_not_b, address, PACTEST_MODIFIER); EXPECT_EQ(test_address, address); rc = autxx(_state->instr_not_data, _state->key_a_not_b, address, PACTEST_MODIFIER, &address); EXPECT_EQ(rc, 0) EXPECT_EQ(test_address, address); } test_abort:; } TEST_P(pactest, pac_length) { if (!arch_pac_address_supported()) { GTEST_SKIP(); } uint8_t top = 0, bot = 64; /* * Probe a number of times in order to ensure we find the top and bottom * bits used. Odds of missing correct bounds are about P=(1/2)^32. */ for (uint16_t t = 0; t < 32; t++) { uint64_t val, orig; /* Get 64-bit random value */ platform_random_get_bytes((void*)&orig, sizeof(orig)); /* * Select which of T0SZ or T1SZ we should probe. * Address bit 55 is used to select between page translation tables * to use (e.g. TTBRx and TxSZ, where x is 0 or 1). */ val = orig & ~(1ull << 55); if (_state->translation_table) { val |= 1ull << 55; } /* Call specific instruction variant */ val = pacxx(_state->instr_not_data, _state->key_a_not_b, val, 0); /* Remove un-changed bits and clear bit 55 */ val ^= orig; val &= ~(1ull << 55); /* Find highest and lowest set bit positions */ if (val) { top = MAX(top, 63 - __builtin_clzll(val)); bot = MIN(bot, __builtin_ctzll(val)); } } if (_state->key_enabled) { /* If this is not true, the PAC key not be functioning */ ASSERT_GT(top, bot); /* Count bit range, except bit 55 if it is in the range */ int bits = (top + 1) - bot; if (bot < 55 && top > 55) { bits--; } trusty_unittest_printf("[ INFO ] PAC bits %" PRIu8 ":%" PRIu8 " = %d effective bits\n", top, bot, bits); } else { trusty_unittest_printf("[ INFO ] PAC key disabled\n"); ASSERT_EQ(top, 0); ASSERT_EQ(bot, 64); } test_abort:; } PORT_TEST(pactest, "com.android.kernel.pactest");