From d747b5ae3ca397e99cd602404452cf375affc0a7 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Wed, 21 Oct 2026 15:19:21 +0200 Subject: [PATCH 1/7] feat(core): add --deterministic-rng with per-thread PRNG streams Add a fixed-seed splitmix64 PRNG and the --deterministic-rng=no|yes option (parsed in the early pass, since VG_(ii_create_image) runs before the main option pass). When on: - AT_RANDOM is filled from a dedicated stream instead of the kernel's bytes, so glibc's canary/pointer guard and Go's hash seed are stable. - Every client thread gets its own stream in VG_(alloc_ThreadState), keyed by a monotonic creation counter rather than the ThreadState slot, so the N-th created thread draws the same bytes whichever slot it reuses. Also update the --help snapshots. --- coregrind/m_initimg/initimg-linux.c | 8 ++++++- coregrind/m_libcbase.c | 33 +++++++++++++++++++++++++++++ coregrind/m_main.c | 8 +++++++ coregrind/m_options.c | 1 + coregrind/m_scheduler/scheduler.c | 8 +++++++ coregrind/pub_core_options.h | 1 + coregrind/pub_core_threadstate.h | 2 ++ include/pub_tool_libcbase.h | 8 +++++++ none/tests/cmdline1.stdout.exp | 2 ++ none/tests/cmdline2.stdout.exp | 2 ++ 10 files changed, 72 insertions(+), 1 deletion(-) diff --git a/coregrind/m_initimg/initimg-linux.c b/coregrind/m_initimg/initimg-linux.c index eca1a9f17..ec630d917 100644 --- a/coregrind/m_initimg/initimg-linux.c +++ b/coregrind/m_initimg/initimg-linux.c @@ -961,7 +961,13 @@ Addr setup_client_stack( void* init_sp, propagated to the client as glibc will assume it is present if it is built for kernel 2.6.29 or later */ auxv->u.a_ptr = strtab; - VG_(memcpy)(strtab, orig_auxv->u.a_ptr, 16); + if (VG_(clo_deterministic_rng)) { + ULong state = VG_(prng_derive)(DETERMINISTIC_RNG_SEED, + DETERMINISTIC_RNG_STREAM_AT_RANDOM); + VG_(prng_fill)(&state, strtab, 16); + } else { + VG_(memcpy)(strtab, orig_auxv->u.a_ptr, 16); + } strtab += 16; break; diff --git a/coregrind/m_libcbase.c b/coregrind/m_libcbase.c index c8ba6454f..a745dfc84 100644 --- a/coregrind/m_libcbase.c +++ b/coregrind/m_libcbase.c @@ -991,6 +991,39 @@ UInt VG_(random)( /*MOD*/UInt* pSeed ) return *pSeed; } +#define SPLITMIX64_GAMMA 0x9E3779B97F4A7C15ULL +#define SPLITMIX64_MIX1 0xBF58476D1CE4E5B9ULL +#define SPLITMIX64_MIX2 0x94D049BB133111EBULL + +static ULong splitmix64_next ( /*MOD*/ULong* state ) +{ + ULong z = (*state += SPLITMIX64_GAMMA); + z = (z ^ (z >> 30)) * SPLITMIX64_MIX1; + z = (z ^ (z >> 27)) * SPLITMIX64_MIX2; + return z ^ (z >> 31); +} + +ULong VG_(prng_derive) ( ULong seed, ULong stream ) +{ + ULong state = seed ^ stream; + return splitmix64_next(&state); +} + +void VG_(prng_fill) ( /*MOD*/ULong* state, void* buf, SizeT n ) +{ + UChar* p = buf; + while (n >= sizeof(ULong)) { + ULong v = splitmix64_next(state); + VG_(memcpy)(p, &v, sizeof(v)); + p += sizeof(v); + n -= sizeof(v); + } + if (n > 0) { + ULong v = splitmix64_next(state); + VG_(memcpy)(p, &v, n); + } +} + /* The following Adler-32 checksum code is taken from zlib-1.2.3, which has the following copyright notice. */ diff --git a/coregrind/m_main.c b/coregrind/m_main.c index a536e5650..1e11f998f 100644 --- a/coregrind/m_main.c +++ b/coregrind/m_main.c @@ -107,6 +107,8 @@ static void usage_NORETURN ( int need_help ) " --allow-file-caps=patt1,patt2,... specifies a list of executables\n" " 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" " --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" @@ -514,6 +516,12 @@ static void process_option (Clo_Mode mode, else if VG_STR_CLOM(cloE, arg, "--allow-file-caps", VG_(clo_allow_file_caps)) {} + // Set up VG_(clo_deterministic_rng). This is needed by + // VG_(ii_create_image), which fills AT_RANDOM before + // main_process_cmd_line_options(). + else if VG_BOOL_CLOM(cloE, arg, "--deterministic-rng", + VG_(clo_deterministic_rng)) {} + // Set up VG_(clo_max_stackframe) and VG_(clo_main_stacksize). // These are needed by VG_(ii_create_image), which happens // before main_process_cmd_line_options(). diff --git a/coregrind/m_options.c b/coregrind/m_options.c index d344db9fb..c231ba8d7 100644 --- a/coregrind/m_options.c +++ b/coregrind/m_options.c @@ -124,6 +124,7 @@ Bool VG_(clo_trace_children) = False; const HChar* VG_(clo_trace_children_skip) = NULL; const HChar* VG_(clo_trace_children_skip_by_arg) = NULL; const HChar* VG_(clo_allow_file_caps) = NULL; +Bool VG_(clo_deterministic_rng) = False; Bool VG_(clo_child_silent_after_fork) = False; const HChar *VG_(clo_log_fname_unexpanded) = NULL; const HChar *VG_(clo_xml_fname_unexpanded) = NULL; diff --git a/coregrind/m_scheduler/scheduler.c b/coregrind/m_scheduler/scheduler.c index 59c8f8287..f2eb95473 100644 --- a/coregrind/m_scheduler/scheduler.c +++ b/coregrind/m_scheduler/scheduler.c @@ -300,6 +300,9 @@ const HChar* name_of_sched_event ( UInt event ) } } +/* Use creation order, not reusable ThreadState slots, for PRNG streams. */ +static ULong n_threads_created = 0; + /* Allocate a completely empty ThreadState record. */ ThreadId VG_(alloc_ThreadState) ( void ) { @@ -311,6 +314,11 @@ ThreadId VG_(alloc_ThreadState) ( void ) if (VG_(threads)[i].thread_name) VG_(free)(VG_(threads)[i].thread_name); VG_(threads)[i].thread_name = NULL; + VG_(threads)[i].prng_state + = VG_(prng_derive)(DETERMINISTIC_RNG_SEED, + DETERMINISTIC_RNG_STREAM_FIRST_THREAD + + n_threads_created); + n_threads_created++; return i; } } diff --git a/coregrind/pub_core_options.h b/coregrind/pub_core_options.h index 945a96c27..b145dc8ba 100644 --- a/coregrind/pub_core_options.h +++ b/coregrind/pub_core_options.h @@ -128,6 +128,7 @@ extern const HChar* VG_(clo_trace_children_skip_by_arg); that may be run although they carry file capabilities. They run without those capabilities, since Valgrind loads them itself. */ extern const HChar* VG_(clo_allow_file_caps); +extern Bool VG_(clo_deterministic_rng); /* After a fork, the child's output can become confusingly intermingled with the parent's output. This is especially problematic when VG_(clo_xml) is True. Setting diff --git a/coregrind/pub_core_threadstate.h b/coregrind/pub_core_threadstate.h index e4b2c3938..a12879852 100644 --- a/coregrind/pub_core_threadstate.h +++ b/coregrind/pub_core_threadstate.h @@ -391,6 +391,8 @@ typedef struct { /* OS-specific thread state */ ThreadOSstate os_state; + ULong prng_state; + /* Error disablement level. A counter which allows selectively disabling error reporting in threads. When zero, reporting is enabled. When nonzero, it is disabled. This is controlled by diff --git a/include/pub_tool_libcbase.h b/include/pub_tool_libcbase.h index 7fb7bf04d..e20f5c431 100644 --- a/include/pub_tool_libcbase.h +++ b/include/pub_tool_libcbase.h @@ -235,6 +235,14 @@ extern Int VG_(log2_64)( ULong x ); // non-NULL, it uses and updates whatever pSeed points at. extern UInt VG_(random) ( /*MOD*/UInt* pSeed ); +/* Splitmix64 streams; callers own the state and allocate stream ids. */ +#define DETERMINISTIC_RNG_SEED 0x436F6453706565ULL /* "CodSpee" */ +/* Thread stream ids follow creation order. */ +#define DETERMINISTIC_RNG_STREAM_AT_RANDOM 0ULL +#define DETERMINISTIC_RNG_STREAM_FIRST_THREAD 1ULL +extern ULong VG_(prng_derive) ( ULong seed, ULong stream ); +extern void VG_(prng_fill) ( /*MOD*/ULong* state, void* buf, SizeT n ); + /* Update a running Adler-32 checksum with the bytes buf[0..len-1] and return the updated checksum. If buf is NULL, this function returns the required initial value for the checksum. An Adler-32 checksum is diff --git a/none/tests/cmdline1.stdout.exp b/none/tests/cmdline1.stdout.exp index 21486743e..3a8e14d51 100644 --- a/none/tests/cmdline1.stdout.exp +++ b/none/tests/cmdline1.stdout.exp @@ -22,6 +22,8 @@ usage: valgrind [options] prog-and-args --allow-file-caps=patt1,patt2,... specifies a list of executables 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] --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 a465fb2aa..58d77335b 100644 --- a/none/tests/cmdline2.stdout.exp +++ b/none/tests/cmdline2.stdout.exp @@ -22,6 +22,8 @@ usage: valgrind [options] prog-and-args --allow-file-caps=patt1,patt2,... specifies a list of executables 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] --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 From 924804853428cfc09f5f43cbdf61258883ba737e Mon Sep 17 00:00:00 2001 From: not-matthias Date: Wed, 21 Oct 2026 15:19:29 +0200 Subject: [PATCH 2/7] feat(syswrap): serve getrandom and /dev/urandom from the deterministic PRNG With --deterministic-rng=yes, getrandom(2) is completed in the PRE wrapper from the calling thread's stream and never reaches the kernel. fds opened on /dev/random or /dev/urandom (resolved via /proc/self/fd) are tracked across open/openat/openat2, dup/dup2/dup3, fcntl F_DUPFD*, close and close_range; read, pread64, readv, preadv and preadv2 on a tracked fd are served the same way. Served paths clear SfMayBlock, as a PRE that completes the syscall must not keep it. All served paths go through ML_(rng_serve_read), which fails with EFAULT when the client buffer is not writable instead of faulting inside Valgrind. A newly opened device fd is only tracked if a zero-length read succeeds, so O_WRONLY and O_PATH fds keep the kernel's EBADF. --- coregrind/m_syswrap/priv_syswrap-generic.h | 19 ++- coregrind/m_syswrap/syswrap-generic.c | 159 ++++++++++++++++++++- coregrind/m_syswrap/syswrap-linux.c | 92 +++++++++--- include/vki/vki-linux.h | 17 +++ 4 files changed, 262 insertions(+), 25 deletions(-) diff --git a/coregrind/m_syswrap/priv_syswrap-generic.h b/coregrind/m_syswrap/priv_syswrap-generic.h index a59cdf5ef..0d66fc5ca 100644 --- a/coregrind/m_syswrap/priv_syswrap-generic.h +++ b/coregrind/m_syswrap/priv_syswrap-generic.h @@ -69,6 +69,19 @@ void ML_(fd_at_check_allowed)(Int fd, const HChar* path, const HChar* function_name, ThreadId tid, SyscallStatus* status); +extern void ML_(rng_fd_note_open) (Int fd); +extern void ML_(rng_fd_note_dup) (Int oldfd, Int newfd); +extern void ML_(rng_fd_note_close) (Int fd); +extern void ML_(rng_fd_note_close_range) (UInt first, UInt last); +extern Bool ML_(rng_fd_is_marked) (Int fd); +/* Fill a client buffer from the calling thread's stream; returns its + length, or an EFAULT error if the buffer is not writable. */ +extern SysRes ML_(rng_serve_read) (ThreadId tid, Addr buf, SizeT n); +/* Serve a readv-style iovec array, validating the vector and Linux RWF flags + before filling buffers. Return a partial count if a later buffer faults. */ +extern SysRes ML_(rng_serve_iovec) (ThreadId tid, Addr vec, UInt count, + UInt rwf_flags); + extern void ML_(record_fd_close) (ThreadId tid, Int fd); extern Int ML_(get_fd_count) (void); @@ -133,9 +146,9 @@ extern void handle_sys_mprotect (ThreadId tid, SyscallStatus *status, Addr *addr, SizeT *len, Int *prot); /* Helper functions for preadv/preadv2. */ extern -void handle_pre_sys_preadv(ThreadId tid, SyscallStatus* status, - Int fd, Addr vector, Int count, - const char *str); +void handle_pre_sys_preadv(ThreadId tid, SyscallStatus* status, UWord* flags, + Int fd, Addr vector, Int count, Long offset, + UInt rwf_flags, const char *str); extern void handle_post_sys_preadv(ThreadId tid, SyscallStatus* status, Addr vector, Int count); diff --git a/coregrind/m_syswrap/syswrap-generic.c b/coregrind/m_syswrap/syswrap-generic.c index 37f312fe8..9c7717af5 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -1794,6 +1794,135 @@ Bool ML_(fd_allowed)(Int fd, const HChar *syscallname, ThreadId tid, return allowed; } +/* Fork inherits this table; exec resets it, leaving inherited random-device + fds served by the kernel. */ +static const HChar* const rng_dev_random = "/dev/random"; +static const HChar* const rng_dev_urandom = "/dev/urandom"; +static Bool* rng_fd_table = NULL; + +static Bool rng_fd_in_range(Int fd) +{ + return fd >= 0 && fd < VG_(fd_hard_limit); +} + +void ML_(rng_fd_note_open)(Int fd) +{ + const HChar* path; + UChar probe; + + if (!VG_(clo_deterministic_rng) || !rng_fd_in_range(fd)) + return; + if (rng_fd_table == NULL) { + rng_fd_table = VG_(malloc)("syswrap.rng_fd_table", + VG_(fd_hard_limit) * sizeof(Bool)); + VG_(memset)(rng_fd_table, 0, VG_(fd_hard_limit) * sizeof(Bool)); + } + rng_fd_table[fd] = False; + if (!VG_(resolve_filename)(fd, &path)) + return; + if (VG_(strcmp)(path, rng_dev_random) != 0 + && VG_(strcmp)(path, rng_dev_urandom) != 0) + return; + /* Serve only descriptors the kernel would let us read: a zero-length + read fails with EBADF on O_WRONLY and O_PATH descriptors and + consumes no entropy otherwise. */ + if (VG_(read)(fd, &probe, 0) < 0) + return; + rng_fd_table[fd] = True; +} + +void ML_(rng_fd_note_dup)(Int oldfd, Int newfd) +{ + if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL + || !rng_fd_in_range(newfd)) + return; + rng_fd_table[newfd] = rng_fd_in_range(oldfd) && rng_fd_table[oldfd]; +} + +void ML_(rng_fd_note_close)(Int fd) +{ + if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL + || !rng_fd_in_range(fd)) + return; + rng_fd_table[fd] = False; +} + +void ML_(rng_fd_note_close_range)(UInt first, UInt last) +{ + UInt i; + + if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL) + return; + if (last >= (UInt)VG_(fd_hard_limit)) + last = VG_(fd_hard_limit) - 1; + if (first > last) + return; + for (i = first; i <= last; i++) + rng_fd_table[i] = False; +} + +Bool ML_(rng_fd_is_marked)(Int fd) +{ + if (rng_fd_table == NULL || !rng_fd_in_range(fd)) + return False; + return rng_fd_table[fd]; +} + +SysRes ML_(rng_serve_read)(ThreadId tid, Addr buf, SizeT n) +{ + if (!VG_(am_is_valid_for_client)(buf, n, VKI_PROT_WRITE)) + return VG_(mk_SysRes_Error)(VKI_EFAULT); + VG_(prng_fill)(&VG_(threads)[tid].prng_state, (void*)buf, n); + return VG_(mk_SysRes_Success)(n); +} + +/* The kernel truncates the count to 32 bits and copies the vector before + filling buffers, which may overlap it. */ +SysRes ML_(rng_serve_iovec)(ThreadId tid, Addr vec, UInt count, UInt rwf_flags) +{ + /* Syscall wrappers run under the big lock, so one buffer suffices. */ + static struct vki_iovec iov[VKI_UIO_MAXIOV]; + UInt i; + SizeT total = 0; + Bool has_data = False; + SysRes res = VG_(mk_SysRes_Success)(0); + + if (count > VKI_UIO_MAXIOV) + return VG_(mk_SysRes_Error)(VKI_EINVAL); + if (!ML_(safe_to_deref)((void*)vec, count * sizeof(*iov))) + return VG_(mk_SysRes_Error)(VKI_EFAULT); + VG_(memcpy)(iov, (void*)vec, count * sizeof(*iov)); + for (i = 0; i < count; i++) { + if ((SSizeT)iov[i].iov_len < 0) + return VG_(mk_SysRes_Error)(VKI_EINVAL); + if (iov[i].iov_len != 0) + has_data = True; + } + + /* Linux returns zero for empty vectors before checking RWF flags. */ + if (!has_data) + return VG_(mk_SysRes_Success)(0); + +#if defined(VGO_linux) + const UInt supported_flags = + VKI_RWF_HIPRI | VKI_RWF_DSYNC | VKI_RWF_SYNC | + VKI_RWF_NOWAIT | VKI_RWF_APPEND | VKI_RWF_NOAPPEND; + if (rwf_flags & ~supported_flags) + return VG_(mk_SysRes_Error)(VKI_EOPNOTSUPP); +#endif + + for (i = 0; i < count; i++) { + res = ML_(rng_serve_read)(tid, (Addr)iov[i].iov_base, iov[i].iov_len); + if (sr_isError(res)) + break; + total += iov[i].iov_len; + } + /* A fault after some bytes were written reports the partial count. */ + if (sr_isError(res) && total == 0) + return res; + return VG_(mk_SysRes_Success)(total); +} + void ML_(fd_at_check_allowed)(Int fd, const HChar* path, const HChar* function_name, ThreadId tid, SyscallStatus* status) { if ((ML_(safe_to_deref) (path, 1)) && (path[0] != '/')) @@ -3277,6 +3406,20 @@ PRE(sys_pread64) # error Unexpected word size #endif PRE_MEM_WRITE( "pread64(buf)", ARG2, ARG3 ); + + if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(ARG1)) { +#if VG_WORDSIZE == 4 + Long offset = MERGE64(ARG4, ARG5); +#else + Long offset = SARG4; +#endif + *flags &= ~SfMayBlock; + /* The kernel rejects negative offsets before reading. */ + if (offset < 0) + SET_STATUS_Failure(VKI_EINVAL); + else + SET_STATUS_from_SysRes(ML_(rng_serve_read)(tid, ARG2, ARG3)); + } } POST(sys_pread64) { @@ -3790,6 +3933,7 @@ PRE(sys_close) file descriptor is still really closed/invalid. So we do the recording and checking here. */ if (VG_(clo_track_fds)) ML_(record_fd_close)(tid, ARG1); + ML_(rng_fd_note_close)(ARG1); } } @@ -3809,6 +3953,7 @@ POST(sys_dup) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } } @@ -3827,6 +3972,7 @@ POST(sys_dup2) vg_assert(SUCCESS); if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } PRE(sys_fchdir) @@ -4946,6 +5092,7 @@ POST(sys_open) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_with_given_name)(tid, RES, (HChar*)(Addr)ARG1); + ML_(rng_fd_note_open)(RES); } } @@ -4959,8 +5106,13 @@ PRE(sys_read) if (!ML_(fd_allowed)(ARG1, "read", tid, False)) SET_STATUS_Failure( VKI_EBADF ); - else + else { PRE_MEM_WRITE( "read(buf)", ARG2, ARG3 ); + if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(ARG1)) { + *flags &= ~SfMayBlock; + SET_STATUS_from_SysRes(ML_(rng_serve_read)(tid, ARG2, ARG3)); + } + } } POST(sys_read) @@ -5126,6 +5278,11 @@ PRE(sys_readv) PRE_MEM_WRITE(buf, (Addr)vec[i].iov_base, vec[i].iov_len ); } } + + if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(ARG1)) { + *flags &= ~SfMayBlock; + SET_STATUS_from_SysRes(ML_(rng_serve_iovec)(tid, ARG2, ARG3, 0)); + } } } diff --git a/coregrind/m_syswrap/syswrap-linux.c b/coregrind/m_syswrap/syswrap-linux.c index 718f8aee1..f8e0d5600 100644 --- a/coregrind/m_syswrap/syswrap-linux.c +++ b/coregrind/m_syswrap/syswrap-linux.c @@ -4166,6 +4166,7 @@ POST(sys_dup3) vg_assert(SUCCESS); if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } PRE(sys_quotactl) @@ -4357,6 +4358,17 @@ PRE(sys_getrandom) PRE_REG_READ3(int, "getrandom", char *, buf, vki_size_t, count, unsigned int, flags); PRE_MEM_WRITE( "getrandom(cpu)", ARG1, ARG2 ); + + if (!VG_(clo_deterministic_rng)) + return; + /* Reject flags as the kernel does; valid ones never block here. */ + if ((ARG3 & ~(VKI_GRND_NONBLOCK | VKI_GRND_RANDOM | VKI_GRND_INSECURE)) != 0 + || (ARG3 & (VKI_GRND_RANDOM | VKI_GRND_INSECURE)) + == (VKI_GRND_RANDOM | VKI_GRND_INSECURE)) { + SET_STATUS_Failure(VKI_EINVAL); + return; + } + SET_STATUS_from_SysRes(ML_(rng_serve_read)(tid, ARG1, ARG2)); } POST(sys_getrandom) @@ -6295,6 +6307,7 @@ POST(sys_openat) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_with_given_name)(tid, RES, (HChar*)(Addr)ARG2); + ML_(rng_fd_note_open)(RES); } } @@ -6667,8 +6680,9 @@ POST(sys_open_by_handle_at) p{read,write}v wrappers ------------------------------------------------------------------ */ /* This handles the common part of the PRE macro for preadv and preadv2. */ -void handle_pre_sys_preadv(ThreadId tid, SyscallStatus* status, - Int fd, Addr vector, Int count, const char *str) +void handle_pre_sys_preadv(ThreadId tid, SyscallStatus* status, UWord* flags, + Int fd, Addr vector, Int count, Long offset, + UInt rwf_flags, const char *str) { struct vki_iovec * vec; Int i; @@ -6677,22 +6691,35 @@ void handle_pre_sys_preadv(ThreadId tid, SyscallStatus* status, if (!ML_(fd_allowed)(fd, str, tid, False)) { SET_STATUS_Failure( VKI_EBADF ); - } else if (count > 0) { - VG_(strcpy) (tmp, str); - VG_(strcat) (tmp, "(vector)"); - PRE_MEM_READ( tmp, vector, count * sizeof(struct vki_iovec) ); + return; + } + if (count <= 0) + return; - if (ML_(safe_to_deref) ((void *)(Addr)vector, - count * sizeof(struct vki_iovec))) { - vec = (struct vki_iovec *)(Addr)vector; - for (i = 0; i < count; i++) { - /* Note: building such a dynamic error string is *not* - a pattern to follow. See bug 417075. */ - VG_(snprintf) (tmp, 30, "%s(vector[%d])", str, i); - PRE_MEM_WRITE( tmp, (Addr)vec[i].iov_base, vec[i].iov_len ); - } + VG_(strcpy) (tmp, str); + VG_(strcat) (tmp, "(vector)"); + PRE_MEM_READ( tmp, vector, count * sizeof(struct vki_iovec) ); + + if (ML_(safe_to_deref) ((void *)(Addr)vector, + count * sizeof(struct vki_iovec))) { + vec = (struct vki_iovec *)(Addr)vector; + for (i = 0; i < count; i++) { + /* Note: building such a dynamic error string is *not* + a pattern to follow. See bug 417075. */ + VG_(snprintf) (tmp, 30, "%s(vector[%d])", str, i); + PRE_MEM_WRITE( tmp, (Addr)vec[i].iov_base, vec[i].iov_len ); } } + + if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(fd)) { + *flags &= ~SfMayBlock; + /* The kernel rejects negative offsets before reading. */ + if (offset < 0) + SET_STATUS_Failure(VKI_EINVAL); + else + SET_STATUS_from_SysRes( + ML_(rng_serve_iovec)(tid, vector, count, rwf_flags)); + } } /* This handles the common part of the POST macro for preadv and preadv2. */ @@ -6740,8 +6767,13 @@ PRE(sys_preadv) Int fd = ARG1; Addr vector = ARG2; Int count = ARG3; +#if VG_WORDSIZE == 4 + Long offset = LOHI64(ARG4, ARG5); +#else + Long offset = SARG4; +#endif - handle_pre_sys_preadv(tid, status, fd, vector, count, str); + handle_pre_sys_preadv(tid, status, flags, fd, vector, count, offset, 0, str); } @@ -6766,20 +6798,30 @@ PRE(sys_preadv2) PRE_REG_READ6(ssize_t, "preadv2", unsigned long, fd, const struct iovec *, vector, unsigned long, count, vki_u32, offset_low, - vki_u32, offset_high, unsigned long, flags); + vki_u32, offset_high, unsigned int, flags); #elif VG_WORDSIZE == 8 - PRINT("sys_preadv2 ( %lu, %#lx, %lu, %ld, %lu )", ARG1, ARG2, ARG3, SARG4, ARG5); - PRE_REG_READ5(ssize_t, "preadv2", + PRINT("sys_preadv2 ( %lu, %#lx, %lu, %ld, %lu )", ARG1, ARG2, ARG3, SARG4, ARG6); + PRE_REG_READ6(ssize_t, "preadv2", unsigned long, fd, const struct iovec *, vector, - unsigned long, count, Word, offset, unsigned long, flags); + unsigned long, count, Word, offset, + unsigned long, offset_high, unsigned int, flags); #else # error Unexpected word size #endif Int fd = ARG1; Addr vector = ARG2; Int count = ARG3; +#if VG_WORDSIZE == 4 + Long offset = LOHI64(ARG4, ARG5); +#else + Long offset = SARG4; +#endif - handle_pre_sys_preadv(tid, status, fd, vector, count, str); + /* preadv2 treats -1 as "use the file position". */ + if (offset == -1) + offset = 0; + handle_pre_sys_preadv(tid, status, flags, fd, vector, count, offset, + (UInt)ARG6, str); } POST(sys_preadv2) @@ -7515,6 +7557,7 @@ POST(sys_fcntl) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } } else if (ARG2 == VKI_F_DUPFD_CLOEXEC) { @@ -7525,6 +7568,7 @@ POST(sys_fcntl) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } } else if (ARG2 == VKI_F_GETOWN_EX) { POST_MEM_WRITE(ARG3, sizeof(struct vki_f_owner_ex)); @@ -7640,6 +7684,7 @@ POST(sys_fcntl64) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } } else if (ARG2 == VKI_F_DUPFD_CLOEXEC) { @@ -7650,6 +7695,7 @@ POST(sys_fcntl64) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_named)(tid, RES); + ML_(rng_fd_note_dup)(ARG1, RES); } } else if (ARG2 == VKI_F_GETOWN_EX) { POST_MEM_WRITE(ARG3, sizeof(struct vki_f_owner_ex)); @@ -14165,6 +14211,9 @@ POST(sys_close_range) UInt fd; UInt last = ARG2; + if (VG_(clo_deterministic_rng) && (ARG3 & VKI_CLOSE_RANGE_CLOEXEC) == 0) + ML_(rng_fd_note_close_range)(ARG1, ARG2); + if (!VG_(clo_track_fds) || (ARG3 & VKI_CLOSE_RANGE_CLOEXEC) != 0) return; @@ -14348,6 +14397,7 @@ POST(sys_openat2) } else { if (VG_(clo_track_fds)) ML_(record_fd_open_with_given_name)(tid, RES, (HChar*)(Addr)ARG2); + ML_(rng_fd_note_open)(RES); } } diff --git a/include/vki/vki-linux.h b/include/vki/vki-linux.h index 5c937d9e4..8d9c957a1 100644 --- a/include/vki/vki-linux.h +++ b/include/vki/vki-linux.h @@ -5515,6 +5515,23 @@ struct vki_open_how { #define VKI_CLOSE_RANGE_UNSHARE (1U << 1) #define VKI_CLOSE_RANGE_CLOEXEC (1U << 2) +#define VKI_GRND_NONBLOCK 0x0001 +#define VKI_GRND_RANDOM 0x0002 +#define VKI_GRND_INSECURE 0x0004 + +#define VKI_UIO_MAXIOV 1024 + +#ifndef VKI_EOPNOTSUPP +#define VKI_EOPNOTSUPP 95 +#endif + +#define VKI_RWF_HIPRI 0x00000001 +#define VKI_RWF_DSYNC 0x00000002 +#define VKI_RWF_SYNC 0x00000004 +#define VKI_RWF_NOWAIT 0x00000008 +#define VKI_RWF_APPEND 0x00000010 +#define VKI_RWF_NOAPPEND 0x00000020 + struct vki_cachestat_range { __vki_u64 off; __vki_u64 len; From 29bc1573e620a2705721b24f98a78c8c149d4d7a Mon Sep 17 00:00:00 2001 From: not-matthias Date: Wed, 21 Oct 2026 15:19:36 +0200 Subject: [PATCH 3/7] feat(vex): emulate hardware RNG instructions deterministically Route amd64 RDRAND and RDSEED helpers through a VEX callback backed by the calling thread's PRNG stream when --deterministic-rng is enabled, so both instructions always report success with reproducible values. CPUID and instruction decoding still follow the host: the instructions are only advertised and decoded where the host CPU supports them. With the option off, the helpers execute the host instructions as before. Test all operand widths, status flags, advancing streams and fresh threads on hosts with RDRAND and RDSEED. --- .gitignore | 1 + VEX/priv/guest_amd64_helpers.c | 4 ++ VEX/priv/main_main.c | 8 +++ VEX/priv/main_util.h | 2 + VEX/pub/libvex.h | 4 ++ coregrind/m_translate.c | 11 ++++ none/tests/amd64/Makefile.am | 5 ++ none/tests/amd64/deterministic_rng.c | 50 +++++++++++++++++++ none/tests/amd64/deterministic_rng.stderr.exp | 0 none/tests/amd64/deterministic_rng.stdout.exp | 12 +++++ none/tests/amd64/deterministic_rng.vgtest | 3 ++ 11 files changed, 100 insertions(+) create mode 100644 none/tests/amd64/deterministic_rng.c create mode 100644 none/tests/amd64/deterministic_rng.stderr.exp create mode 100644 none/tests/amd64/deterministic_rng.stdout.exp create mode 100644 none/tests/amd64/deterministic_rng.vgtest diff --git a/.gitignore b/.gitignore index fba8ae8f0..aed4d944c 100644 --- a/.gitignore +++ b/.gitignore @@ -1744,6 +1744,7 @@ /none/tests/amd64/clc /none/tests/amd64/cmpxchg /none/tests/amd64/crc32 +/none/tests/amd64/deterministic_rng /none/tests/amd64/f16c /none/tests/amd64/faultstatus /none/tests/amd64/fb_test_amd64 diff --git a/VEX/priv/guest_amd64_helpers.c b/VEX/priv/guest_amd64_helpers.c index ca33f7c81..e73845d5d 100644 --- a/VEX/priv/guest_amd64_helpers.c +++ b/VEX/priv/guest_amd64_helpers.c @@ -3979,6 +3979,8 @@ void amd64g_dirtyhelper_SxDT ( void *address, ULong op ) { 32 bit random number using RDRAND, and return it and the associated rflags.C value. */ ULong amd64g_dirtyhelper_RDRAND ( void ) { + if (vex_random32) + return (1ULL << 32) | (ULong)vex_random32(); # if defined(__x86_64__) ULong res = 0; ULong cflag = 0; @@ -4002,6 +4004,8 @@ ULong amd64g_dirtyhelper_RDRAND ( void ) { } ULong amd64g_dirtyhelper_RDSEED ( void ) { + if (vex_random32) + return (1ULL << 32) | (ULong)vex_random32(); # if defined(__x86_64__) ULong res = 0; ULong cflag = 0; diff --git a/VEX/priv/main_main.c b/VEX/priv/main_main.c index 08e4587f1..d5eb5e153 100644 --- a/VEX/priv/main_main.c +++ b/VEX/priv/main_main.c @@ -212,6 +212,14 @@ void LibVEX_set_VexControl ( VexControl vcon ) __builtin_memcpy(&vex_control, &vcon, sizeof vex_control); } +UInt (*vex_random32)(void) = NULL; + +void LibVEX_SetRandom32 ( UInt (*random32)(void) ) +{ + vassert(vex_initdone); + vex_random32 = random32; +} + /* Exported to library client. */ diff --git a/VEX/priv/main_util.h b/VEX/priv/main_util.h index 4abae5440..57fef2016 100644 --- a/VEX/priv/main_util.h +++ b/VEX/priv/main_util.h @@ -48,6 +48,8 @@ #define NULL ((void*)0) +extern UInt (*vex_random32)(void); + #if defined(_MSC_VER) // building with MSVC # define LIKELY(x) (x) # define UNLIKELY(x) (x) diff --git a/VEX/pub/libvex.h b/VEX/pub/libvex.h index 0be6deb82..9bf1350dc 100644 --- a/VEX/pub/libvex.h +++ b/VEX/pub/libvex.h @@ -687,6 +687,10 @@ extern void LibVEX_Init ( const VexControl* vcon ); +/* Install before guest execution. NULL preserves host RDRAND/RDSEED. + The callback runs on the executing guest thread and returns 32 random bits. */ +extern void LibVEX_SetRandom32 ( UInt (*random32)(void) ); + /*-------------------------------------------------------*/ /*--- Make a translation ---*/ diff --git a/coregrind/m_translate.c b/coregrind/m_translate.c index 4f1c287cc..cfd2b5144 100644 --- a/coregrind/m_translate.c +++ b/coregrind/m_translate.c @@ -772,6 +772,15 @@ void log_bytes ( const HChar* bytes, SizeT nbytes ) VG_(printf)("%c", bytes[i]); } +static UInt deterministic_random32 ( void ) +{ + ThreadId tid = VG_(running_tid); + UInt value; + vg_assert(tid != VG_INVALID_THREADID); + VG_(prng_fill)(&VG_(threads)[tid].prng_state, &value, sizeof(value)); + return value; +} + /* --------- Various helper functions for translation --------- */ @@ -1519,6 +1528,8 @@ Bool VG_(translate) ( ThreadId tid, LibVEX_Init ( &failure_exit, &log_bytes, 1, /* debug_paranoia */ &VG_(clo_vex_control) ); + if (VG_(clo_deterministic_rng)) + LibVEX_SetRandom32(&deterministic_random32); vex_init_done = True; } diff --git a/none/tests/amd64/Makefile.am b/none/tests/amd64/Makefile.am index e4d907c3d..147355368 100644 --- a/none/tests/amd64/Makefile.am +++ b/none/tests/amd64/Makefile.am @@ -87,6 +87,7 @@ EXTRA_DIST = \ rcl-amd64.vgtest rcl-amd64.stdout.exp rcl-amd64.stderr.exp \ rdrand.vgtest rdrand.stdout.exp rdrand.stderr.exp \ rdseed.vgtest rdseed.stdout.exp rdseed.stderr.exp \ + deterministic_rng.vgtest deterministic_rng.stdout.exp deterministic_rng.stderr.exp \ redundantRexW.vgtest redundantRexW.stdout.exp \ redundantRexW.stderr.exp \ smc1.stderr.exp smc1.stdout.exp smc1.vgtest \ @@ -113,6 +114,7 @@ check_PROGRAMS = \ cet_nops \ clc \ cmpxchg \ + deterministic_rng \ getseg \ $(INSN_TESTS) \ nan80and64 \ @@ -180,6 +182,9 @@ if BUILD_RDSEED_TESTS check_PROGRAMS += rdseed endif +deterministic_rng_LDADD = -lpthread +deterministic_rng_CFLAGS = $(AM_CFLAGS) -mno-red-zone + # DDD: these need to be made to work on Darwin like the x86/ ones were. if ! VGCONF_OS_IS_DARWIN diff --git a/none/tests/amd64/deterministic_rng.c b/none/tests/amd64/deterministic_rng.c new file mode 100644 index 000000000..da458ea35 --- /dev/null +++ b/none/tests/amd64/deterministic_rng.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +enum { + STATUS_FLAGS = 0x8d5, + DRAW_COUNT = 2 +}; + +#define REGISTER_SENTINEL UINT64_C(0x5555555555555555) + +#define DRAW(name, instruction, operand) \ +static void* name(void* unused) \ +{ \ + (void)unused; \ + for (int i = 0; i < DRAW_COUNT; i++) { \ + uint64_t value = REGISTER_SENTINEL; \ + uint64_t flags; \ + __asm__ volatile("pushq $0x8d7; popfq; " instruction " " operand \ + "; pushfq; popq %1" \ + : "+r"(value), "=&r"(flags) : : "cc", "memory"); \ + assert((flags & STATUS_FLAGS) == 1); \ + printf(#name ": %016llx\n", (unsigned long long)value); \ + } \ + return NULL; \ +} + +DRAW(rdrand16, "rdrand", "%w0") +DRAW(rdrand32, "rdrand", "%k0") +DRAW(rdrand64, "rdrand", "%q0") +DRAW(rdseed16, "rdseed", "%w0") +DRAW(rdseed32, "rdseed", "%k0") +DRAW(rdseed64, "rdseed", "%q0") + +int main(void) +{ + void* (*draws[])(void*) = { + rdrand16, rdrand32, rdrand64, rdseed16, rdseed32, rdseed64 + }; + /* 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/amd64/deterministic_rng.stderr.exp b/none/tests/amd64/deterministic_rng.stderr.exp new file mode 100644 index 000000000..e69de29bb diff --git a/none/tests/amd64/deterministic_rng.stdout.exp b/none/tests/amd64/deterministic_rng.stdout.exp new file mode 100644 index 000000000..724aa2242 --- /dev/null +++ b/none/tests/amd64/deterministic_rng.stdout.exp @@ -0,0 +1,12 @@ +rdrand16: 555555555555d78e +rdrand16: 555555555555b519 +rdrand32: 00000000131a7a6b +rdrand32: 00000000b8e63bec +rdrand64: 9e504484d55234a4 +rdrand64: c316b49861f426a4 +rdseed16: 555555555555ecf4 +rdseed16: 55555555555590e4 +rdseed32: 000000003a9d93d8 +rdseed32: 000000007ee26b2a +rdseed64: 29a452a97aac8e98 +rdseed64: 1aabedcd856a2b0d diff --git a/none/tests/amd64/deterministic_rng.vgtest b/none/tests/amd64/deterministic_rng.vgtest new file mode 100644 index 000000000..de13b9135 --- /dev/null +++ b/none/tests/amd64/deterministic_rng.vgtest @@ -0,0 +1,3 @@ +prog: deterministic_rng +prereq: test -x deterministic_rng && ../../../tests/x86_amd64_features amd64-rdrand && ../../../tests/x86_amd64_features amd64-rdseed +vgopts: -q --deterministic-rng=yes From 2470ecccf4f71b00bad65336c225ac07eecbd127 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Wed, 21 Oct 2026 15:19:45 +0200 Subject: [PATCH 4/7] test(callgrind): add deterministic_rng regression test Under --deterministic-rng=yes, print AT_RANDOM and bytes from getrandom, /dev/urandom via read, a dup'd fd and readv. Each source is drawn on a fresh, sequentially joined thread so the expected bytes do not depend on how much libc pulls from getrandom(2) before main(). Also check that the served paths keep the kernel's errors: EFAULT for a read-only buffer and EBADF for O_WRONLY and O_PATH device fds. --- .gitignore | 1 + callgrind/tests/Makefile.am | 5 +- callgrind/tests/deterministic_rng.c | 277 +++++++++++++++++++ callgrind/tests/deterministic_rng.stderr.exp | 0 callgrind/tests/deterministic_rng.stdout.exp | 26 ++ callgrind/tests/deterministic_rng.vgtest | 3 + 6 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 callgrind/tests/deterministic_rng.c create mode 100644 callgrind/tests/deterministic_rng.stderr.exp create mode 100644 callgrind/tests/deterministic_rng.stdout.exp create mode 100644 callgrind/tests/deterministic_rng.vgtest diff --git a/.gitignore b/.gitignore index aed4d944c..bd2496358 100644 --- a/.gitignore +++ b/.gitignore @@ -168,6 +168,7 @@ /callgrind/tests/runtime_obj_skip_c /callgrind/tests/runtime_obj_skip_underflow /callgrind/tests/prctl_get_auxv +/callgrind/tests/deterministic_rng /callgrind/tests/*.so /callgrind/tests/out_dbg /callgrind/tests/test-suite-overall.log diff --git a/callgrind/tests/Makefile.am b/callgrind/tests/Makefile.am index f41e58437..0b7f9965d 100644 --- a/callgrind/tests/Makefile.am +++ b/callgrind/tests/Makefile.am @@ -19,6 +19,8 @@ EXTRA_DIST = \ allow_file_caps_refused.stdout.exp \ prctl_get_auxv.vgtest prctl_get_auxv.stderr.exp \ prctl_get_auxv.stdout.exp \ + deterministic_rng.vgtest deterministic_rng.stderr.exp \ + deterministic_rng.stdout.exp \ find_debuginfo.vgtest find_debuginfo.stderr.exp find_debuginfo.post.exp \ runtime_obj_skip_py.vgtest runtime_obj_skip_py.stderr.exp runtime_obj_skip_py.post.exp \ runtime_obj_skip_py.py runtime_obj_skip_py_shim.c \ @@ -44,7 +46,7 @@ EXTRA_DIST = \ inline-crossfile.vgtest inline-crossfile.stderr.exp inline-crossfile.stdout.exp inline-crossfile.post.exp \ inline-crossfile-helper1.h inline-crossfile-helper2.h filter_inline -check_PROGRAMS = clreq register_desc register_desc_threads find_debuginfo simwork threads inline-samefile inline-crossfile runtime_obj_skip_c runtime_obj_skip_underflow +check_PROGRAMS = clreq register_desc register_desc_threads find_debuginfo simwork threads inline-samefile inline-crossfile runtime_obj_skip_c runtime_obj_skip_underflow deterministic_rng if HAVE_PR_GET_AUXV check_PROGRAMS += prctl_get_auxv endif @@ -57,6 +59,7 @@ inline_samefile_CFLAGS = $(AM_CFLAGS) -O2 -g inline_crossfile_CFLAGS = $(AM_CFLAGS) -O2 -g threads_LDADD = -lpthread +deterministic_rng_LDADD = -lpthread register_desc_threads_LDADD = -lpthread # Shim loaded by runtime_obj_skip_py.py via ctypes. Built unconditionally; diff --git a/callgrind/tests/deterministic_rng.c b/callgrind/tests/deterministic_rng.c new file mode 100644 index 000000000..1f8dbeff7 --- /dev/null +++ b/callgrind/tests/deterministic_rng.c @@ -0,0 +1,277 @@ +/* libc startup draws advance the main thread's stream. Use fresh threads + for libc-independent expected bytes; AT_RANDOM has its own stream. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + AT_RANDOM_LEN = 16, + GETRANDOM_LEN = 32, + URANDOM_LEN = 32, + URANDOM_DUP_LEN = 8, + READV_PART_LEN = 8, + READV_PARTS = 2 +}; + +static void fail(const char* name, int err) +{ + printf("%s: error %d\n", name, err); + exit(1); +} + +static void print_hex(const char* label, const unsigned char* p, size_t n) +{ + printf("%s: ", label); + for (size_t i = 0; i < n; i++) + printf("%02x", p[i]); + printf("\n"); +} + +static void getrandom_full(unsigned char* buf, size_t n) +{ + size_t got = 0; + while (got < n) { + ssize_t r = getrandom(buf + got, n - got, 0); + if (r < 0) { + if (errno == EINTR) + continue; + fail("getrandom", errno); + } + got += (size_t)r; + } +} + +static void read_full(const char* name, int fd, unsigned char* buf, size_t n) +{ + size_t got = 0; + while (got < n) { + ssize_t r = read(fd, buf + got, n - got); + if (r < 0) { + if (errno == EINTR) + continue; + fail(name, errno); + } + if (r == 0) + fail(name, EIO); + got += (size_t)r; + } +} + +static int open_urandom(const char* name) +{ + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + fail(name, errno); + return fd; +} + +static void expect_errno(const char* label, ssize_t r, int want) +{ + if (r < 0 && errno == want) + printf("%s: ok\n", label); + else + printf("%s: r=%zd errno=%d, want errno %d\n", label, r, errno, want); +} + +static void expect_count(const char* label, ssize_t r, ssize_t want) +{ + if (r == want) + printf("%s: ok\n", label); + else + printf("%s: r=%zd errno=%d, want %zd\n", label, r, errno, want); +} + +enum { + BAD_GRND_FLAG = 0x80, + BAD_RWF_FLAG = 0x40000000, + ATOMIC_RWF_FLAG = 0x40, + TOO_MANY_IOVS = 1025, + WORD = sizeof(size_t) +}; + +/* Served syscalls must fail like the kernel: EFAULT on a read-only + buffer, EBADF on descriptors not opened for reading, EINVAL on bad + flags, vector counts and negative offsets. Vector reads copy the + vector first and report a partial count when a later buffer faults. */ +static void check_errors(void) +{ + long page = sysconf(_SC_PAGESIZE); + unsigned char* ro = mmap(NULL, page, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + unsigned char buf[1]; + int fd; + + if (ro == MAP_FAILED) + fail("mmap", errno); + expect_errno("getrandom_readonly", getrandom(ro, page, 0), EFAULT); + fd = open_urandom("urandom_readonly"); + expect_errno("urandom_readonly", read(fd, ro, page), EFAULT); + close(fd); + munmap(ro, page); + + fd = open("/dev/urandom", O_WRONLY); + if (fd < 0) + fail("urandom_wronly", errno); + expect_errno("urandom_wronly", read(fd, buf, sizeof(buf)), EBADF); + close(fd); + + fd = open("/dev/urandom", O_PATH); + if (fd < 0) + fail("urandom_opath", errno); + expect_errno("urandom_opath", read(fd, buf, sizeof(buf)), EBADF); + close(fd); + + expect_errno("getrandom_badflag", getrandom(buf, sizeof(buf), BAD_GRND_FLAG), + EINVAL); + expect_errno("getrandom_random_insecure", + getrandom(buf, sizeof(buf), GRND_RANDOM | GRND_INSECURE), + EINVAL); + + static struct iovec many[TOO_MANY_IOVS]; + struct iovec self[1]; + struct iovec partial[2]; + size_t words[WORD]; + int i; + + fd = open_urandom("readv_errors"); + for (i = 0; i < TOO_MANY_IOVS; i++) { + many[i].iov_base = buf; + many[i].iov_len = 0; + } + expect_errno("readv_too_many", readv(fd, many, TOO_MANY_IOVS), EINVAL); + expect_errno("readv_negative_count", + syscall(SYS_readv, fd, many, (unsigned long)-1), EINVAL); + many[0].iov_len = (size_t)-1; + expect_errno("readv_negative_len", readv(fd, many, 1), EINVAL); + + self[0].iov_base = &self[0].iov_len; + self[0].iov_len = WORD; + expect_count("readv_self_overlap", readv(fd, self, 1), WORD); + + partial[0].iov_base = words; + partial[0].iov_len = WORD; + partial[1].iov_base = (void*)WORD; + partial[1].iov_len = WORD; + expect_count("readv_partial", readv(fd, partial, 2), WORD); + + expect_errno("pread_negative_offset", pread(fd, words, WORD, -2), EINVAL); + expect_errno("preadv_negative_offset", preadv(fd, partial, 1, -2), EINVAL); + expect_count("preadv2_current_offset", preadv2(fd, partial, 1, -1, 0), WORD); + + expect_errno("preadv2_badflag", + syscall(SYS_preadv2, fd, partial, 1, 0UL, 0UL, BAD_RWF_FLAG), + EOPNOTSUPP); + expect_errno("preadv2_atomic", + syscall(SYS_preadv2, fd, partial, 1, 0UL, 0UL, ATOMIC_RWF_FLAG), + EOPNOTSUPP); + expect_errno("preadv2_badflag_negative_len", + syscall(SYS_preadv2, fd, many, 1, 0UL, 0UL, BAD_RWF_FLAG), + EINVAL); + expect_errno("preadv2_badflag_negative_offset", + preadv2(fd, partial, 1, -2, BAD_RWF_FLAG), EINVAL); + expect_count("preadv2_dsync", + syscall(SYS_preadv2, fd, partial, 1, 0UL, 0UL, RWF_DSYNC), + WORD); + expect_count("preadv2_badflag_zero_count", + syscall(SYS_preadv2, fd, partial, 0, 0UL, 0UL, BAD_RWF_FLAG), + 0); + partial[0].iov_len = 0; + expect_count("preadv2_badflag_empty", + syscall(SYS_preadv2, fd, partial, 1, 0UL, 0UL, BAD_RWF_FLAG), + 0); + close(fd); +} + +typedef void (*draw_fn)(unsigned char* buf); + +static void draw_getrandom(unsigned char* buf) +{ + getrandom_full(buf, GETRANDOM_LEN); +} + +static void draw_urandom(unsigned char* buf) +{ + int fd = open_urandom("urandom"); + struct iovec iov = { buf, URANDOM_LEN }; + /* Rejection must leave the expected bytes below unchanged. */ + if (syscall(SYS_preadv2, fd, &iov, 1, 0UL, 0UL, BAD_RWF_FLAG) != -1 + || errno != EOPNOTSUPP) + fail("preadv2_badflag_stream", errno); + read_full("urandom", fd, buf, URANDOM_LEN); + close(fd); +} + +static void draw_urandom_dup(unsigned char* buf) +{ + int fd = open_urandom("urandom_dup"); + int fd2 = dup(fd); + if (fd2 < 0) + fail("urandom_dup", errno); + close(fd); + read_full("urandom_dup", fd2, buf, URANDOM_DUP_LEN); + close(fd2); +} + +static void draw_readv(unsigned char* buf) +{ + int fd = open_urandom("readv"); + struct iovec iov[READV_PARTS] = { + { buf, READV_PART_LEN }, + { buf + READV_PART_LEN, READV_PART_LEN }, + }; + size_t total = READV_PARTS * READV_PART_LEN; + ssize_t r = readv(fd, iov, READV_PARTS); + if (r < 0) + fail("readv", errno); + if ((size_t)r != total) + fail("readv", EIO); + close(fd); +} + +typedef struct { + draw_fn draw; + unsigned char* buf; +} draw_job; + +static void* draw_thread(void* arg) +{ + draw_job* job = arg; + job->draw(job->buf); + return NULL; +} + +static void draw_on_fresh_thread(const char* label, draw_fn draw, size_t n) +{ + unsigned char buf[GETRANDOM_LEN]; + draw_job job = { draw, buf }; + pthread_t t; + int err = pthread_create(&t, NULL, draw_thread, &job); + if (err) + fail(label, err); + err = pthread_join(t, NULL); + if (err) + fail(label, err); + print_hex(label, buf, n); +} + + +int main(void) +{ + check_errors(); + print_hex("at_random", (const unsigned char*)getauxval(AT_RANDOM), + AT_RANDOM_LEN); + draw_on_fresh_thread("getrandom", draw_getrandom, GETRANDOM_LEN); + draw_on_fresh_thread("urandom", draw_urandom, URANDOM_LEN); + draw_on_fresh_thread("urandom_dup", draw_urandom_dup, URANDOM_DUP_LEN); + draw_on_fresh_thread("readv", draw_readv, READV_PARTS * READV_PART_LEN); + return 0; +} diff --git a/callgrind/tests/deterministic_rng.stderr.exp b/callgrind/tests/deterministic_rng.stderr.exp new file mode 100644 index 000000000..e69de29bb diff --git a/callgrind/tests/deterministic_rng.stdout.exp b/callgrind/tests/deterministic_rng.stdout.exp new file mode 100644 index 000000000..bb357a4f1 --- /dev/null +++ b/callgrind/tests/deterministic_rng.stdout.exp @@ -0,0 +1,26 @@ +getrandom_readonly: ok +urandom_readonly: ok +urandom_wronly: ok +urandom_opath: ok +getrandom_badflag: ok +getrandom_random_insecure: ok +readv_too_many: ok +readv_negative_count: ok +readv_negative_len: ok +readv_self_overlap: ok +readv_partial: ok +pread_negative_offset: ok +preadv_negative_offset: ok +preadv2_current_offset: ok +preadv2_badflag: ok +preadv2_atomic: ok +preadv2_badflag_negative_len: ok +preadv2_badflag_negative_offset: ok +preadv2_dsync: ok +preadv2_badflag_zero_count: ok +preadv2_badflag_empty: ok +at_random: a6994d39c8a7e9d31c9e23a1d0eb53d2 +getrandom: 8ed714994f0b56b219b5f45e069d4b8b6f48a57231b10044bfc3b47f3ddb9f85 +urandom: 6b7a1a135a8a966fec3be6b88e91d4f72e8600910ab56c52fe70aaf1782306f6 +urandom_dup: a43452d5a1cd8f55 +readv: f4ecbbd9da0e77e1e49041b2e10e5627 diff --git a/callgrind/tests/deterministic_rng.vgtest b/callgrind/tests/deterministic_rng.vgtest new file mode 100644 index 000000000..8939f6820 --- /dev/null +++ b/callgrind/tests/deterministic_rng.vgtest @@ -0,0 +1,3 @@ +prog: deterministic_rng +vgopts: -q --deterministic-rng=yes --callgrind-out-file=callgrind.out.deterministic_rng +cleanup: rm -f callgrind.out.deterministic_rng From d3b114c6995c7c120a100716e8d096599809f344 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Wed, 21 Oct 2026 15:19:46 +0200 Subject: [PATCH 5/7] docs: document --deterministic-rng Describe the option in the core manual and the CodSpeed changelog: the covered randomness sources, per-thread streams, fork and exec behavior, and known limits (thread affinity of draws, Go map iteration order). --- CODSPEED-CHANGELOG.md | 26 +++++++++++++++++++++ docs/xml/manual-core.xml | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/CODSPEED-CHANGELOG.md b/CODSPEED-CHANGELOG.md index 576fb846e..89d247c41 100644 --- a/CODSPEED-CHANGELOG.md +++ b/CODSPEED-CHANGELOG.md @@ -18,6 +18,32 @@ results are unchanged. Reduce simulated cache-miss variation from argument and environment sizes by keeping the Linux initial stack pointer at a fixed offset from the stack top. +### Deterministic randomness: `--deterministic-rng` + +**Feature**: Added `--deterministic-rng=no|yes` (default `no`). When enabled, randomness the client reads from the kernel or the CPU comes from a fixed-seed PRNG, so it is identical across runs. + +**Motivation**: Programs seed hash tables and similar structures from startup randomness. A different seed changes bucket collisions and probe lengths in HashMap-heavy benchmarks, which shows up as instruction-count noise under Callgrind. + +**How it works**: +- `AT_RANDOM` auxv bytes (glibc stack canary and pointer guard, Go runtime hash seed) come from a dedicated stream. +- `getrandom(2)` (Rust `RandomState`, CPython hash seed, OpenSSL, glibc `arc4random`) is served from the PRNG. +- `read`, `pread` and `readv` on `/dev/random` and `/dev/urandom` are served from the PRNG. The device is identified by resolving the fd through `/proc/self/fd`, so symlinks to the devices are covered. +- On amd64, `RDRAND` and `RDSEED` draw from the calling thread's PRNG stream and always report success. CPUID still reports them only when the host CPU supports them. +- Each client thread has its own splitmix64 stream, keyed by a fixed built-in seed and the thread's creation index. The N-th created thread always draws the same bytes regardless of scheduling. Output depends only on the order of draws within each thread. +- A forked child continues its parent's streams, so parent and child draw identical bytes. Under `--trace-children=yes` each exec'd program restarts from the seed. + +**Usage**: +```bash +valgrind --tool=callgrind --deterministic-rng=yes ./your_program +``` + +**Limitations**: +- Time (`clock_gettime`, `rdtsc`), pids and tids are not made deterministic. +- `/proc/sys/kernel/random/*` is not covered. +- `/dev/urandom` fds inherited across `exec` are not covered. +- The seed is fixed; there is no option to change it. +- Each thread draws from its own stream, so the bytes a call returns depend on which thread makes it. If a runtime moves work between threads based on timing (thread pools, work stealing, Go goroutines), the results can still differ between runs. + ### Skip `.plt.sec` in `--skip-plt` **Feature**: Added support for skipping `.plt.sec` sections when using the `--skip-plt` option. diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index ea5dee5cc..ee27062b6 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -793,6 +793,55 @@ in most cases. We group the available options by rough categories. + + + + + + When enabled, randomness the client can read is served + from a fixed-seed PRNG, so it is identical across runs. This + covers the AT_RANDOM auxv + bytes, getrandom(2), and + read, + pread, + readv, + preadv and + preadv2 on + /dev/random and + /dev/urandom (resolved + through /proc/self/fd, so + symlinks count). On amd64, RDRAND + and RDSEED draw from the calling + thread's PRNG stream and always report success. CPUID still + reports them only when the host supports them. + For nonempty preadv2 reads, + the supported flags are RWF_HIPRI, + RWF_DSYNC, + RWF_SYNC, + RWF_NOWAIT, + RWF_APPEND and + RWF_NOAPPEND. Other flags return + EOPNOTSUPP without advancing the + PRNG stream. + Each client thread draws from its own stream, keyed by a + fixed built-in seed and the thread's creation index, so output + depends only on the order of draws within each thread. + AT_RANDOM has its own stream. + A forked child continues its parent's streams; with + each exec'd program + restarts from the seed. + Time, pids and tids, + /proc/sys/kernel/random/*, + mmap of the devices, and + device fds inherited across exec + are not covered. The seed cannot be changed. Bytes come from + the stream of the OS thread that makes the call, so a program + whose thread scheduling is not reproducible (for example a Go + program, where map iteration uses the generator of whichever + thread runs the goroutine) can still vary between runs. + + + From d12e154196deebb7689796958cdc377ad701ad73 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Tue, 27 Oct 2026 19:37:02 +0100 Subject: [PATCH 6/7] feat(callgrind): make proc UUID reads deterministic Cover /proc/sys/kernel/random/uuid and boot_id with typed RNG descriptor tracking. Draw UUIDv4 text from the calling thread stream and derive boot_id independently from the fixed seed. Run proc reads under the big lock and replace only returned bytes, retaining kernel offsets, short reads, EOF and errors. Snapshot vector reads before output can overwrite their iovecs. Add regression coverage and document the supported sources. --- CODSPEED-CHANGELOG.md | 7 +- callgrind/tests/deterministic_rng.c | 188 ++++++++++++++++++- callgrind/tests/deterministic_rng.stdout.exp | 10 + coregrind/m_syswrap/priv_syswrap-generic.h | 13 +- coregrind/m_syswrap/syswrap-generic.c | 157 +++++++++++++--- coregrind/m_syswrap/syswrap-linux.c | 31 ++- docs/xml/manual-core.xml | 27 ++- include/pub_tool_libcbase.h | 1 + 8 files changed, 392 insertions(+), 42 deletions(-) diff --git a/CODSPEED-CHANGELOG.md b/CODSPEED-CHANGELOG.md index 89d247c41..6416e4bf7 100644 --- a/CODSPEED-CHANGELOG.md +++ b/CODSPEED-CHANGELOG.md @@ -28,6 +28,7 @@ by keeping the Linux initial stack pointer at a fixed offset from the stack top. - `AT_RANDOM` auxv bytes (glibc stack canary and pointer guard, Go runtime hash seed) come from a dedicated stream. - `getrandom(2)` (Rust `RandomState`, CPython hash seed, OpenSSL, glibc `arc4random`) is served from the PRNG. - `read`, `pread` and `readv` on `/dev/random` and `/dev/urandom` are served from the PRNG. The device is identified by resolving the fd through `/proc/self/fd`, so symlinks to the devices are covered. +- Reads of `/proc/sys/kernel/random/uuid` return lowercase UUIDv4 text from the thread's stream; `/proc/sys/kernel/random/boot_id` uses a fixed seed-derived UUID independent of thread or read order. This covers `read`, `pread`, `readv`, `preadv` and `preadv2`. The kernel preserves offsets, short reads, EOF and errors; only returned bytes are replaced. UUIDs are not cached across short reads. Errors and empty reads consume no PRNG state. - On amd64, `RDRAND` and `RDSEED` draw from the calling thread's PRNG stream and always report success. CPUID still reports them only when the host CPU supports them. - Each client thread has its own splitmix64 stream, keyed by a fixed built-in seed and the thread's creation index. The N-th created thread always draws the same bytes regardless of scheduling. Output depends only on the order of draws within each thread. - A forked child continues its parent's streams, so parent and child draw identical bytes. Under `--trace-children=yes` each exec'd program restarts from the seed. @@ -39,10 +40,10 @@ valgrind --tool=callgrind --deterministic-rng=yes ./your_program **Limitations**: - Time (`clock_gettime`, `rdtsc`), pids and tids are not made deterministic. -- `/proc/sys/kernel/random/*` is not covered. -- `/dev/urandom` fds inherited across `exec` are not covered. +- Only `/proc/sys/kernel/random/uuid` and `/proc/sys/kernel/random/boot_id` are covered among the files under `/proc/sys/kernel/random/`. +- Fds to `/dev/urandom` or the two procfs files inherited across `exec` are not covered. - The seed is fixed; there is no option to change it. -- Each thread draws from its own stream, so the bytes a call returns depend on which thread makes it. If a runtime moves work between threads based on timing (thread pools, work stealing, Go goroutines), the results can still differ between runs. +- Each thread draws from its own stream, so the bytes a call returns depend on which thread makes it. If a runtime moves work between threads based on timing (thread pools, work stealing, Go goroutines), the results can still differ between runs. `boot_id` is the exception: it is derived from the seed alone, not from a thread's stream. ### Skip `.plt.sec` in `--skip-plt` diff --git a/callgrind/tests/deterministic_rng.c b/callgrind/tests/deterministic_rng.c index 1f8dbeff7..678e2a05d 100644 --- a/callgrind/tests/deterministic_rng.c +++ b/callgrind/tests/deterministic_rng.c @@ -1,11 +1,13 @@ /* libc startup draws advance the main thread's stream. Use fresh threads for libc-independent expected bytes; AT_RANDOM has its own stream. */ #define _GNU_SOURCE +#include #include #include #include #include #include +#include #include #include #include @@ -19,9 +21,16 @@ enum { URANDOM_LEN = 32, URANDOM_DUP_LEN = 8, READV_PART_LEN = 8, - READV_PARTS = 2 + READV_PARTS = 2, + UUID_TEXT_LEN = 37, + UUID_SPLIT = 10 }; +static const char uuid_path[] = "/proc/sys/kernel/random/uuid"; +static const char boot_id_path[] = "/proc/sys/kernel/random/boot_id"; + +static unsigned char boot_id_text[UUID_TEXT_LEN]; + static void fail(const char* name, int err) { printf("%s: error %d\n", name, err); @@ -90,6 +99,35 @@ static void expect_count(const char* label, ssize_t r, ssize_t want) printf("%s: r=%zd errno=%d, want %zd\n", label, r, errno, want); } +/* A UUID string is lowercase hex with hyphens at fixed positions, version 4 + at text offset 14 and an RFC variant at offset 19; proc files add \n. */ +static int uuid_pos_ok(size_t pos, unsigned char c) +{ + if (pos == UUID_TEXT_LEN - 1) + return c == '\n'; + if (pos == 8 || pos == 13 || pos == 18 || pos == 23) + return c == '-'; + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); +} + +static int uuid_window_ok(size_t offset, const unsigned char* p, size_t n) +{ + size_t i; + for (i = 0; i < n; i++) + if (!uuid_pos_ok(offset + i, p[i])) + return 0; + return 1; +} + +static int uuid_text_ok(const unsigned char* p) +{ + if (p[14] != '4') + return 0; + if (p[19] != '8' && p[19] != '9' && p[19] != 'a' && p[19] != 'b') + return 0; + return uuid_window_ok(0, p, UUID_TEXT_LEN); +} + enum { BAD_GRND_FLAG = 0x80, BAD_RWF_FLAG = 0x40000000, @@ -263,6 +301,152 @@ static void draw_on_fresh_thread(const char* label, draw_fn draw, size_t n) print_hex(label, buf, n); } +static void run_fresh_thread(const char* label, void* (*fn)(void*), void* arg) +{ + pthread_t t; + int err = pthread_create(&t, NULL, fn, arg); + if (err) + fail(label, err); + err = pthread_join(t, NULL); + if (err) + fail(label, err); +} + +enum proc_read_kind { + ProcRead, + ProcPread, + ProcReadv, + ProcPreadv, + ProcPreadv2, + ProcCurrent, + ProcReadKinds +}; + +static ssize_t +proc_read(enum proc_read_kind kind, int fd, unsigned char* buf, size_t split) +{ + struct iovec vec[] = {{buf, split}, {buf + split, UUID_TEXT_LEN - split}}; + switch (kind) { + case ProcRead: + return read(fd, buf, UUID_TEXT_LEN); + case ProcPread: + return pread(fd, buf, UUID_TEXT_LEN, 0); + case ProcReadv: + return readv(fd, vec, 2); + case ProcPreadv: + return preadv(fd, vec, 2, 0); + case ProcPreadv2: + return preadv2(fd, vec, 2, 0, 0); + case ProcCurrent: + return preadv2(fd, vec, 2, -1, 0); + default: + abort(); + } +} + +static int proc_open(const char* path) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) + fail(path, errno); + return fd; +} + +/* Full reads on a fresh thread give libc-independent expected bytes. + Errors and EOF between draws must not advance the stream. */ +static void* proc_uuid_thread(void* arg) +{ + unsigned char buf[UUID_TEXT_LEN]; + long page = sysconf(_SC_PAGESIZE); + void* ro = mmap(NULL, page, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + int fd = proc_open(uuid_path); + (void)arg; + assert(ro != MAP_FAILED); + for (enum proc_read_kind kind = ProcRead; kind < ProcReadKinds; kind++) { + assert(lseek(fd, 0, SEEK_SET) == 0); + assert(read(fd, ro, UUID_TEXT_LEN) == -1 && errno == EFAULT); + assert(lseek(fd, 0, SEEK_SET) == 0); + assert(proc_read(kind, fd, buf, UUID_TEXT_LEN) == UUID_TEXT_LEN); + assert(uuid_text_ok(buf)); + printf("proc_uuid_%d: %.*s", kind, UUID_TEXT_LEN, buf); + int positional = + kind == ProcPread || kind == ProcPreadv || kind == ProcPreadv2; + assert(lseek(fd, 0, SEEK_CUR) == (positional ? 0 : UUID_TEXT_LEN)); + assert(pread(fd, buf, sizeof(buf), UUID_TEXT_LEN) == 0); + if (!positional) + assert(read(fd, buf, sizeof(buf)) == 0); + } + close(fd); + munmap(ro, page); + + fd = proc_open(boot_id_path); + read_full("boot_id", fd, boot_id_text, UUID_TEXT_LEN); + printf("boot_id: %.*s", UUID_TEXT_LEN, boot_id_text); + assert(lseek(fd, 0, SEEK_SET) == 0); + read_full("boot_id_repeat", fd, buf, UUID_TEXT_LEN); + assert(memcmp(buf, boot_id_text, UUID_TEXT_LEN) == 0); + close(fd); + return NULL; +} + +/* Older kernels return EOF at nonzero offsets; newer ones return a slice. */ +static void* proc_uuid_offsets_thread(void* arg) +{ + unsigned char buf[UUID_TEXT_LEN + 8], bid[UUID_TEXT_LEN]; + int fd = proc_open(uuid_path), fd2 = dup(fd); + ssize_t r; + (void)arg; + assert(fd2 >= 0); + assert(read(fd, buf, UUID_SPLIT) == UUID_SPLIT); + assert(uuid_window_ok(0, buf, UUID_SPLIT)); + r = read(fd2, buf, sizeof(buf)); + assert(r == 0 || (r == UUID_TEXT_LEN - UUID_SPLIT && + uuid_window_ok(UUID_SPLIT, buf, r))); + assert(lseek(fd, 0, SEEK_CUR) == UUID_SPLIT + r); + close(fd2); + + for (enum proc_read_kind kind = ProcReadv; kind < ProcReadKinds; kind++) { + assert(lseek(fd, 0, SEEK_SET) == 0); + r = proc_read(kind, fd, buf, UUID_SPLIT); + assert((r == UUID_TEXT_LEN && uuid_text_ok(buf)) || + (r == UUID_SPLIT && uuid_window_ok(0, buf, r))); + } + const int offsets[] = {4, UUID_TEXT_LEN - 1, UUID_TEXT_LEN}; + for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + r = pread(fd, buf, sizeof(buf), offsets[i]); + assert(r == 0 || (r == UUID_TEXT_LEN - offsets[i] && + uuid_window_ok(offsets[i], buf, r))); + } + expect_errno("proc_uuid_pread_negative", pread(fd, buf, 1, -2), EINVAL); + close(fd); + fd = open(uuid_path, O_PATH); + assert(fd >= 0); + expect_errno("proc_uuid_opath", read(fd, buf, 1), EBADF); + close(fd); + + fd = proc_open(boot_id_path); + read_full("boot_id_other_thread", fd, bid, UUID_TEXT_LEN); + assert(memcmp(bid, boot_id_text, UUID_TEXT_LEN) == 0); + /* The output overwrites the iovec that the kernel already imported. */ + struct iovec vec[] = {{NULL, sizeof(size_t)}, + {buf, UUID_TEXT_LEN - sizeof(size_t)}}; + vec[0].iov_base = &vec[0].iov_len; + assert(lseek(fd, 0, SEEK_SET) == 0); + r = readv(fd, vec, 2); + assert(r == UUID_TEXT_LEN || r == sizeof(size_t)); + assert(memcmp(&vec[0].iov_len, bid, sizeof(size_t)) == 0); + assert(r != UUID_TEXT_LEN || + memcmp(buf, bid + sizeof(size_t), r - sizeof(size_t)) == 0); + fd2 = proc_open(uuid_path); + read_full("uuid_between_boot_reads", fd2, buf, UUID_TEXT_LEN); + close(fd2); + assert(lseek(fd, 0, SEEK_SET) == 0); + read_full("boot_id_read_order", fd, buf, UUID_TEXT_LEN); + assert(memcmp(bid, buf, UUID_TEXT_LEN) == 0); + close(fd); + puts("proc_uuid_offsets: ok"); + return NULL; +} int main(void) { @@ -273,5 +457,7 @@ int main(void) draw_on_fresh_thread("urandom", draw_urandom, URANDOM_LEN); draw_on_fresh_thread("urandom_dup", draw_urandom_dup, URANDOM_DUP_LEN); draw_on_fresh_thread("readv", draw_readv, READV_PARTS * READV_PART_LEN); + run_fresh_thread("proc_uuid", proc_uuid_thread, NULL); + run_fresh_thread("proc_uuid_offsets", proc_uuid_offsets_thread, NULL); return 0; } diff --git a/callgrind/tests/deterministic_rng.stdout.exp b/callgrind/tests/deterministic_rng.stdout.exp index bb357a4f1..8a80b9ee9 100644 --- a/callgrind/tests/deterministic_rng.stdout.exp +++ b/callgrind/tests/deterministic_rng.stdout.exp @@ -24,3 +24,13 @@ getrandom: 8ed714994f0b56b219b5f45e069d4b8b6f48a57231b10044bfc3b47f3ddb9f85 urandom: 6b7a1a135a8a966fec3be6b88e91d4f72e8600910ab56c52fe70aaf1782306f6 urandom_dup: a43452d5a1cd8f55 readv: f4ecbbd9da0e77e1e49041b2e10e5627 +proc_uuid_0: d8939d3a-1526-46e4-aa6b-e27e1238ab1f +proc_uuid_1: 5ab7ae59-ff8c-4f96-a562-fe86d3cb0459 +proc_uuid_2: 19315e0a-1252-4cdf-add5-921798381e25 +proc_uuid_3: 1e19eeec-dee4-467f-9d5a-05047eb9fd1c +proc_uuid_4: 93bfedd5-373b-4456-81cd-5a66024086a9 +proc_uuid_5: e37d863b-5e10-48e9-99f5-f25d37a736c3 +boot_id: 42a6a49b-c07f-40e7-b99e-dd747582b7d6 +proc_uuid_pread_negative: ok +proc_uuid_opath: ok +proc_uuid_offsets: ok diff --git a/coregrind/m_syswrap/priv_syswrap-generic.h b/coregrind/m_syswrap/priv_syswrap-generic.h index 0d66fc5ca..a3e9290f6 100644 --- a/coregrind/m_syswrap/priv_syswrap-generic.h +++ b/coregrind/m_syswrap/priv_syswrap-generic.h @@ -69,11 +69,15 @@ void ML_(fd_at_check_allowed)(Int fd, const HChar* path, const HChar* function_name, ThreadId tid, SyscallStatus* status); +typedef enum { RngFdNone, RngFdBytes, RngFdUuid, RngFdBootId } RngFdKind; + +typedef enum { RngReadBuffer, RngReadVector } RngReadKind; + extern void ML_(rng_fd_note_open) (Int fd); extern void ML_(rng_fd_note_dup) (Int oldfd, Int newfd); extern void ML_(rng_fd_note_close) (Int fd); extern void ML_(rng_fd_note_close_range) (UInt first, UInt last); -extern Bool ML_(rng_fd_is_marked) (Int fd); +extern RngFdKind ML_(rng_fd_kind)(Int fd); /* Fill a client buffer from the calling thread's stream; returns its length, or an EFAULT error if the buffer is not writable. */ extern SysRes ML_(rng_serve_read) (ThreadId tid, Addr buf, SizeT n); @@ -81,7 +85,12 @@ extern SysRes ML_(rng_serve_read) (ThreadId tid, Addr buf, SizeT n); before filling buffers. Return a partial count if a later buffer faults. */ extern SysRes ML_(rng_serve_iovec) (ThreadId tid, Addr vec, UInt count, UInt rwf_flags); - +/* Run a proc UUID read under the big lock, then replace the returned text. + Vector writes are also reported to the tool here, using a saved iovec. */ +extern SysRes ML_(rng_serve_proc_read)(ThreadId tid, + const SyscallArgs* args, + Long offset, + RngReadKind read_kind); extern void ML_(record_fd_close) (ThreadId tid, Int fd); extern Int ML_(get_fd_count) (void); diff --git a/coregrind/m_syswrap/syswrap-generic.c b/coregrind/m_syswrap/syswrap-generic.c index 9c7717af5..f8da46666 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -1794,11 +1794,11 @@ Bool ML_(fd_allowed)(Int fd, const HChar *syscallname, ThreadId tid, return allowed; } -/* Fork inherits this table; exec resets it, leaving inherited random-device +/* Fork inherits this table; exec resets it, leaving inherited random-source fds served by the kernel. */ static const HChar* const rng_dev_random = "/dev/random"; static const HChar* const rng_dev_urandom = "/dev/urandom"; -static Bool* rng_fd_table = NULL; +static RngFdKind* rng_fd_table = NULL; static Bool rng_fd_in_range(Int fd) { @@ -1808,43 +1808,52 @@ static Bool rng_fd_in_range(Int fd) void ML_(rng_fd_note_open)(Int fd) { const HChar* path; - UChar probe; + UChar probe; + RngFdKind kind = RngFdNone; if (!VG_(clo_deterministic_rng) || !rng_fd_in_range(fd)) return; if (rng_fd_table == NULL) { rng_fd_table = VG_(malloc)("syswrap.rng_fd_table", - VG_(fd_hard_limit) * sizeof(Bool)); - VG_(memset)(rng_fd_table, 0, VG_(fd_hard_limit) * sizeof(Bool)); + VG_(fd_hard_limit)* sizeof(*rng_fd_table)); + VG_(memset)(rng_fd_table, 0, VG_(fd_hard_limit)* sizeof(*rng_fd_table)); } - rng_fd_table[fd] = False; + rng_fd_table[fd] = RngFdNone; if (!VG_(resolve_filename)(fd, &path)) return; - if (VG_(strcmp)(path, rng_dev_random) != 0 - && VG_(strcmp)(path, rng_dev_urandom) != 0) + if (VG_(strcmp)(path, rng_dev_random) == 0 || + VG_(strcmp)(path, rng_dev_urandom) == 0) + kind = RngFdBytes; +#if defined(VGO_linux) + else if (VG_(strcmp)(path, "/proc/sys/kernel/random/uuid") == 0) + kind = RngFdUuid; + else if (VG_(strcmp)(path, "/proc/sys/kernel/random/boot_id") == 0) + kind = RngFdBootId; +#endif + if (kind == RngFdNone) return; /* Serve only descriptors the kernel would let us read: a zero-length read fails with EBADF on O_WRONLY and O_PATH descriptors and consumes no entropy otherwise. */ if (VG_(read)(fd, &probe, 0) < 0) return; - rng_fd_table[fd] = True; + rng_fd_table[fd] = kind; } void ML_(rng_fd_note_dup)(Int oldfd, Int newfd) { - if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL - || !rng_fd_in_range(newfd)) + if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL || + !rng_fd_in_range(newfd)) return; - rng_fd_table[newfd] = rng_fd_in_range(oldfd) && rng_fd_table[oldfd]; + rng_fd_table[newfd] = ML_(rng_fd_kind)(oldfd); } void ML_(rng_fd_note_close)(Int fd) { - if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL - || !rng_fd_in_range(fd)) + if (!VG_(clo_deterministic_rng) || rng_fd_table == NULL || + !rng_fd_in_range(fd)) return; - rng_fd_table[fd] = False; + rng_fd_table[fd] = RngFdNone; } void ML_(rng_fd_note_close_range)(UInt first, UInt last) @@ -1858,13 +1867,13 @@ void ML_(rng_fd_note_close_range)(UInt first, UInt last) if (first > last) return; for (i = first; i <= last; i++) - rng_fd_table[i] = False; + rng_fd_table[i] = RngFdNone; } -Bool ML_(rng_fd_is_marked)(Int fd) +RngFdKind ML_(rng_fd_kind)(Int fd) { if (rng_fd_table == NULL || !rng_fd_in_range(fd)) - return False; + return RngFdNone; return rng_fd_table[fd]; } @@ -1923,6 +1932,88 @@ SysRes ML_(rng_serve_iovec)(ThreadId tid, Addr vec, UInt count, UInt rwf_flags) return VG_(mk_SysRes_Success)(total); } +enum { RNG_UUID_BYTES = 16, RNG_UUID_TEXT_LEN = 37 }; + +static void +rng_uuid_text(ThreadId tid, RngFdKind kind, HChar text[RNG_UUID_TEXT_LEN]) +{ + static const HChar hex[] = "0123456789abcdef"; + UChar uuid[RNG_UUID_BYTES]; + ULong boot_state; + ULong* state = &VG_(threads)[tid].prng_state; + UInt i, pos = 0; + + if (kind == RngFdBootId) { + boot_state = VG_(prng_derive)(DETERMINISTIC_RNG_SEED, + DETERMINISTIC_RNG_STREAM_BOOT_ID); + state = &boot_state; + } + VG_(prng_fill)(state, uuid, sizeof(uuid)); + /* RFC 9562: version 4 and the RFC variant (binary 10). */ + uuid[6] = (uuid[6] & 0x0f) | 0x40; + uuid[8] = (uuid[8] & 0x3f) | 0x80; + for (i = 0; i < RNG_UUID_BYTES; i++) { + if (i == 4 || i == 6 || i == 8 || i == 10) + text[pos++] = '-'; + text[pos++] = hex[uuid[i] >> 4]; + text[pos++] = hex[uuid[i] & 0x0f]; + } + text[pos] = '\n'; +} + +SysRes ML_(rng_serve_proc_read)(ThreadId tid, + const SyscallArgs* args, + Long offset, + RngReadKind read_kind) +{ + /* Keep a snapshot: the kernel output can overwrite the client's iovec. + The big lock protects both this scratch space and the fd position. */ + static struct vki_iovec vector[VKI_UIO_MAXIOV]; + struct vki_iovec scalar = {(void*)(Addr)args->arg2, args->arg3}; + struct vki_iovec* iov = &scalar; + UInt count = 1, i; + SizeT remaining, pos; + HChar text[RNG_UUID_TEXT_LEN]; + SysRes res; + + if (read_kind == RngReadVector) { + count = (UInt)args->arg3; + iov = vector; + if (count <= VKI_UIO_MAXIOV && + ML_(safe_to_deref)((void*)(Addr)args->arg2, count * sizeof(*iov))) + VG_(memcpy)(iov, (void*)(Addr)args->arg2, count * sizeof(*iov)); + else + count = 0; /* Let the kernel choose the error and its precedence. */ + } + + /* proc_do_uuid does not wait for entropy. Keep this syscall synchronous + so no other client thread can seek/close the fd before replacement. + In particular, retain the host kernel's partial-read and EOF rules. */ + res = VG_(do_syscall8)(args->sysno, args->arg1, args->arg2, args->arg3, + args->arg4, args->arg5, args->arg6, args->arg7, + args->arg8); + if (sr_isError(res) || sr_Res(res) == 0) + return res; + + remaining = sr_Res(res); + vg_assert(count > 0 && offset >= 0 && offset < RNG_UUID_TEXT_LEN); + vg_assert(remaining <= RNG_UUID_TEXT_LEN - offset); + /* Like proc_do_uuid, generate anew on each successful retrieval, not + once per open: successive short reads need not share a UUID. */ + rng_uuid_text(tid, ML_(rng_fd_kind)(args->arg1), text); + pos = offset; + for (i = 0; i < count && remaining > 0; i++) { + SizeT n = iov[i].iov_len < remaining ? iov[i].iov_len : remaining; + VG_(memcpy)(iov[i].iov_base, text + pos, n); + if (read_kind == RngReadVector) + POST_MEM_WRITE((Addr)iov[i].iov_base, n); + pos += n; + remaining -= n; + } + vg_assert(remaining == 0); + return res; +} + void ML_(fd_at_check_allowed)(Int fd, const HChar* path, const HChar* function_name, ThreadId tid, SyscallStatus* status) { if ((ML_(safe_to_deref) (path, 1)) && (path[0] != '/')) @@ -3407,15 +3498,19 @@ PRE(sys_pread64) #endif PRE_MEM_WRITE( "pread64(buf)", ARG2, ARG3 ); - if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(ARG1)) { + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (VG_(clo_deterministic_rng)&& kind != RngFdNone) { #if VG_WORDSIZE == 4 Long offset = MERGE64(ARG4, ARG5); #else Long offset = SARG4; #endif *flags &= ~SfMayBlock; + if (kind != RngFdBytes) + SET_STATUS_from_SysRes( + ML_(rng_serve_proc_read)(tid, arrghs, offset, RngReadBuffer)); /* The kernel rejects negative offsets before reading. */ - if (offset < 0) + else if (offset < 0) SET_STATUS_Failure(VKI_EINVAL); else SET_STATUS_from_SysRes(ML_(rng_serve_read)(tid, ARG2, ARG3)); @@ -5108,9 +5203,14 @@ PRE(sys_read) SET_STATUS_Failure( VKI_EBADF ); else { PRE_MEM_WRITE( "read(buf)", ARG2, ARG3 ); - if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(ARG1)) { + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (VG_(clo_deterministic_rng)&& kind != RngFdNone) { *flags &= ~SfMayBlock; - SET_STATUS_from_SysRes(ML_(rng_serve_read)(tid, ARG2, ARG3)); + if (kind == RngFdBytes) + SET_STATUS_from_SysRes(ML_(rng_serve_read)(tid, ARG2, ARG3)); + else + SET_STATUS_from_SysRes(ML_(rng_serve_proc_read)( + tid, arrghs, VG_(lseek)(ARG1, 0, VKI_SEEK_CUR), RngReadBuffer)); } } } @@ -5279,9 +5379,14 @@ PRE(sys_readv) } } - if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(ARG1)) { + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (VG_(clo_deterministic_rng)&& kind != RngFdNone) { *flags &= ~SfMayBlock; - SET_STATUS_from_SysRes(ML_(rng_serve_iovec)(tid, ARG2, ARG3, 0)); + if (kind == RngFdBytes) + SET_STATUS_from_SysRes(ML_(rng_serve_iovec)(tid, ARG2, ARG3, 0)); + else + SET_STATUS_from_SysRes(ML_(rng_serve_proc_read)( + tid, arrghs, VG_(lseek)(ARG1, 0, VKI_SEEK_CUR), RngReadVector)); } } } @@ -5289,6 +5394,10 @@ PRE(sys_readv) POST(sys_readv) { vg_assert(SUCCESS); + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + /* Already reported from the saved vector, before it could be overwritten. */ + if (kind == RngFdUuid || kind == RngFdBootId) + return; if (RES > 0) { Int i; struct vki_iovec * vec = (struct vki_iovec *)(Addr)ARG2; diff --git a/coregrind/m_syswrap/syswrap-linux.c b/coregrind/m_syswrap/syswrap-linux.c index f8e0d5600..1265b3e3e 100644 --- a/coregrind/m_syswrap/syswrap-linux.c +++ b/coregrind/m_syswrap/syswrap-linux.c @@ -6711,7 +6711,7 @@ void handle_pre_sys_preadv(ThreadId tid, SyscallStatus* status, UWord* flags, } } - if (VG_(clo_deterministic_rng) && ML_(rng_fd_is_marked)(fd)) { + if (VG_(clo_deterministic_rng)&& ML_(rng_fd_kind)(fd) == RngFdBytes) { *flags &= ~SfMayBlock; /* The kernel rejects negative offsets before reading. */ if (offset < 0) @@ -6774,7 +6774,13 @@ PRE(sys_preadv) #endif handle_pre_sys_preadv(tid, status, flags, fd, vector, count, offset, 0, str); - + RngFdKind kind = ML_(rng_fd_kind)(fd); + if (status->what != SsComplete && + (kind == RngFdUuid || kind == RngFdBootId)) { + *flags &= ~SfMayBlock; + SET_STATUS_from_SysRes( + ML_(rng_serve_proc_read)(tid, arrghs, offset, RngReadVector)); + } } POST(sys_preadv) @@ -6782,6 +6788,9 @@ POST(sys_preadv) Addr vector = ARG2; Int count = ARG3; + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (kind == RngFdUuid || kind == RngFdBootId) + return; /* Writes were reported from the saved vector in PRE. */ handle_post_sys_preadv(tid, status, vector, count); } @@ -6818,16 +6827,26 @@ PRE(sys_preadv2) #endif /* preadv2 treats -1 as "use the file position". */ - if (offset == -1) - offset = 0; - handle_pre_sys_preadv(tid, status, flags, fd, vector, count, offset, - (UInt)ARG6, str); + handle_pre_sys_preadv(tid, status, flags, fd, vector, count, + offset == -1 ? 0 : offset, (UInt)ARG6, str); + RngFdKind kind = ML_(rng_fd_kind)(fd); + if (status->what != SsComplete && + (kind == RngFdUuid || kind == RngFdBootId)) { + *flags &= ~SfMayBlock; + if (offset == -1) + offset = VG_(lseek)(fd, 0, VKI_SEEK_CUR); + SET_STATUS_from_SysRes( + ML_(rng_serve_proc_read)(tid, arrghs, offset, RngReadVector)); + } } POST(sys_preadv2) { Addr vector = ARG2; Int count = ARG3; + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (kind == RngFdUuid || kind == RngFdBootId) + return; /* Writes were reported from the saved vector in PRE. */ handle_post_sys_preadv(tid, status, vector, count); } diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index ee27062b6..851ecb065 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -814,8 +814,9 @@ in most cases. We group the available options by rough categories. and RDSEED draw from the calling thread's PRNG stream and always report success. CPUID still reports them only when the host supports them. - For nonempty preadv2 reads, - the supported flags are RWF_HIPRI, + For nonempty random-device + preadv2 reads, the supported flags are + RWF_HIPRI, RWF_DSYNC, RWF_SYNC, RWF_NOWAIT, @@ -823,6 +824,15 @@ in most cases. We group the available options by rough categories. RWF_NOAPPEND. Other flags return EOPNOTSUPP without advancing the PRNG stream. + The same read calls also cover + /proc/sys/kernel/random/uuid and + /proc/sys/kernel/random/boot_id. + The former draws fresh UUIDv4 text from the thread's stream for + each nonempty read, including short reads; the latter uses a fixed, + seed-derived UUID independent of thread and read order. + The kernel handles offsets, short reads, EOF and errors; only + returned bytes are replaced. Errors and empty reads consume no + PRNG state. Each client thread draws from its own stream, keyed by a fixed built-in seed and the thread's creation index, so output depends only on the order of draws within each thread. @@ -831,14 +841,19 @@ in most cases. We group the available options by rough categories. each exec'd program restarts from the seed. Time, pids and tids, - /proc/sys/kernel/random/*, - mmap of the devices, and - device fds inherited across exec + the files under /proc/sys/kernel/random/ + other than uuid and + boot_id, + mmap of the devices, and fds to + any of these sources inherited across + exec are not covered. The seed cannot be changed. Bytes come from the stream of the OS thread that makes the call, so a program whose thread scheduling is not reproducible (for example a Go program, where map iteration uses the generator of whichever - thread runs the goroutine) can still vary between runs. + thread runs the goroutine) can still vary between runs. + boot_id is the exception: it is + derived from the seed alone, not from a thread's stream. diff --git a/include/pub_tool_libcbase.h b/include/pub_tool_libcbase.h index e20f5c431..dce048f0f 100644 --- a/include/pub_tool_libcbase.h +++ b/include/pub_tool_libcbase.h @@ -239,6 +239,7 @@ extern UInt VG_(random) ( /*MOD*/UInt* pSeed ); #define DETERMINISTIC_RNG_SEED 0x436F6453706565ULL /* "CodSpee" */ /* Thread stream ids follow creation order. */ #define DETERMINISTIC_RNG_STREAM_AT_RANDOM 0ULL +#define DETERMINISTIC_RNG_STREAM_BOOT_ID (~0ULL) #define DETERMINISTIC_RNG_STREAM_FIRST_THREAD 1ULL extern ULong VG_(prng_derive) ( ULong seed, ULong stream ); extern void VG_(prng_fill) ( /*MOD*/ULong* state, void* buf, SizeT n ); From 81d73657383e88b0d4e441196eb173d930c5f00b Mon Sep 17 00:00:00 2001 From: not-matthias Date: Thu, 29 Oct 2026 14:49:18 +0100 Subject: [PATCH 7/7] feat(vex): hide RDRAND and RDSEED from CPUID with --deterministic-rng With --deterministic-rng, stop advertising RDRAND and RDSEED in CPUID so runtimes that check for them fall back to getrandom or /dev/urandom, which are already served from the PRNG. This matches arm64, where VEX does not support the hardware RNG instruction. Decoding still follows the host, so programs that execute the instructions without checking CPUID keep getting the seeded values. Refs COD-3957 --- VEX/priv/guest_amd64_toIR.c | 8 ++++++-- VEX/pub/libvex.h | 3 ++- docs/xml/manual-core.xml | 9 +++++---- none/tests/amd64/deterministic_rng.c | 7 +++++++ none/tests/amd64/deterministic_rng.stdout.exp | 2 ++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/VEX/priv/guest_amd64_toIR.c b/VEX/priv/guest_amd64_toIR.c index 34ebcbdf9..47a99ea52 100644 --- a/VEX/priv/guest_amd64_toIR.c +++ b/VEX/priv/guest_amd64_toIR.c @@ -22079,8 +22079,12 @@ Long dis_ESC_0F ( if (fAddr == &amd64g_dirtyhelper_CPUID_avx2 || fAddr == &amd64g_dirtyhelper_CPUID_avx_and_cx16) { Bool hasF16C = (archinfo->hwcaps & VEX_HWCAPS_AMD64_F16C) != 0; - Bool hasRDRAND = (archinfo->hwcaps & VEX_HWCAPS_AMD64_RDRAND) != 0; - Bool hasRDSEED = (archinfo->hwcaps & VEX_HWCAPS_AMD64_RDSEED) != 0; + /* A deterministic RNG still serves RDRAND/RDSEED but hides them. */ + Bool advertiseRng = vex_random32 == NULL; + Bool hasRDRAND = advertiseRng + && (archinfo->hwcaps & VEX_HWCAPS_AMD64_RDRAND) != 0; + Bool hasRDSEED = advertiseRng + && (archinfo->hwcaps & VEX_HWCAPS_AMD64_RDSEED) != 0; args = mkIRExprVec_4(IRExpr_GSPTR(), mkIRExpr_HWord(hasF16C ? 1 : 0), mkIRExpr_HWord(hasRDRAND ? 1 : 0), diff --git a/VEX/pub/libvex.h b/VEX/pub/libvex.h index 9bf1350dc..f50971159 100644 --- a/VEX/pub/libvex.h +++ b/VEX/pub/libvex.h @@ -688,7 +688,8 @@ extern void LibVEX_Init ( ); /* Install before guest execution. NULL preserves host RDRAND/RDSEED. - The callback runs on the executing guest thread and returns 32 random bits. */ + Otherwise CPUID hides them, and they return bits from this callback, + which runs on the executing guest thread. */ extern void LibVEX_SetRandom32 ( UInt (*random32)(void) ); diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index 851ecb065..ff2292256 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -810,10 +810,11 @@ in most cases. We group the available options by rough categories. /dev/random and /dev/urandom (resolved through /proc/self/fd, so - symlinks count). On amd64, RDRAND - and RDSEED draw from the calling - thread's PRNG stream and always report success. CPUID still - reports them only when the host supports them. + symlinks count). On amd64, CPUID does not report + 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. For nonempty random-device preadv2 reads, the supported flags are RWF_HIPRI, diff --git a/none/tests/amd64/deterministic_rng.c b/none/tests/amd64/deterministic_rng.c index da458ea35..366036262 100644 --- a/none/tests/amd64/deterministic_rng.c +++ b/none/tests/amd64/deterministic_rng.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -35,6 +36,12 @@ DRAW(rdseed64, "rdseed", "%q0") int main(void) { + unsigned int eax, ebx, ecx, edx; + __cpuid(1, eax, ebx, ecx, edx); + printf("cpuid rdrand: %u\n", (ecx >> 30) & 1); + __cpuid_count(7, 0, eax, ebx, ecx, edx); + printf("cpuid rdseed: %u\n", (ebx >> 18) & 1); + void* (*draws[])(void*) = { rdrand16, rdrand32, rdrand64, rdseed16, rdseed32, rdseed64 }; diff --git a/none/tests/amd64/deterministic_rng.stdout.exp b/none/tests/amd64/deterministic_rng.stdout.exp index 724aa2242..a462030cb 100644 --- a/none/tests/amd64/deterministic_rng.stdout.exp +++ b/none/tests/amd64/deterministic_rng.stdout.exp @@ -1,3 +1,5 @@ +cpuid rdrand: 0 +cpuid rdseed: 0 rdrand16: 555555555555d78e rdrand16: 555555555555b519 rdrand32: 00000000131a7a6b