From nobody Sun May 19 03:54:47 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=git.sr.ht Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1687788965563868.2427824642601; Mon, 26 Jun 2023 07:16:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qDmzp-0004Nm-QB; Mon, 26 Jun 2023 10:15:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qDmzk-0004NB-8J for qemu-devel@nongnu.org; Mon, 26 Jun 2023 10:15:08 -0400 Received: from mail-b.sr.ht ([173.195.146.151]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qDmzi-0002pn-0Y for qemu-devel@nongnu.org; Mon, 26 Jun 2023 10:15:07 -0400 Received: from git.sr.ht (unknown [173.195.146.142]) by mail-b.sr.ht (Postfix) with ESMTPSA id 0EA7F11EF84; Mon, 26 Jun 2023 14:15:04 +0000 (UTC) Authentication-Results: mail-b.sr.ht; dkim=none From: ~jhogberg Date: Thu, 08 Jun 2023 19:49:15 +0200 Subject: [PATCH qemu v4 1/2] target/arm: Handle IC IVAU to improve compatibility with JITs MIME-Version: 1.0 Message-ID: <168778890374.24232.3402138851538068785-1@git.sr.ht> X-Mailer: git.sr.ht In-Reply-To: <168778890374.24232.3402138851538068785-0@git.sr.ht> To: qemu-devel@nongnu.org Cc: peter.maydell@linaro.org Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=173.195.146.151; envelope-from=outgoing@sr.ht; helo=mail-b.sr.ht X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DATE_IN_PAST_96_XX=3.405, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: ~jhogberg Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1687788967787100007 From: John H=C3=B6gberg Unlike architectures with precise self-modifying code semantics (e.g. x86) ARM processors do not maintain coherency for instruction execution and memory, requiring an instruction synchronization barrier on every core that will execute the new code, and on many models also the explicit use of cache management instructions. While this is required to make JITs work on actual hardware, QEMU has gotten away with not handling this since it does not emulate caches, and unconditionally invalidates code whenever the softmmu or the user-mode page protection logic detects that code has been modified. Unfortunately the latter does not work in the face of dual-mapped code (a common W^X workaround), where one page is executable and the other is writable: user-mode has no way to connect one with the other as that is only known to the kernel and the emulated application. This commit works around the issue by telling software that instruction cache invalidation is required by clearing the CPR_EL0.DIC flag (regardless of whether the emulated processor needs it), and then invalidating code in IC IVAU instructions. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1034 Co-authored-by: Richard Henderson Signed-off-by: John H=C3=B6gberg Reviewed-by: Richard Henderson --- target/arm/cpu.c | 13 +++++++++++++ target/arm/helper.c | 47 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 4d5bb57f07..b82fb46157 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -1674,6 +1674,19 @@ static void arm_cpu_realizefn(DeviceState *dev, Erro= r **errp) return; } =20 + /* + * User mode relies on IC IVAU instructions to catch modification of + * dual-mapped code. + * + * Clear CTR_EL0.DIC to ensure that software that honors these flags u= ses + * IC IVAU even if the emulated processor does not normally require it. + */ +#ifdef CONFIG_USER_ONLY + if (arm_feature(env, ARM_FEATURE_AARCH64)) { + cpu->ctr =3D FIELD_DP64(cpu->ctr, CTR_EL0, DIC, 0); + } +#endif + if (!cpu->has_vfp) { uint64_t t; uint32_t u; diff --git a/target/arm/helper.c b/target/arm/helper.c index d4bee43bd0..235e3cd0b6 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -5228,6 +5228,36 @@ static void mdcr_el2_write(CPUARMState *env, const A= RMCPRegInfo *ri, } } =20 +#ifdef CONFIG_USER_ONLY +/* + * `IC IVAU` is handled to improve compatibility with JITs that dual-map t= heir + * code to get around W^X restrictions, where one region is writable and t= he + * other is executable. + * + * Since the executable region is never written to we cannot detect code + * changes when running in user mode, and rely on the emulated JIT telling= us + * that the code has changed by executing this instruction. + */ +static void ic_ivau_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + uint64_t icache_line_mask, start_address, end_address; + const ARMCPU *cpu; + + cpu =3D env_archcpu(env); + + icache_line_mask =3D (4 << extract32(cpu->ctr, 0, 4)) - 1; + start_address =3D value & ~icache_line_mask; + end_address =3D value | icache_line_mask; + + mmap_lock(); + + tb_invalidate_phys_range(start_address, end_address); + + mmap_unlock(); +} +#endif + static const ARMCPRegInfo v8_cp_reginfo[] =3D { /* * Minimal set of EL0-visible registers. This will need to be expanded @@ -5267,7 +5297,10 @@ static const ARMCPRegInfo v8_cp_reginfo[] =3D { { .name =3D "CURRENTEL", .state =3D ARM_CP_STATE_AA64, .opc0 =3D 3, .opc1 =3D 0, .opc2 =3D 2, .crn =3D 4, .crm =3D 2, .access =3D PL1_R, .type =3D ARM_CP_CURRENTEL }, - /* Cache ops: all NOPs since we don't emulate caches */ + /* + * Instruction cache ops. All of these except `IC IVAU` NOP because we + * don't emulate caches. + */ { .name =3D "IC_IALLUIS", .state =3D ARM_CP_STATE_AA64, .opc0 =3D 1, .opc1 =3D 0, .crn =3D 7, .crm =3D 1, .opc2 =3D 0, .access =3D PL1_W, .type =3D ARM_CP_NOP, @@ -5280,9 +5313,17 @@ static const ARMCPRegInfo v8_cp_reginfo[] =3D { .accessfn =3D access_tocu }, { .name =3D "IC_IVAU", .state =3D ARM_CP_STATE_AA64, .opc0 =3D 1, .opc1 =3D 3, .crn =3D 7, .crm =3D 5, .opc2 =3D 1, - .access =3D PL0_W, .type =3D ARM_CP_NOP, + .access =3D PL0_W, .fgt =3D FGT_ICIVAU, - .accessfn =3D access_tocu }, + .accessfn =3D access_tocu, +#ifdef CONFIG_USER_ONLY + .type =3D ARM_CP_NO_RAW, + .writefn =3D ic_ivau_write +#else + .type =3D ARM_CP_NOP +#endif + }, + /* Cache ops: all NOPs since we don't emulate caches */ { .name =3D "DC_IVAC", .state =3D ARM_CP_STATE_AA64, .opc0 =3D 1, .opc1 =3D 0, .crn =3D 7, .crm =3D 6, .opc2 =3D 1, .access =3D PL1_W, .accessfn =3D aa64_cacheop_poc_access, --=20 2.38.5 From nobody Sun May 19 03:54:47 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=git.sr.ht Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1687788965654523.0726788687292; Mon, 26 Jun 2023 07:16:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qDmzp-0004Nt-Qn; Mon, 26 Jun 2023 10:15:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qDmzk-0004NC-C4 for qemu-devel@nongnu.org; Mon, 26 Jun 2023 10:15:08 -0400 Received: from mail-b.sr.ht ([173.195.146.151]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qDmzh-0002po-VQ for qemu-devel@nongnu.org; Mon, 26 Jun 2023 10:15:08 -0400 Received: from git.sr.ht (unknown [173.195.146.142]) by mail-b.sr.ht (Postfix) with ESMTPSA id 3396A11F041; Mon, 26 Jun 2023 14:15:04 +0000 (UTC) Authentication-Results: mail-b.sr.ht; dkim=none From: ~jhogberg Date: Fri, 09 Jun 2023 14:04:14 +0200 Subject: [PATCH qemu v4 2/2] tests/tcg/aarch64: Add testcases for IC IVAU and dual-mapped code MIME-Version: 1.0 Message-ID: <168778890374.24232.3402138851538068785-2@git.sr.ht> X-Mailer: git.sr.ht In-Reply-To: <168778890374.24232.3402138851538068785-0@git.sr.ht> To: qemu-devel@nongnu.org Cc: peter.maydell@linaro.org Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=173.195.146.151; envelope-from=outgoing@sr.ht; helo=mail-b.sr.ht X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DATE_IN_PAST_96_XX=3.405, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: ~jhogberg Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1687788967878100009 From: John H=C3=B6gberg https://gitlab.com/qemu-project/qemu/-/issues/1034 Signed-off-by: John H=C3=B6gberg Reviewed-by: Peter Maydell --- tests/tcg/aarch64/Makefile.target | 3 +- tests/tcg/aarch64/icivau.c | 189 ++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 tests/tcg/aarch64/icivau.c diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile= .target index 3430fd3cd8..de6566d0d4 100644 --- a/tests/tcg/aarch64/Makefile.target +++ b/tests/tcg/aarch64/Makefile.target @@ -9,9 +9,10 @@ AARCH64_SRC=3D$(SRC_PATH)/tests/tcg/aarch64 VPATH +=3D $(AARCH64_SRC) =20 # Base architecture tests -AARCH64_TESTS=3Dfcvt pcalign-a64 +AARCH64_TESTS=3Dfcvt pcalign-a64 icivau =20 fcvt: LDFLAGS+=3D-lm +icivau: LDFLAGS+=3D-lrt =20 run-fcvt: fcvt $(call run-test,$<,$(QEMU) $<, "$< on $(TARGET_NAME)") diff --git a/tests/tcg/aarch64/icivau.c b/tests/tcg/aarch64/icivau.c new file mode 100644 index 0000000000..e3e8569912 --- /dev/null +++ b/tests/tcg/aarch64/icivau.c @@ -0,0 +1,189 @@ +/* + * Tests the IC IVAU-driven workaround for catching changes made to dual-m= apped + * code that would otherwise go unnoticed in user mode. + * + * Copyright (c) 2023 Ericsson AB + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CODE_SIZE 128 + +typedef int (SelfModTest)(uint32_t, uint32_t*); +typedef int (BasicTest)(int); + +static void mark_code_modified(const uint32_t *exec_data, size_t length) +{ + int dc_required, ic_required; + unsigned long ctr_el0; + + /* + * Clear the data/instruction cache, as indicated by the CTR_ELO.{DIC,= IDC} + * flags. + * + * For completeness we might be tempted to assert that we should fail = when + * the whole code update sequence is omitted, but that would make the = test + * flaky as it can succeed by coincidence on actual hardware. + */ + asm ("mrs %0, ctr_el0\n" : "=3Dr"(ctr_el0)); + + /* CTR_EL0.IDC */ + dc_required =3D !((ctr_el0 >> 28) & 1); + + /* CTR_EL0.DIC */ + ic_required =3D !((ctr_el0 >> 29) & 1); + + if (dc_required) { + size_t dcache_stride, i; + + /* + * Step according to the minimum cache size, as the cache maintena= nce + * instructions operate on the cache line of the given address. + * + * We assume that exec_data is properly aligned. + */ + dcache_stride =3D (4 << ((ctr_el0 >> 16) & 0xF)); + + for (i =3D 0; i < length; i +=3D dcache_stride) { + const char *dc_addr =3D &((const char *)exec_data)[i]; + asm volatile ("dc cvau, %x[dc_addr]\n" + : /* no outputs */ + : [dc_addr] "r"(dc_addr) + : "memory"); + } + + asm volatile ("dmb ish\n"); + } + + if (ic_required) { + size_t icache_stride, i; + + icache_stride =3D (4 << (ctr_el0 & 0xF)); + + for (i =3D 0; i < length; i +=3D icache_stride) { + const char *ic_addr =3D &((const char *)exec_data)[i]; + asm volatile ("ic ivau, %x[ic_addr]\n" + : /* no outputs */ + : [ic_addr] "r"(ic_addr) + : "memory"); + } + + asm volatile ("dmb ish\n"); + } + + asm volatile ("isb sy\n"); +} + +static int basic_test(uint32_t *rw_data, const uint32_t *exec_data) +{ + /* + * As user mode only misbehaved for dual-mapped code when previously + * translated code had been changed, we'll start off with this basic t= est + * function to ensure that there's already some translated code at + * exec_data before the next test. This should cause the next test to = fail + * if `mark_code_modified` fails to invalidate the code. + * + * Note that the payload is in binary form instead of inline assembler + * because we cannot use __attribute__((naked)) on this platform and t= he + * workarounds are at least as ugly as this is. + */ + static const uint32_t basic_payload[] =3D { + 0xD65F03C0 /* 0x00: RET */ + }; + + BasicTest *copied_ptr =3D (BasicTest *)exec_data; + + memcpy(rw_data, basic_payload, sizeof(basic_payload)); + mark_code_modified(exec_data, sizeof(basic_payload)); + + return copied_ptr(1234) =3D=3D 1234; +} + +static int self_modification_test(uint32_t *rw_data, const uint32_t *exec_= data) +{ + /* + * This test is self-modifying in an attempt to cover an edge case whe= re + * the IC IVAU instruction invalidates itself. + * + * Note that the IC IVAU instruction is 16 bytes into the function, in= what + * will be the same cache line as the modifed instruction on machines = with + * a cache line size >=3D 16 bytes. + */ + static const uint32_t self_mod_payload[] =3D { + /* Overwrite the placeholder instruction with the new one. */ + 0xB9001C20, /* 0x00: STR w0, [x1, 0x1C] */ + + /* Get the executable address of the modified instruction. */ + 0x100000A8, /* 0x04: ADR x8, <0x1C> */ + + /* Mark the modified instruction as updated. */ + 0xD50B7B28, /* 0x08: DC CVAU x8 */ + 0xD5033BBF, /* 0x0C: DMB ISH */ + 0xD50B7528, /* 0x10: IC IVAU x8 */ + 0xD5033BBF, /* 0x14: DMB ISH */ + 0xD5033FDF, /* 0x18: ISB */ + + /* Placeholder instruction, overwritten above. */ + 0x52800000, /* 0x1C: MOV w0, 0 */ + + 0xD65F03C0 /* 0x20: RET */ + }; + + SelfModTest *copied_ptr =3D (SelfModTest *)exec_data; + int i; + + memcpy(rw_data, self_mod_payload, sizeof(self_mod_payload)); + mark_code_modified(exec_data, sizeof(self_mod_payload)); + + for (i =3D 1; i < 10; i++) { + /* Replace the placeholder instruction with `MOV w0, i` */ + uint32_t new_instr =3D 0x52800000 | (i << 5); + + if (copied_ptr(new_instr, rw_data) !=3D i) { + return 0; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + const char *shm_name =3D "qemu-test-tcg-aarch64-icivau"; + int fd; + + fd =3D shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + + if (fd < 0) { + return EXIT_FAILURE; + } + + /* Unlink early to avoid leaving garbage in case the test crashes. */ + shm_unlink(shm_name); + + if (ftruncate(fd, MAX_CODE_SIZE) =3D=3D 0) { + const uint32_t *exec_data; + uint32_t *rw_data; + + rw_data =3D mmap(0, MAX_CODE_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + exec_data =3D mmap(0, MAX_CODE_SIZE, PROT_READ | PROT_EXEC, + MAP_SHARED, fd, 0); + + if (rw_data && exec_data) { + if (basic_test(rw_data, exec_data) && + self_modification_test(rw_data, exec_data)) { + return EXIT_SUCCESS; + } + } + } + + return EXIT_FAILURE; +} --=20 2.38.5