diff --git a/.gitignore b/.gitignore index fba8ae8f0..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 @@ -1744,6 +1745,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/CODSPEED-CHANGELOG.md b/CODSPEED-CHANGELOG.md index 576fb846e..6416e4bf7 100644 --- a/CODSPEED-CHANGELOG.md +++ b/CODSPEED-CHANGELOG.md @@ -18,6 +18,33 @@ 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. +- 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. + +**Usage**: +```bash +valgrind --tool=callgrind --deterministic-rng=yes ./your_program +``` + +**Limitations**: +- Time (`clock_gettime`, `rdtsc`), pids and tids are not made deterministic. +- 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. `boot_id` is the exception: it is derived from the seed alone, not from a thread's stream. + ### Skip `.plt.sec` in `--skip-plt` **Feature**: Added support for skipping `.plt.sec` sections when using the `--skip-plt` option. 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/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/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..f50971159 100644 --- a/VEX/pub/libvex.h +++ b/VEX/pub/libvex.h @@ -687,6 +687,11 @@ extern void LibVEX_Init ( const VexControl* vcon ); +/* Install before guest execution. NULL preserves host RDRAND/RDSEED. + 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) ); + /*-------------------------------------------------------*/ /*--- Make a translation ---*/ 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..678e2a05d --- /dev/null +++ b/callgrind/tests/deterministic_rng.c @@ -0,0 +1,463 @@ +/* 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 +#include +#include + +enum { + AT_RANDOM_LEN = 16, + GETRANDOM_LEN = 32, + URANDOM_LEN = 32, + URANDOM_DUP_LEN = 8, + READV_PART_LEN = 8, + 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); + 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); +} + +/* 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, + 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); +} + +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) +{ + 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); + 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.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..8a80b9ee9 --- /dev/null +++ b/callgrind/tests/deterministic_rng.stdout.exp @@ -0,0 +1,36 @@ +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 +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/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 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/m_syswrap/priv_syswrap-generic.h b/coregrind/m_syswrap/priv_syswrap-generic.h index a59cdf5ef..a3e9290f6 100644 --- a/coregrind/m_syswrap/priv_syswrap-generic.h +++ b/coregrind/m_syswrap/priv_syswrap-generic.h @@ -69,6 +69,28 @@ 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 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); +/* 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); +/* 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); @@ -133,9 +155,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..f8da46666 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -1794,6 +1794,226 @@ Bool ML_(fd_allowed)(Int fd, const HChar *syscallname, ThreadId tid, return allowed; } +/* 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 RngFdKind* 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; + 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(*rng_fd_table)); + VG_(memset)(rng_fd_table, 0, VG_(fd_hard_limit)* sizeof(*rng_fd_table)); + } + 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) + 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] = 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)) + return; + 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)) + return; + rng_fd_table[fd] = RngFdNone; +} + +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] = RngFdNone; +} + +RngFdKind ML_(rng_fd_kind)(Int fd) +{ + if (rng_fd_table == NULL || !rng_fd_in_range(fd)) + return RngFdNone; + 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); +} + +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] != '/')) @@ -3277,6 +3497,24 @@ PRE(sys_pread64) # error Unexpected word size #endif PRE_MEM_WRITE( "pread64(buf)", ARG2, ARG3 ); + + 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. */ + else 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 +4028,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 +4048,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 +4067,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 +5187,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 +5201,18 @@ 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 ); + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (VG_(clo_deterministic_rng)&& kind != RngFdNone) { + *flags &= ~SfMayBlock; + 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)); + } + } } POST(sys_read) @@ -5126,12 +5378,26 @@ PRE(sys_readv) PRE_MEM_WRITE(buf, (Addr)vec[i].iov_base, vec[i].iov_len ); } } + + RngFdKind kind = ML_(rng_fd_kind)(ARG1); + if (VG_(clo_deterministic_rng)&& kind != RngFdNone) { + *flags &= ~SfMayBlock; + 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)); + } } } 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 718f8aee1..1265b3e3e 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_kind)(fd) == RngFdBytes) { + *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,9 +6767,20 @@ 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); + 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) @@ -6750,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); } @@ -6766,26 +6807,46 @@ 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". */ + 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); } @@ -7515,6 +7576,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 +7587,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 +7703,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 +7714,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 +14230,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 +14416,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/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/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/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index ea5dee5cc..ff2292256 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -793,6 +793,71 @@ 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, 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, + RWF_DSYNC, + RWF_SYNC, + RWF_NOWAIT, + RWF_APPEND and + 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. + 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, + 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. + 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 7fb7bf04d..dce048f0f 100644 --- a/include/pub_tool_libcbase.h +++ b/include/pub_tool_libcbase.h @@ -235,6 +235,15 @@ 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_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 ); + /* 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/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; 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..366036262 --- /dev/null +++ b/none/tests/amd64/deterministic_rng.c @@ -0,0 +1,57 @@ +#include +#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) +{ + 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 + }; + /* 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..a462030cb --- /dev/null +++ b/none/tests/amd64/deterministic_rng.stdout.exp @@ -0,0 +1,14 @@ +cpuid rdrand: 0 +cpuid rdseed: 0 +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 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