Skip to main content

detcore/
random.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * This source code is licensed under the BSD-style license found in the
4 * LICENSE file in the root directory of this source tree.
5 */
6
7//! Shared guest random-state and memory operations, independent of a backend.
8//! These synchronous operations preserve draws even when a later write fails.
9
10use std::collections::hash_map::DefaultHasher;
11use std::hash::Hash;
12use std::hash::Hasher;
13
14use rand::RngExt as _;
15use rand::SeedableRng as _;
16use rand_pcg::Pcg64Mcg;
17use reverie::Error;
18use reverie::syscalls::AddrMut;
19use reverie::syscalls::Errno;
20use reverie::syscalls::Getrandom;
21use reverie::syscalls::MemoryAccess;
22use serde::Deserialize;
23use serde::Serialize;
24use sha2::Digest as _;
25use sha2::Sha256;
26
27use crate::detlog;
28use crate::types::DetTid;
29
30pub(crate) const RANDOM_FILL_CHUNK_BYTES: usize = 4096;
31
32/// A user-access random copy failed for a reason other than a guest fault.
33///
34/// Memory may already contain a copied prefix, or even the entire attempted
35/// write. This is a failed run, not a guest errno or a successful short read.
36#[derive(Debug)]
37pub struct RandomCopyFailure {
38    errno: Errno,
39}
40
41impl RandomCopyFailure {
42    /// The exact error returned by the backend's user-access copy.
43    pub fn errno(&self) -> Errno {
44        self.errno
45    }
46}
47
48impl std::fmt::Display for RandomCopyFailure {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "random user-access copy failed: {}", self.errno)?;
51        if self.errno == Errno::EPERM {
52            f.write_str(
53                "; for a non-dumpable ptrace guest, retry with Hermit's default namespace \
54                 configuration (omit --no-namespace); embedders must check tracing permissions \
55                 in the guest's user namespace",
56            )?;
57        }
58        Ok(())
59    }
60}
61
62impl std::error::Error for RandomCopyFailure {}
63
64pub(crate) fn copy_error(error: Errno) -> Error {
65    match error {
66        Errno::EFAULT => Error::Errno(error),
67        errno => Error::Tool(anyhow::Error::new(RandomCopyFailure { errno })),
68    }
69}
70
71pub(crate) fn is_copy_failure(error: &Error) -> bool {
72    matches!(error, Error::Tool(inner) if inner.is::<RandomCopyFailure>())
73}
74
75/// Construct the root guest stream from its configured seed, without creating
76/// a thread or discovering any process metadata.
77pub fn root_prng(seed: u64) -> Pcg64Mcg {
78    Pcg64Mcg::seed_from_u64(seed)
79}
80
81/// Fixed maximum for the backend-independent initial random-state handoff.
82pub const MAX_INITIAL_STATE_BYTES: usize = 4096;
83
84/// Identity of the sole initial image whose real auxv was already written.
85/// Backends must authenticate this identity before constructing a handoff.
86#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
87#[serde(deny_unknown_fields)]
88pub struct InitialImage {
89    /// Initial physical process ID in the backend's guest PID namespace.
90    pub pid: i32,
91    /// Linux process generation from field22 of this process's proc stat.
92    pub start_time_ticks: u64,
93    /// Actual writable16-byte target from the authenticated initial auxv.
94    pub at_random: usize,
95}
96
97impl InitialImage {
98    fn validate(self) -> Result<(), Errno> {
99        if self.pid <= 0
100            || self.start_time_ticks == 0
101            || self.at_random == 0
102            || self.at_random.checked_add(16).is_none()
103        {
104            return Err(Errno::EPROTO);
105        }
106        Ok(())
107    }
108}
109
110#[derive(Serialize, Deserialize)]
111#[serde(deny_unknown_fields)]
112struct InitialRandomState {
113    version: u32,
114    configuration: [u8; 32],
115    image: InitialImage,
116    state: LoaderState,
117}
118
119/// Authenticated loader result. Continuations carry no random state and must
120/// leave the ordinary newly constructed thread completely unchanged.
121#[derive(Serialize, Deserialize)]
122#[serde(deny_unknown_fields)]
123pub enum LoaderState {
124    /// Initial dynamic image: the actual auxv write and early requests ran.
125    InitialRandom {
126        /// Stream after the real auxv and getrandom operations.
127        prng: Pcg64Mcg,
128    },
129    /// A later real kernel exec observed in this owned process lineage.
130    ObservedExecContinuation,
131    /// The held initial program takes the existing static-loader path.
132    InitialStaticLegacy,
133}
134
135fn configuration_identity(config: &crate::Config) -> Result<[u8; 32], Errno> {
136    struct HashWriter(Sha256);
137    impl std::io::Write for HashWriter {
138        fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
139            self.0.update(bytes);
140            Ok(bytes.len())
141        }
142        fn flush(&mut self) -> std::io::Result<()> {
143            Ok(())
144        }
145    }
146    let mut writer = HashWriter(Sha256::new());
147    writer.0.update(b"hermit-initial-random-state-v1\0");
148    writer.0.update(crate::config_wire_fingerprint());
149    // Include actual effective values, not merely Config's type fingerprint.
150    // Stream into the digest instead of allocating a second config copy.
151    serde_json::to_writer(&mut writer, config).map_err(|_| Errno::EPROTO)?;
152    Ok(writer.0.finalize().into())
153}
154
155/// Encode only the actual PRNG and completed auxv identity. No clock, metadata,
156/// chaos RNG, scheduler state or request history is transferred.
157pub fn encode_initial_state(
158    config: &crate::Config,
159    image: InitialImage,
160    prng: &Pcg64Mcg,
161) -> Result<Vec<u8>, Errno> {
162    image.validate()?;
163    let value = InitialRandomState {
164        version: 1,
165        configuration: configuration_identity(config)?,
166        image,
167        state: LoaderState::InitialRandom { prng: prng.clone() },
168    };
169    encode_state(value)
170}
171
172/// Encode a supervisor-authenticated legacy path without transferring RNG,
173/// clock, metadata or any auxiliary-vector completion fact.
174pub fn encode_continuation(
175    config: &crate::Config,
176    image: InitialImage,
177    state: LoaderState,
178) -> Result<Vec<u8>, Errno> {
179    image.validate()?;
180    if matches!(state, LoaderState::InitialRandom { .. }) {
181        return Err(Errno::EPROTO);
182    }
183    encode_state(InitialRandomState {
184        version: 1,
185        configuration: configuration_identity(config)?,
186        image,
187        state,
188    })
189}
190
191fn encode_state(value: InitialRandomState) -> Result<Vec<u8>, Errno> {
192    let bytes = serde_json::to_vec(&value).map_err(|_| Errno::EPROTO)?;
193    if bytes.is_empty() || bytes.len() > MAX_INITIAL_STATE_BYTES {
194        return Err(Errno::EOVERFLOW);
195    }
196    Ok(bytes)
197}
198
199/// Decode an exact canonical, configuration- and image-bound loader result.
200/// The backend must obtain these bytes only from its authenticated callback.
201pub fn decode_loader_state(
202    bytes: &[u8],
203    config: &crate::Config,
204    expected: InitialImage,
205) -> Result<LoaderState, Errno> {
206    expected.validate()?;
207    if bytes.is_empty() || bytes.len() > MAX_INITIAL_STATE_BYTES {
208        return Err(Errno::EPROTO);
209    }
210    let value: InitialRandomState = serde_json::from_slice(bytes).map_err(|_| Errno::EPROTO)?;
211    if value.version != 1
212        || value.configuration != configuration_identity(config)?
213        || value.image != expected
214        || serde_json::to_vec(&value).map_err(|_| Errno::EPROTO)? != bytes
215    {
216        // Re-encoding also rejects trailing whitespace/bytes and alternate
217        // representations. A rejected handoff is never replaced with a seed.
218        return Err(Errno::EPROTO);
219    }
220    Ok(value.state)
221}
222
223pub(crate) fn decode_initial_state(
224    bytes: &[u8],
225    config: &crate::Config,
226    expected: InitialImage,
227) -> Result<Pcg64Mcg, Errno> {
228    match decode_loader_state(bytes, config, expected)? {
229        LoaderState::InitialRandom { prng } => Ok(prng),
230        LoaderState::ObservedExecContinuation | LoaderState::InitialStaticLegacy => {
231            Err(Errno::EPROTO)
232        }
233    }
234}
235
236const GETRANDOM_ALLOWED_FLAGS: u32 = libc::GRND_NONBLOCK | libc::GRND_RANDOM | libc::GRND_INSECURE;
237
238// AUTONOMOUS-BOT-IMPLEMENTED
239// TODO-HUMAN-REVIEW(#545): Confirm getrandom flag, stream, and fault semantics.
240pub(crate) fn validate_getrandom_flags(flags: usize) -> Result<(), Errno> {
241    let flags = flags as u32;
242    let random = flags & libc::GRND_RANDOM != 0;
243    let insecure = flags & libc::GRND_INSECURE != 0;
244
245    if flags & !GETRANDOM_ALLOWED_FLAGS != 0 || (random && insecure) {
246        Err(Errno::EINVAL)
247    } else {
248        Ok(())
249    }
250}
251
252// Linux's import_ubuf clamps getrandom requests to MAX_RW_COUNT on x86_64.
253pub(crate) const GETRANDOM_MAX_BYTES: usize = (i32::MAX as usize) & !4095;
254
255pub(crate) fn getrandom_request_len(requested: usize) -> usize {
256    requested.min(GETRANDOM_MAX_BYTES)
257}
258
259pub(crate) fn write_random_chunk(
260    memory: &mut impl MemoryAccess,
261    remote_buf: AddrMut<u8>,
262    local_buf: &[u8],
263) -> Result<usize, Errno> {
264    const PTRACE_WORD_SPLIT: usize = std::mem::size_of::<u64>() / 2;
265
266    if local_buf.len() != std::mem::size_of::<u64>() {
267        return memory.write_with_user_access(remote_buf, local_buf);
268    }
269
270    // Preserve the existing eight-byte split and its prefix semantics. Every
271    // length now uses the explicit user-access capability; debugger writes can
272    // bypass protection for other sizes too (including the KVM backend).
273    let first = memory.write_with_user_access(remote_buf, &local_buf[..PTRACE_WORD_SPLIT])?;
274    if first < PTRACE_WORD_SPLIT {
275        return Ok(first);
276    }
277    let Some(second_buf) = remote_buf
278        .as_raw()
279        .checked_add(PTRACE_WORD_SPLIT)
280        .and_then(AddrMut::<u8>::from_raw)
281    else {
282        return Ok(first);
283    };
284    match memory.write_with_user_access(second_buf, &local_buf[PTRACE_WORD_SPLIT..]) {
285        Ok(second) => Ok(first + second),
286        Err(Errno::EFAULT) => Ok(first),
287        Err(error) => Err(error),
288    }
289}
290
291/// Fill guest memory from the same stream/chunk/write algorithm used by the
292/// normal Detcore handler. No syscall/scheduler accounting is performed here.
293pub fn fill_bytes(
294    prng: &mut Pcg64Mcg,
295    mut memory: impl MemoryAccess,
296    remote_buf: AddrMut<u8>,
297    len: usize,
298    dettid: DetTid,
299    source: &str,
300) -> Result<usize, Error> {
301    let mut local_words = [0_u64; RANDOM_FILL_CHUNK_BYTES / std::mem::size_of::<u64>()];
302    let mut hasher = DefaultHasher::new();
303    let mut written = 0;
304
305    while written < len {
306        let remote_chunk = match remote_buf
307            .as_raw()
308            .checked_add(written)
309            .and_then(AddrMut::<u8>::from_raw)
310        {
311            Some(address) => address,
312            None if written == 0 => return Err(Errno::EFAULT.into()),
313            None => break,
314        };
315        let chunk_len = (len - written).min(RANDOM_FILL_CHUNK_BYTES);
316        // Keep the existing aligned scratch and full attempted-chunk draw.
317        let local_buf = unsafe {
318            std::slice::from_raw_parts_mut(local_words.as_mut_ptr().cast::<u8>(), chunk_len)
319        };
320        prng.fill(local_buf);
321        let n = match write_random_chunk(&mut memory, remote_chunk, local_buf) {
322            Ok(n) => n,
323            Err(Errno::EFAULT) if written > 0 => break,
324            Err(error) => return Err(copy_error(error)),
325        };
326        if n == 0 {
327            if written == 0 {
328                return Err(Errno::EFAULT.into());
329            }
330            break;
331        }
332        if cfg!(debug_assertions) {
333            Hash::hash_slice(&local_buf[..n], &mut hasher);
334        }
335        written += n;
336        if n < chunk_len {
337            break;
338        }
339    }
340
341    if cfg!(debug_assertions) {
342        detlog!(
343            "[dtid {}] USER RAND [{}] Filled guest memory with {} random bytes, hash of bytes: {}",
344            dettid,
345            source,
346            written,
347            hasher.finish()
348        );
349    }
350    Ok(written)
351}
352
353/// Apply getrandom's existing flag, length, null-buffer and fill semantics.
354pub fn getrandom(
355    prng: &mut Pcg64Mcg,
356    memory: impl MemoryAccess,
357    dettid: DetTid,
358    call: Getrandom,
359) -> Result<i64, Error> {
360    validate_getrandom_flags(call.flags())?;
361    let len = getrandom_request_len(call.buflen());
362    if len == 0 {
363        return Ok(0);
364    }
365    let buf = call.buf().ok_or(Errno::EFAULT)?;
366    fill_bytes(prng, memory, buf, len, dettid, "getrandom").map(|n| n as i64)
367}
368
369/// Draw and write the actual initial auxv bytes. A write failure preserves the
370/// consumed PRNG state, as in the normal post-exec callback.
371pub fn initialize_auxv(
372    prng: &mut Pcg64Mcg,
373    mut memory: impl MemoryAccess,
374    pointer: AddrMut<u8>,
375    dettid: DetTid,
376) -> Result<(), Errno> {
377    let bytes: [u8; 16] = prng.random();
378    detlog!(
379        "[post_exec, dtid {}] init auxv AT_RANDOM value to {:?}",
380        dettid,
381        bytes
382    );
383    memory.write_value(pointer.cast::<[u8; 16]>(), &bytes)
384}
385
386#[cfg(test)]
387mod tests {
388    include!("random/user_access_tests.rs");
389    use std::io::IoSlice;
390    use std::io::IoSliceMut;
391
392    use reverie::syscalls::Syscall;
393    use reverie::syscalls::SyscallArgs;
394    use reverie::syscalls::Sysno;
395
396    use super::*;
397
398    #[derive(Clone, Copy)]
399    struct OwnMemory;
400
401    impl MemoryAccess for OwnMemory {
402        fn write_with_user_access(
403            &mut self,
404            addr: AddrMut<u8>,
405            bytes: &[u8],
406        ) -> Result<usize, Errno> {
407            addr.as_raw()
408                .checked_add(bytes.len())
409                .ok_or(Errno::EFAULT)?;
410            if bytes.is_empty() {
411                return Ok(0);
412            }
413            let local = libc::iovec {
414                iov_base: bytes.as_ptr().cast_mut().cast(),
415                iov_len: bytes.len(),
416            };
417            let remote = libc::iovec {
418                iov_base: addr.as_raw() as *mut libc::c_void,
419                iov_len: bytes.len(),
420            };
421            let n = unsafe { libc::process_vm_writev(libc::getpid(), &local, 1, &remote, 1, 0) };
422            if n < 0 {
423                Err(Errno::last())
424            } else {
425                Ok(n as usize)
426            }
427        }
428        fn read_vectored(
429            &self,
430            remote: &[IoSlice],
431            local: &mut [IoSliceMut],
432        ) -> Result<usize, Errno> {
433            let n = unsafe {
434                libc::process_vm_readv(
435                    libc::getpid(),
436                    local.as_ptr().cast(),
437                    local.len() as _,
438                    remote.as_ptr().cast(),
439                    remote.len() as _,
440                    0,
441                )
442            };
443            if n < 0 {
444                Err(Errno::last())
445            } else {
446                Ok(n as usize)
447            }
448        }
449        fn write_vectored(
450            &mut self,
451            local: &[IoSlice],
452            remote: &mut [IoSliceMut],
453        ) -> Result<usize, Errno> {
454            let n = unsafe {
455                libc::process_vm_writev(
456                    libc::getpid(),
457                    local.as_ptr().cast(),
458                    local.len() as _,
459                    remote.as_ptr().cast(),
460                    remote.len() as _,
461                    0,
462                )
463            };
464            if n < 0 {
465                Err(Errno::last())
466            } else {
467                Ok(n as usize)
468            }
469        }
470    }
471
472    struct SecondHalfFailure {
473        error: Errno,
474        bytes: [u8; 8],
475        writes: Vec<(usize, Vec<u8>)>,
476    }
477
478    impl MemoryAccess for SecondHalfFailure {
479        fn read_vectored(
480            &self,
481            _remote: &[IoSlice],
482            _local: &mut [IoSliceMut],
483        ) -> Result<usize, Errno> {
484            panic!("random copying must not read guest memory")
485        }
486
487        fn write_vectored(
488            &mut self,
489            _local: &[IoSlice],
490            _remote: &mut [IoSliceMut],
491        ) -> Result<usize, Errno> {
492            panic!("random copying must use the user-access capability")
493        }
494
495        fn write_with_user_access(
496            &mut self,
497            addr: AddrMut<u8>,
498            buf: &[u8],
499        ) -> Result<usize, Errno> {
500            self.writes.push((addr.as_raw(), buf.to_vec()));
501            match self.writes.len() {
502                1 => {
503                    assert_eq!(addr.as_raw(), 0x1000);
504                    assert_eq!(buf.len(), 4);
505                    self.bytes[..4].copy_from_slice(buf);
506                    Ok(4)
507                }
508                2 => {
509                    assert_eq!(addr.as_raw(), 0x1004);
510                    assert_eq!(buf.len(), 4);
511                    Err(self.error)
512                }
513                _ => panic!("random copying retried a failed write"),
514            }
515        }
516    }
517
518    #[test]
519    fn eight_byte_random_copy_distinguishes_faults_from_backend_errors() {
520        for (error, expected) in [(Errno::EFAULT, Ok(4)), (Errno::EIO, Err(Errno::EIO))] {
521            let mut memory = SecondHalfFailure {
522                error,
523                bytes: [0xa5; 8],
524                writes: Vec::new(),
525            };
526            let address = AddrMut::from_raw(0x1000).unwrap();
527
528            assert_eq!(
529                write_random_chunk(&mut memory, address, &[1, 2, 3, 4, 5, 6, 7, 8]),
530                expected
531            );
532            assert_eq!(memory.bytes, [1, 2, 3, 4, 0xa5, 0xa5, 0xa5, 0xa5]);
533            assert_eq!(
534                memory.writes,
535                [(0x1000, vec![1, 2, 3, 4]), (0x1004, vec![5, 6, 7, 8])]
536            );
537        }
538    }
539
540    struct Pages {
541        address: *mut u8,
542        size: usize,
543    }
544    impl Pages {
545        fn new() -> Self {
546            let size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
547            assert!(size >= 4096);
548            let address = unsafe {
549                libc::mmap(
550                    std::ptr::null_mut(),
551                    2 * size,
552                    libc::PROT_READ | libc::PROT_WRITE,
553                    libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
554                    -1,
555                    0,
556                )
557            };
558            assert_ne!(address, libc::MAP_FAILED);
559            unsafe {
560                std::ptr::write_bytes(address.cast::<u8>(), 0xa5, 2 * size);
561            }
562            assert_eq!(
563                unsafe { libc::mprotect(address.add(size), size, libc::PROT_READ) },
564                0
565            );
566            Self {
567                address: address.cast(),
568                size,
569            }
570        }
571        fn address(&self, offset: usize) -> AddrMut<'static, u8> {
572            assert!(offset < self.size * 2);
573            AddrMut::from_raw(self.address as usize + offset).unwrap()
574        }
575        fn bytes(&self, offset: usize, length: usize) -> Vec<u8> {
576            assert!(offset + length <= 2 * self.size);
577            unsafe { std::slice::from_raw_parts(self.address.add(offset), length) }.to_vec()
578        }
579    }
580    impl Drop for Pages {
581        fn drop(&mut self) {
582            assert_eq!(
583                unsafe { libc::munmap(self.address.cast(), 2 * self.size) },
584                0
585            );
586        }
587    }
588
589    fn call(buffer: usize, length: usize, flags: usize) -> Getrandom {
590        let Syscall::Getrandom(call) = Syscall::from_raw(
591            Sysno::getrandom,
592            SyscallArgs::new(buffer, length, flags, 0, 0, 0),
593        ) else {
594            unreachable!()
595        };
596        call
597    }
598    // Existing guest-errno cases must still be guest errnos. A terminal Tool
599    // error here is a failed assertion, never a successful errno projection.
600    fn guest_getrandom(
601        prng: &mut Pcg64Mcg,
602        memory: impl MemoryAccess,
603        tid: DetTid,
604        call: Getrandom,
605    ) -> Result<i64, Errno> {
606        super::getrandom(prng, memory, tid, call).map_err(|error| match error {
607            Error::Errno(errno) => errno,
608            other => panic!("unexpected terminal failure in guest-errno companion: {other:?}"),
609        })
610    }
611    fn same_state(a: &Pcg64Mcg, b: &Pcg64Mcg) {
612        assert_eq!(
613            serde_json::to_vec(a).unwrap(),
614            serde_json::to_vec(b).unwrap()
615        );
616    }
617
618    #[test]
619    fn initial_handoff_preserves_unrelated_state_and_consumes_only_auxv_fact() {
620        let pages = Pages::new();
621        let config = crate::Config::default();
622        let tid = DetTid::from_raw(3);
623        let image = InitialImage {
624            pid: 3,
625            start_time_ticks: 1234,
626            at_random: pages.address(0).as_raw(),
627        };
628        let mut stream = root_prng(config.rng_seed());
629        initialize_auxv(&mut stream, OwnMemory, pages.address(0), tid).unwrap();
630        guest_getrandom(
631            &mut stream,
632            OwnMemory,
633            tid,
634            call(pages.address(32).as_raw(), 8, 1),
635        )
636        .unwrap();
637        let encoded = encode_initial_state(&config, image, &stream).unwrap();
638        same_state(
639            &decode_initial_state(&encoded, &config, image).unwrap(),
640            &stream,
641        );
642        for bad in [
643            Vec::new(),
644            [encoded.as_slice(), b" "].concat(),
645            String::from_utf8(encoded.clone())
646                .unwrap()
647                .replace("\"version\":1", "\"version\":2")
648                .into_bytes(),
649        ] {
650            assert!(decode_initial_state(&bad, &config, image).is_err());
651        }
652        for wrong in [
653            InitialImage { pid: 4, ..image },
654            InitialImage {
655                start_time_ticks: 1235,
656                ..image
657            },
658            InitialImage {
659                at_random: image.at_random + 16,
660                ..image
661            },
662        ] {
663            assert!(decode_initial_state(&encoded, &config, wrong).is_err());
664        }
665        let mut different = config.clone();
666        different.virtualize_time = !different.virtualize_time;
667        assert!(decode_initial_state(&encoded, &different, image).is_err());
668
669        for kind in [
670            LoaderState::ObservedExecContinuation,
671            LoaderState::InitialStaticLegacy,
672        ] {
673            let legacy = encode_continuation(&config, image, kind).unwrap();
674            assert!(matches!(
675                decode_loader_state(&legacy, &config, image).unwrap(),
676                LoaderState::ObservedExecContinuation | LoaderState::InitialStaticLegacy
677            ));
678            // Continuation is not a random state and cannot be applied through
679            // the initial-state API, even to an otherwise eligible normal root.
680            assert!(matches!(
681                decode_initial_state(&legacy, &config, image),
682                Err(Errno::EPROTO)
683            ));
684            let mut untouched = crate::tool_local::ThreadState::new(tid, &config, ());
685            let prng_before = serde_json::to_vec(&untouched.prng).unwrap();
686            let chaos_before = serde_json::to_vec(&untouched.chaos_prng).unwrap();
687            let clock_before = serde_json::to_vec(&untouched.thread_logical_time).unwrap();
688            let metadata_before = std::sync::Arc::clone(&untouched.file_metadata);
689            let memory_before = std::sync::Arc::clone(&untouched.memory_metadata);
690            assert_eq!(
691                untouched.apply_initial_random_state(&legacy, &config, image),
692                Err(Errno::EPROTO)
693            );
694            assert_eq!(serde_json::to_vec(&untouched.prng).unwrap(), prng_before);
695            assert_eq!(
696                serde_json::to_vec(&untouched.chaos_prng).unwrap(),
697                chaos_before
698            );
699            assert_eq!(
700                serde_json::to_vec(&untouched.thread_logical_time).unwrap(),
701                clock_before
702            );
703            assert!(std::sync::Arc::ptr_eq(
704                &untouched.file_metadata,
705                &metadata_before
706            ));
707            assert!(std::sync::Arc::ptr_eq(
708                &untouched.memory_metadata,
709                &memory_before
710            ));
711            assert!(
712                !untouched
713                    .complete_initial_random_auxv(Some(image.at_random))
714                    .unwrap()
715            );
716            assert!(matches!(
717                decode_loader_state(
718                    &legacy,
719                    &config,
720                    InitialImage {
721                        start_time_ticks: 1235,
722                        ..image
723                    }
724                ),
725                Err(Errno::EPROTO)
726            ));
727        }
728
729        let mut state = crate::tool_local::ThreadState::new(tid, &config, ());
730        let chaos = serde_json::to_vec(&state.chaos_prng).unwrap();
731        let clock = serde_json::to_vec(&state.thread_logical_time).unwrap();
732        let metadata = std::sync::Arc::clone(&state.file_metadata);
733        let memory = std::sync::Arc::clone(&state.memory_metadata);
734        let pedigree = state.pedigree.clone();
735        state.committed_clock_value = 47;
736        state
737            .apply_initial_random_state(&encoded, &config, image)
738            .unwrap();
739        same_state(&state.prng, &stream);
740        assert_eq!(serde_json::to_vec(&state.chaos_prng).unwrap(), chaos);
741        assert_eq!(
742            serde_json::to_vec(&state.thread_logical_time).unwrap(),
743            clock
744        );
745        assert!(std::sync::Arc::ptr_eq(&state.file_metadata, &metadata));
746        assert!(std::sync::Arc::ptr_eq(&state.memory_metadata, &memory));
747        assert_eq!(state.pedigree.raw(), pedigree.raw());
748        assert_eq!(state.committed_clock_value, 47);
749        assert!(
750            state
751                .apply_initial_random_state(&encoded, &config, image)
752                .is_err()
753        );
754        assert!(
755            state
756                .complete_initial_random_auxv(Some(image.at_random + 1))
757                .is_err()
758        );
759        // Libc/guest writes after the early acknowledgement must survive the
760        // normal late post-exec completion. Completion performs no memory I/O.
761        OwnMemory
762            .write_exact(pages.address(0), &[0x7c; 16])
763            .unwrap();
764        // handle_post_exec sets this before consuming the completion fact.
765        state.past_global_first_execve = true;
766        assert!(
767            state
768                .complete_initial_random_auxv(Some(image.at_random))
769                .unwrap()
770        );
771        assert_eq!(pages.bytes(0, 16), [0x7c; 16]);
772        same_state(&state.prng, &stream);
773        assert!(
774            !state
775                .complete_initial_random_auxv(Some(image.at_random))
776                .unwrap()
777        );
778        assert!(
779            state
780                .apply_initial_random_state(&encoded, &config, image)
781                .is_err()
782        );
783    }
784
785    #[test]
786    fn shared_random_preserves_auxv_and_fault_semantics() {
787        let pages = Pages::new();
788        let tid = DetTid::from_raw(3);
789        let mut actual = root_prng(0);
790        let mut expected = Pcg64Mcg::seed_from_u64(0);
791        let auxv: [u8; 16] = expected.random();
792        initialize_auxv(&mut actual, OwnMemory, pages.address(0), tid).unwrap();
793        assert_eq!(pages.bytes(0, 16), auxv);
794        same_state(&actual, &expected);
795
796        for (buffer, len, flags, result) in [
797            (0, 0, 0, Ok(0)),
798            (0, 8, 0, Err(Errno::EFAULT)),
799            (
800                pages.address(32).as_raw(),
801                16,
802                0x8000_0001,
803                Err(Errno::EINVAL),
804            ),
805        ] {
806            assert_eq!(
807                guest_getrandom(&mut actual, OwnMemory, tid, call(buffer, len, flags)),
808                result
809            );
810            same_state(&actual, &expected);
811            assert_eq!(pages.bytes(32, 16), [0xa5; 16]);
812        }
813        for len in [8, 16, 32, 4096] {
814            let mut bytes = vec![0; len];
815            expected.fill(&mut bytes[..]);
816            assert_eq!(
817                guest_getrandom(
818                    &mut actual,
819                    OwnMemory,
820                    tid,
821                    call(pages.address(0).as_raw(), len, 1)
822                ),
823                Ok(len as i64)
824            );
825            assert_eq!(pages.bytes(0, len), bytes);
826            same_state(&actual, &expected);
827        }
828        // A fully read-only destination consumes the generated chunk before
829        // its first write fails; it must not use ptrace's protection bypass.
830        let mut discarded = [0u8; 8];
831        expected.fill(&mut discarded[..]);
832        assert_eq!(
833            guest_getrandom(
834                &mut actual,
835                OwnMemory,
836                tid,
837                call(pages.address(pages.size).as_raw(), 8, 0)
838            ),
839            Err(Errno::EFAULT)
840        );
841        assert_eq!(pages.bytes(pages.size, 8), [0xa5; 8]);
842        same_state(&actual, &expected);
843
844        // A cross-page short write returns the writable prefix, but the PRNG
845        // has generated the complete requested chunk, exactly as before.
846        for len in [8, 16] {
847            let mut bytes = vec![0; len];
848            expected.fill(&mut bytes[..]);
849            assert_eq!(
850                guest_getrandom(
851                    &mut actual,
852                    OwnMemory,
853                    tid,
854                    call(pages.address(pages.size - 4).as_raw(), len, 0)
855                ),
856                Ok(4)
857            );
858            assert_eq!(pages.bytes(pages.size - 4, 4), bytes[..4]);
859            assert_eq!(pages.bytes(pages.size, len - 4), vec![0xa5; len - 4]);
860            same_state(&actual, &expected);
861        }
862    }
863}