Skip to main content

detcore/syscalls/
socket_timestamp_ioctl.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Deterministic socket receive timestamps returned through `ioctl(2)`.
10
11use reverie::Error;
12use reverie::Guest;
13use reverie::syscalls;
14use reverie::syscalls::AddrMut;
15use reverie::syscalls::Errno;
16use reverie::syscalls::MemoryAccess;
17use reverie::syscalls::ioctl::Request;
18
19use crate::fd::FdType;
20use crate::record_or_replay::RecordOrReplay;
21use crate::tool_local::Detcore;
22use crate::types::LogicalTime;
23
24const SIOCGSTAMPNS_OLD: usize = 0x8907;
25const SIOCGSTAMP_NEW: usize = 0x8010_8906;
26const SIOCGSTAMPNS_NEW: usize = 0x8010_8907;
27
28#[derive(Clone, Copy)]
29enum TimestampOutput {
30    Timeval(usize),
31    Timespec(usize),
32}
33
34fn timestamp_output(call: syscalls::Ioctl) -> Option<TimestampOutput> {
35    match call.request() {
36        Request::SIOCGSTAMP(address) => Some(TimestampOutput::Timeval(
37            address.map_or(0, |address| address.as_raw()),
38        )),
39        Request::Other(SIOCGSTAMP_NEW, address) => Some(TimestampOutput::Timeval(address)),
40        Request::Other(SIOCGSTAMPNS_OLD | SIOCGSTAMPNS_NEW, address) => {
41            Some(TimestampOutput::Timespec(address))
42        }
43        _ => None,
44    }
45}
46
47/// Returns whether `call` reads a socket receive timestamp.
48pub(crate) fn is_socket_timestamp_ioctl(call: syscalls::Ioctl) -> bool {
49    matches!(
50        call.request(),
51        Request::SIOCGSTAMP(_)
52            | Request::Other(SIOCGSTAMP_NEW | SIOCGSTAMPNS_OLD | SIOCGSTAMPNS_NEW, _,)
53    )
54}
55
56fn logical_timeval(now: LogicalTime) -> libc::timeval {
57    libc::timeval {
58        tv_sec: now.as_secs() as libc::time_t,
59        tv_usec: (now.subsec_nanos() / 1_000) as libc::suseconds_t,
60    }
61}
62
63fn logical_timespec(now: LogicalTime) -> libc::timespec {
64    libc::timespec {
65        tv_sec: now.as_secs() as libc::time_t,
66        tv_nsec: now.subsec_nanos() as libc::c_long,
67    }
68}
69
70impl<T: RecordOrReplay> Detcore<T> {
71    // AUTONOMOUS-BOT-IMPLEMENTED
72    // TODO-HUMAN-REVIEW(PR-912)
73    /// Return the logical timestamp stored by the last successful socket receive.
74    pub async fn handle_socket_timestamp_ioctl<G: Guest<Self>>(
75        &self,
76        guest: &mut G,
77        call: syscalls::Ioctl,
78    ) -> Result<i64, Error> {
79        let output = timestamp_output(call).ok_or(Errno::EFAULT)?;
80        // Legacy socket timestamp requests predate _IOC direction/size bits, so
81        // Reverie's generic recorder cannot identify their output. On x86_64,
82        // the time64 request layouts are ABI-identical and encode the 16-byte
83        // output, allowing the existing record/replay path to preserve the
84        // kernel's fd, socket-state, and pointer validation.
85        let recorded_call = match output {
86            TimestampOutput::Timeval(address) => {
87                call.with_request(Request::Other(SIOCGSTAMP_NEW, address))
88            }
89            TimestampOutput::Timespec(address) => {
90                call.with_request(Request::Other(SIOCGSTAMPNS_NEW, address))
91            }
92        };
93        if !self.cfg.virtualize_time {
94            return Ok(self.record_or_replay(guest, recorded_call).await?);
95        }
96
97        let now = guest.thread_state().with_detfd(call.fd(), |detfd| {
98            if detfd.ty() != FdType::Socket {
99                return Err(Errno::ENOTTY);
100            }
101            detfd.socket_receive_timestamp().ok_or(Errno::ENOENT)
102        })??;
103        match output {
104            TimestampOutput::Timeval(address) => {
105                let address = AddrMut::from_raw(address).ok_or(Errno::EFAULT)?;
106                guest.memory().write_value(address, &logical_timeval(now))?;
107            }
108            TimestampOutput::Timespec(address) => {
109                let address = AddrMut::from_raw(address).ok_or(Errno::EFAULT)?;
110                guest
111                    .memory()
112                    .write_value(address, &logical_timespec(now))?;
113            }
114        }
115        Ok(0)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn logical_socket_timestamps_use_linux_units() {
125        let now = LogicalTime::from_nanos(2_345_678_901);
126        let timeval = logical_timeval(now);
127        assert_eq!(timeval.tv_sec, 2);
128        assert_eq!(timeval.tv_usec, 345_678);
129        let timespec = logical_timespec(now);
130        assert_eq!(timespec.tv_sec, 2);
131        assert_eq!(timespec.tv_nsec, 345_678_901);
132    }
133}