From 97727ef4c9b648cae1da62ed8d6d1a926bae3c0a Mon Sep 17 00:00:00 2001 From: not-matthias Date: Thu, 29 Oct 2026 16:51:19 +0100 Subject: [PATCH] feat(vex): serve arm64 RNDR and RNDRRS with --deterministic-rng Decode MRS RNDR and RNDRRS (FEAT_RNG). With --deterministic-rng they draw 64 bits from the calling thread's PRNG stream and always report success (NZCV=0000), also on hosts without FEAT_RNG. Otherwise the host register is read and its NZCV is passed through. FEAT_RNG is advertised to the client only when the host has it: AT_HWCAP2 now keeps HWCAP2_RNG instead of being dropped, and the emulated ID_AA64ISAR0_EL1 keeps the RNDR field. Refs COD-4061 --- VEX/priv/guest_arm64_defs.h | 2 + VEX/priv/guest_arm64_helpers.c | 40 ++++++++++++++-- VEX/priv/guest_arm64_toIR.c | 28 +++++++++++ VEX/priv/main_main.c | 1 + VEX/pub/libvex.h | 1 + coregrind/m_initimg/initimg-linux.c | 8 ++++ coregrind/m_machine.c | 13 +++++- coregrind/m_main.c | 2 +- docs/xml/manual-core.xml | 8 +++- include/vki/vki-arm64-linux.h | 2 + none/tests/arm64/Makefile.am | 4 ++ none/tests/arm64/deterministic_rng.c | 46 +++++++++++++++++++ none/tests/arm64/deterministic_rng.stderr.exp | 0 none/tests/arm64/deterministic_rng.stdout.exp | 4 ++ none/tests/arm64/deterministic_rng.vgtest | 3 ++ none/tests/cmdline1.stdout.exp | 2 +- none/tests/cmdline2.stdout.exp | 2 +- 17 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 none/tests/arm64/deterministic_rng.c create mode 100644 none/tests/arm64/deterministic_rng.stderr.exp create mode 100644 none/tests/arm64/deterministic_rng.stdout.exp create mode 100644 none/tests/arm64/deterministic_rng.vgtest diff --git a/VEX/priv/guest_arm64_defs.h b/VEX/priv/guest_arm64_defs.h index 18b08b7ee1..629ae6a583 100644 --- a/VEX/priv/guest_arm64_defs.h +++ b/VEX/priv/guest_arm64_defs.h @@ -132,6 +132,8 @@ extern ULong arm64g_dirtyhelper_MRS_ID_AA64MMFR1_EL1 ( void ); extern ULong arm64g_dirtyhelper_MRS_ID_AA64ISAR0_EL1 ( void ); extern ULong arm64g_dirtyhelper_MRS_ID_AA64ISAR1_EL1 ( void ); +extern void arm64g_dirtyhelper_RNDR ( /*OUT*/V128* res, ULong reseed ); + extern void arm64g_dirtyhelper_PMULLQ ( /*OUT*/V128* res, ULong arg1, ULong arg2 ); diff --git a/VEX/priv/guest_arm64_helpers.c b/VEX/priv/guest_arm64_helpers.c index d6a03fe364..e5260b782a 100644 --- a/VEX/priv/guest_arm64_helpers.c +++ b/VEX/priv/guest_arm64_helpers.c @@ -924,10 +924,11 @@ ULong arm64g_dirtyhelper_MRS_ID_AA64ISAR0_EL1 ( void ) // 9 - SM3 (unsupported) // 10 - SM4 (unsupported) // 11 - DP + // 15 - RNDR - // 10 - // 109876543210 - w &= 0xF000F0FFFFFF; + // 1 10 + // 5432109876543210 + w &= 0xF000F000F0FFFFFFULL; return w; # else @@ -953,6 +954,39 @@ ULong arm64g_dirtyhelper_MRS_ID_AA64ISAR1_EL1 ( void ) # endif } +/* CALLED FROM GENERATED CODE */ +/* DIRTY HELPER (non-referentially-transparent) */ +/* Read RNDR (reseed == 0) or RNDRRS (reseed != 0). Writes the random value + to res->w64[0] and the resulting NZCV, in bits 31:28, to res->w64[1]. With + a deterministic RNG installed, both read the calling thread's stream and + always succeed. Otherwise the host's register is read. On non-arm64 + hosts, report failure. */ +void arm64g_dirtyhelper_RNDR ( /*OUT*/V128* res, ULong reseed ) +{ + if (vex_random32) { + ULong lo = vex_random32(); + ULong hi = vex_random32(); + res->w64[0] = (hi << 32) | lo; + res->w64[1] = 0; + return; + } +# if defined(__aarch64__) && !defined(__arm__) + ULong value = 0; + ULong nzcv = 0; + if (reseed) + __asm__ __volatile__("mrs %0, s3_3_c2_c4_1 ; mrs %1, nzcv" + : "=r"(value), "=r"(nzcv) : : "cc"); + else + __asm__ __volatile__("mrs %0, s3_3_c2_c4_0 ; mrs %1, nzcv" + : "=r"(value), "=r"(nzcv) : : "cc"); + res->w64[0] = value; + res->w64[1] = nzcv & (0xFULL << ARM64G_CC_SHIFT_V); +# else + res->w64[0] = 0; + res->w64[1] = 1ULL << ARM64G_CC_SHIFT_Z; +# endif +} + void arm64g_dirtyhelper_PMULLQ ( /*OUT*/V128* res, ULong arg1, ULong arg2 ) { /* This doesn't need to be a dirty helper, except for the fact that diff --git a/VEX/priv/guest_arm64_toIR.c b/VEX/priv/guest_arm64_toIR.c index 62927537c4..ebab85b823 100644 --- a/VEX/priv/guest_arm64_toIR.c +++ b/VEX/priv/guest_arm64_toIR.c @@ -7978,6 +7978,34 @@ Bool dis_ARM64_branch_etc(/*MB_OUT*/DisResult* dres, UInt insn, DIP("mrs %s, cntfrq_el0\n", nameIReg64orZR(tt)); return True; } + /* ---- Cases for RNDR and RNDRRS (FEAT_RNG) ---- + D5 3B 24 000 Rt MRS Xt, rndr + D5 3B 24 001 Rt MRS Xt, rndrrs + Decoded when the host has FEAT_RNG, or when a deterministic RNG is + installed, since then the host register is never read. On success + NZCV is 0000; on failure Xt is 0 and NZCV is 0100. + */ + if ((INSN(31,0) & 0xFFFFFFC0) == 0xD53B2400 + && ((archinfo->hwcaps & VEX_HWCAPS_ARM64_RNG) || vex_random32)) { + UInt tt = INSN(4,0); + Bool reseed = INSN(5,5) == 1; + IRTemp pair = newTemp(Ity_V128); + IRDirty* d = unsafeIRDirty_1_N( + pair, + 0/*regparms*/, + "arm64g_dirtyhelper_RNDR", + &arm64g_dirtyhelper_RNDR, + mkIRExprVec_2(IRExpr_VECRET(), + mkU64(reseed ? 1 : 0)) + ); + stmt( IRStmt_Dirty(d) ); + IRTemp nzcv = newTemp(Ity_I64); + assign(nzcv, unop(Iop_V128HIto64, mkexpr(pair))); + putIReg64orZR(tt, unop(Iop_V128to64, mkexpr(pair))); + setFlags_COPY(nzcv); + DIP("mrs %s, %s\n", nameIReg64orZR(tt), reseed ? "rndrrs" : "rndr"); + return True; + } /* ------------------ IC_IVAU ------------------ */ /* D5 0B 75 001 Rt ic ivau, rT diff --git a/VEX/priv/main_main.c b/VEX/priv/main_main.c index d5eb5e1534..646dce97be 100644 --- a/VEX/priv/main_main.c +++ b/VEX/priv/main_main.c @@ -1913,6 +1913,7 @@ static const HChar* show_hwcaps_arm64 ( UInt hwcaps ) { VEX_HWCAPS_ARM64_BF16, "bf16" }, { VEX_HWCAPS_ARM64_FP16, "fp16" }, { VEX_HWCAPS_ARM64_VFP16, "vfp16" }, + { VEX_HWCAPS_ARM64_RNG, "rng" }, }; static HChar buf[sizeof prefix + // '\0' diff --git a/VEX/pub/libvex.h b/VEX/pub/libvex.h index f50971159b..1149038d00 100644 --- a/VEX/pub/libvex.h +++ b/VEX/pub/libvex.h @@ -242,6 +242,7 @@ typedef #define VEX_HWCAPS_ARM64_BF16 (1 << 13) #define VEX_HWCAPS_ARM64_FP16 (1 << 14) #define VEX_HWCAPS_ARM64_VFP16 (1 << 15) +#define VEX_HWCAPS_ARM64_RNG (1 << 16) /* MIPS baseline capability */ /* Assigned Company values for bits 23:16 of the PRId Register diff --git a/coregrind/m_initimg/initimg-linux.c b/coregrind/m_initimg/initimg-linux.c index ec630d917d..faa0a2d518 100644 --- a/coregrind/m_initimg/initimg-linux.c +++ b/coregrind/m_initimg/initimg-linux.c @@ -894,6 +894,14 @@ Addr setup_client_stack( void* init_sp, break; # endif +# if defined(VGP_arm64_linux) + case AT_HWCAP2: + /* Limit AT_HWCAP2 to the features VEX supports. RNDR and RNDRRS + are decoded whenever the host has FEAT_RNG. */ + auxv->u.a_val &= VKI_HWCAP2_RNG; + break; +# endif + case AT_ICACHEBSIZE: case AT_DCACHEBSIZE: case AT_UCACHEBSIZE: diff --git a/coregrind/m_machine.c b/coregrind/m_machine.c index c93eaf98a0..2c21fbeb32 100644 --- a/coregrind/m_machine.c +++ b/coregrind/m_machine.c @@ -1856,11 +1856,11 @@ Bool VG_(machine_get_hwcaps)( void ) Bool have_fhm, have_dp, have_sm4, have_sm3, have_sha3, have_rdm; Bool have_atomics, have_i8mm, have_bf16, have_dpbcvap, have_dpbcvadp; - Bool have_vfp16, have_fp16; + Bool have_vfp16, have_fp16, have_rng; have_fhm = have_dp = have_sm4 = have_sm3 = have_sha3 = have_rdm = have_atomics = have_i8mm = have_bf16 = have_dpbcvap - = have_dpbcvadp = have_vfp16 = have_fp16 = False; + = have_dpbcvadp = have_vfp16 = have_fp16 = have_rng = False; /* Some baseline v8.0 kernels do not allow reads of these registers. Use * the same SIGILL handling algorithm as other architectures for such @@ -1938,6 +1938,7 @@ Bool VG_(machine_get_hwcaps)( void ) return True; /* ID_AA64ISAR0_EL1 Instruction set attribute register 0 fields */ + #define ID_AA64ISAR0_RNDR_SHIFT 60 #define ID_AA64ISAR0_FHM_SHIFT 48 #define ID_AA64ISAR0_DP_SHIFT 44 #define ID_AA64ISAR0_SM4_SHIFT 40 @@ -1946,6 +1947,7 @@ Bool VG_(machine_get_hwcaps)( void ) #define ID_AA64ISAR0_RDM_SHIFT 28 #define ID_AA64ISAR0_ATOMICS_SHIFT 20 /* Field values */ + #define ID_AA64ISAR0_RNDR_SUPPORTED 0x1 #define ID_AA64ISAR0_FHM_SUPPORTED 0x1 #define ID_AA64ISAR0_DP_SUPPORTED 0x1 #define ID_AA64ISAR0_SM4_SUPPORTED 0x1 @@ -2032,6 +2034,12 @@ Bool VG_(machine_get_hwcaps)( void ) get_ftr(ID_AA64ISAR0_EL1, ID_AA64ISAR0_ATOMICS_SHIFT, ID_AA64ISAR0_ATOMICS_SUPPORTED, have_atomics); + /* RNDR indicates support for the RNDR and RNDRRS registers. + * Optional for v8.5. + */ + get_ftr(ID_AA64ISAR0_EL1, ID_AA64ISAR0_RNDR_SHIFT, + ID_AA64ISAR0_RNDR_SUPPORTED, have_rng); + /* Read ID_AA64ISAR1_EL1 attributes */ /* I8MM indicates support for SMMLA, SUDOT, UMMLA, USMMLA, and USDOT @@ -2086,6 +2094,7 @@ Bool VG_(machine_get_hwcaps)( void ) if (have_bf16) vai.hwcaps |= VEX_HWCAPS_ARM64_BF16; if (have_fp16) vai.hwcaps |= VEX_HWCAPS_ARM64_FP16; if (have_vfp16) vai.hwcaps |= VEX_HWCAPS_ARM64_VFP16; + if (have_rng) vai.hwcaps |= VEX_HWCAPS_ARM64_RNG; #undef get_cpu_ftr #undef get_ftr diff --git a/coregrind/m_main.c b/coregrind/m_main.c index 1e11f998f4..24ce30a431 100644 --- a/coregrind/m_main.c +++ b/coregrind/m_main.c @@ -108,7 +108,7 @@ static void usage_NORETURN ( int need_help ) " to run despite their file capabilities, which\n" " they do not get under Valgrind\n" " --deterministic-rng=no|yes serve AT_RANDOM, getrandom() and /dev/urandom\n" -" and RDRAND/RDSEED from a fixed-seed PRNG [no]\n" +" and hardware RNG insns from a fixed-seed PRNG [no]\n" " --child-silent-after-fork=no|yes omit child output between fork & exec? [no]\n" " --vgdb=no|yes|full activate gdbserver? [yes]\n" " full is slower but provides precise watchpoint/step\n" diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index ff22922564..e7b97474b6 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -814,7 +814,13 @@ in most cases. We group the available options by rough categories. RDRAND and RDSEED. On hosts that support them, programs that execute them anyway draw from the calling thread's PRNG - stream, which always reports success. + stream, which always reports success. On arm64, + RNDR and + RNDRRS draw from the same stream and + always report success, also on hosts without FEAT_RNG. + HWCAP2_RNG and + ID_AA64ISAR0_EL1.RNDR still report + them only when the host supports them. For nonempty random-device preadv2 reads, the supported flags are RWF_HIPRI, diff --git a/include/vki/vki-arm64-linux.h b/include/vki/vki-arm64-linux.h index f2de1aca00..fe08b92e2d 100644 --- a/include/vki/vki-arm64-linux.h +++ b/include/vki/vki-arm64-linux.h @@ -730,6 +730,8 @@ struct vki_shminfo64 { #define VKI_HWCAP_ILRCPC (1 << 26) #define VKI_HWCAP_FLAGM (1 << 27) +#define VKI_HWCAP2_RNG (1 << 16) + //---------------------------------------------------------------------- // And that's it! //---------------------------------------------------------------------- diff --git a/none/tests/arm64/Makefile.am b/none/tests/arm64/Makefile.am index 4fa101d777..4b0d580c41 100644 --- a/none/tests/arm64/Makefile.am +++ b/none/tests/arm64/Makefile.am @@ -9,6 +9,8 @@ EXTRA_DIST = \ crc32.stdout.exp crc32.stderr.exp crc32.vgtest \ cvtf_imm.stdout.exp cvtf_imm.stderr.exp cvtf_imm.vgtest \ dc_cvax.vgtest dc_cvax.stderr.exp \ + deterministic_rng.stdout.exp deterministic_rng.stderr.exp \ + deterministic_rng.vgtest \ fcvta_s_u.vgtest fcvta_s_u.stderr.exp \ fp_and_simd.stdout.exp fp_and_simd.stderr.exp fp_and_simd.vgtest \ frinta_frintn.stderr.exp frinta_frintn.vgtest \ @@ -33,6 +35,7 @@ check_PROGRAMS = \ bug489338 \ cvtf_imm \ dc_cvax \ + deterministic_rng \ fp_and_simd \ integer \ memory_test \ @@ -92,6 +95,7 @@ fp_and_simd_v82_SOURCES = fp_and_simd_v82.c simd.h simd.c sha512_v82_SOURCES = sha512_v82.c simd.h simd.c bug489338_LDADD = -lm +deterministic_rng_LDADD = -lpthread fp_and_simd_LDADD = -lm simd_v81_LDADD = -lm fp_and_simd_v82_LDADD = -lm diff --git a/none/tests/arm64/deterministic_rng.c b/none/tests/arm64/deterministic_rng.c new file mode 100644 index 0000000000..55dfa6980c --- /dev/null +++ b/none/tests/arm64/deterministic_rng.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +enum { + NZCV_ALL = 0xf0000000, + NZCV_SUCCESS = 0, + DRAW_COUNT = 2 +}; + +#define REGISTER_SENTINEL UINT64_C(0x5555555555555555) + +/* Use the generic register names so the test assembles without +rng. */ +#define DRAW(name, sysreg) \ +static void* name(void* unused) \ +{ \ + (void)unused; \ + for (int i = 0; i < DRAW_COUNT; i++) { \ + uint64_t value = REGISTER_SENTINEL; \ + uint64_t nzcv; \ + __asm__ volatile("msr nzcv, %2; mrs %0, " sysreg "; mrs %1, nzcv" \ + : "=&r"(value), "=&r"(nzcv) \ + : "r"((uint64_t)NZCV_ALL) : "cc"); \ + assert((nzcv & NZCV_ALL) == NZCV_SUCCESS); \ + printf(#name ": %016llx\n", (unsigned long long)value); \ + } \ + return NULL; \ +} + +DRAW(rndr, "s3_3_c2_c4_0") +DRAW(rndrrs, "s3_3_c2_c4_1") + +int main(void) +{ + void* (*draws[])(void*) = { rndr, rndrrs }; + /* Fresh threads keep expected bytes independent of libc startup draws. */ + for (unsigned int i = 0; i < sizeof(draws) / sizeof(draws[0]); i++) { + pthread_t thread; + int err = pthread_create(&thread, NULL, draws[i], NULL); + assert(err == 0); + err = pthread_join(thread, NULL); + assert(err == 0); + } + return 0; +} diff --git a/none/tests/arm64/deterministic_rng.stderr.exp b/none/tests/arm64/deterministic_rng.stderr.exp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/none/tests/arm64/deterministic_rng.stdout.exp b/none/tests/arm64/deterministic_rng.stdout.exp new file mode 100644 index 0000000000..63d772c7f4 --- /dev/null +++ b/none/tests/arm64/deterministic_rng.stdout.exp @@ -0,0 +1,4 @@ +rndr: 5ef4b5199914d78e +rndr: 7fb4c3bf72a5486f +rndrrs: b8e63bec131a7a6b +rndrrs: f1aa70fe9100862e diff --git a/none/tests/arm64/deterministic_rng.vgtest b/none/tests/arm64/deterministic_rng.vgtest new file mode 100644 index 0000000000..855fb4d9df --- /dev/null +++ b/none/tests/arm64/deterministic_rng.vgtest @@ -0,0 +1,3 @@ +prog: deterministic_rng +prereq: test -x deterministic_rng +vgopts: -q --deterministic-rng=yes diff --git a/none/tests/cmdline1.stdout.exp b/none/tests/cmdline1.stdout.exp index 3a8e14d51e..1129051323 100644 --- a/none/tests/cmdline1.stdout.exp +++ b/none/tests/cmdline1.stdout.exp @@ -23,7 +23,7 @@ usage: valgrind [options] prog-and-args to run despite their file capabilities, which they do not get under Valgrind --deterministic-rng=no|yes serve AT_RANDOM, getrandom() and /dev/urandom - and RDRAND/RDSEED from a fixed-seed PRNG [no] + and hardware RNG insns from a fixed-seed PRNG [no] --child-silent-after-fork=no|yes omit child output between fork & exec? [no] --vgdb=no|yes|full activate gdbserver? [yes] full is slower but provides precise watchpoint/step diff --git a/none/tests/cmdline2.stdout.exp b/none/tests/cmdline2.stdout.exp index 58d77335bc..926a2513ac 100644 --- a/none/tests/cmdline2.stdout.exp +++ b/none/tests/cmdline2.stdout.exp @@ -23,7 +23,7 @@ usage: valgrind [options] prog-and-args to run despite their file capabilities, which they do not get under Valgrind --deterministic-rng=no|yes serve AT_RANDOM, getrandom() and /dev/urandom - and RDRAND/RDSEED from a fixed-seed PRNG [no] + and hardware RNG insns from a fixed-seed PRNG [no] --child-silent-after-fork=no|yes omit child output between fork & exec? [no] --vgdb=no|yes|full activate gdbserver? [yes] full is slower but provides precise watchpoint/step