void_ship/
lib.rs

1use core::ffi::CStr;
2use core::fmt::Formatter;
3
4#[cfg(target_os = "linux")]
5use libc::*;
6
7#[derive(Debug)]
8pub enum Error {
9    IoError(&'static str, c_int),
10    InvalidFormat(&'static str),
11    ParseIntError(core::num::ParseIntError),
12}
13
14impl From<core::num::ParseIntError> for Error {
15    fn from(e: core::num::ParseIntError) -> Self {
16        Error::ParseIntError(e)
17    }
18}
19
20// When `error_in_core` lands this can be made `core::error::Error`
21//  see issue #103765 https://github.com/rust-lang/rust/issues/103765
22impl std::error::Error for Error {
23    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
24        match self {
25            Error::IoError(_, _) => None,
26            Error::InvalidFormat(_) => None,
27            Error::ParseIntError(e) => Some(e),
28        }
29    }
30}
31
32impl core::fmt::Display for Error {
33    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
34        match self {
35            Error::IoError(s, e) => write!(f, "IoError: {} {}", s, e),
36            Error::InvalidFormat(s) => write!(f, "InvalidFormat: {}", s),
37            Error::ParseIntError(e) => write!(f, "ParseIntError: {}", e),
38        }
39    }
40}
41
42// Function to unmap a memory region
43#[cfg(target_os = "linux")]
44unsafe fn unmap_region(address: *mut c_void, size: size_t) -> Result<(), Error> {
45    let errno = munmap(address, size);
46    if errno == 0 {
47        Ok(())
48    } else {
49        Err(Error::IoError("munmap", errno))
50    }
51}
52
53/// A helper to validate that vdso has been blocked, you should *never* call this
54/// unless you're just validating that this crate works in your environment.
55/// NOTE: This *will* cause a SIGSEGV if the vdso is blocked!!
56/// Panics if the vdso is not blocked
57#[cfg(feature = "test-clock")]
58#[cfg(target_os = "linux")]
59pub fn test_clock() -> ! {
60    let mut ts = timespec {
61        tv_sec: 0,
62        tv_nsec: 0,
63    };
64
65    let result = unsafe { clock_gettime(CLOCK_MONOTONIC, &mut ts) };
66
67    if result == 0 {
68        panic!("clock_gettime succeeded when it should have failed");
69    } else {
70        panic!("clock_gettime failed as expected, but code continued for some reason!");
71    }
72}
73
74#[cfg(feature = "test-clock")]
75#[cfg(not(target_os = "linux"))]
76pub fn test_clock() -> ! {
77    panic!("test_clock is only available on linux");
78}
79
80// This is used internally, exclusively, so I don't feel the need to refactor the return type
81// let path = unsafe {
82// // SAFETY: This is a valid, static C string
83// CStr::from_bytes_until_nul(b"/proc/self/maps\x00").unwrap_unchecked()
84// };
85#[cfg(target_os = "linux")]
86fn find_mapping_addresses() -> Result<
87    (
88        Option<(*mut libc::c_void, libc::size_t)>,
89        Option<(*mut libc::c_void, libc::size_t)>,
90    ),
91    Error,
92> {
93    let path = unsafe {
94        // SAFETY: This is a valid, static C string
95        CStr::from_bytes_until_nul(b"/proc/self/maps\x00").unwrap_unchecked()
96    };
97    let fd = unsafe { open(path.as_ptr(), O_RDONLY) };
98    if fd < 0 {
99        return Err(Error::IoError("open", fd));
100    }
101
102    // One page size seems appropriate, especially since even a small /proc/self/maps
103    // is typically > 2048 bytes
104    let mut buffer = [0u8; 4096];
105    // `line` should be at least 80 bytes, in order to hold the full `vdso` or `vvar` lines,
106    // but we can make it larger in case there's every something odd about it
107    let mut line = [0u8; 1024];
108    let mut line_idx = 0;
109    let mut vvar = None;
110    let mut vdso = None;
111
112    loop {
113        let bytes_read =
114            unsafe { read(fd, buffer.as_mut_ptr() as *mut libc::c_void, buffer.len()) };
115        if bytes_read <= 0 {
116            break; // EOF or error
117        }
118
119        for &byte in &buffer[..bytes_read as usize] {
120            if byte == b'\n' {
121                if line.windows(6).any(|window| window == b"[vdso]") {
122                    vdso = Some(parse_addresses(&line[..12], &line[13..25])?);
123                } else if line.windows(6).any(|window| window == b"[vvar]") {
124                    vvar = Some(parse_addresses(&line[..12], &line[13..25])?);
125                }
126                line_idx = 0; // Reset for the next line
127            } else {
128                if line_idx < line.len() {
129                    line[line_idx] = byte;
130                    line_idx += 1;
131                }
132            }
133        }
134    }
135
136    unsafe { close(fd) };
137    Ok((vvar, vdso))
138}
139
140fn parse_addresses(
141    start_addr: &[u8],
142    end_addr: &[u8],
143) -> Result<(*mut libc::c_void, libc::size_t), Error> {
144    let start = parse_hex_address(start_addr)?;
145    let end = parse_hex_address(end_addr)?;
146
147    Ok((start as *mut libc::c_void, end - start))
148}
149
150fn parse_hex_address(addr: &[u8]) -> Result<usize, Error> {
151    let mut num = 0;
152    for &byte in addr {
153        num = num * 16
154            + match byte {
155                b'0'..=b'9' => byte - b'0',
156                b'a'..=b'f' => 10 + byte - b'a',
157                b'A'..=b'F' => 10 + byte - b'A',
158                _ => return Err(Error::InvalidFormat("Invalid hexadecimal number")),
159            } as usize;
160    }
161    Ok(num)
162}
163
164#[cfg(target_os = "linux")]
165fn allocate_guard_page(address: *mut c_void, size: size_t) -> Result<(), Error> {
166    let result = unsafe {
167        mmap(
168            address,
169            size,
170            PROT_NONE,
171            MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
172            -1,
173            0,
174        )
175    };
176
177    if result == libc::MAP_FAILED {
178        Err(Error::IoError("mmap", result as c_int))
179    } else {
180        Ok(())
181    }
182}
183
184/// Unmaps the vdso and vvar mappings.
185#[cfg(target_os = "linux")]
186pub fn remove_timer_mappings() -> Result<(), Error> {
187    let (Some((vdso_address, vdso_size)), Some((vvar_address, vvar_size))) =
188        find_mapping_addresses()?
189    else {
190        return Err(Error::InvalidFormat("Could not find vdso or vvar mappings"));
191    };
192    // Assuming the regions are at least one page in size
193    unsafe {
194        unmap_region(vdso_address, vdso_size)?;
195        unmap_region(vvar_address, vvar_size)?;
196    }
197    Ok(())
198}
199
200/// This function will replace the vdso and vvar mappings with guard pages.
201#[cfg(target_os = "linux")]
202pub fn replace_timer_mappings() -> Result<(), Error> {
203    let (Some((vdso_address, vdso_size)), Some((vvar_address, vvar_size))) =
204        find_mapping_addresses()?
205    else {
206        return Err(Error::InvalidFormat("Could not find vdso or vvar mappings"));
207    };
208    // Assuming the regions are at least one page in size
209    unsafe {
210        unmap_region(vdso_address, vdso_size)?;
211        unmap_region(vvar_address, vvar_size)?;
212    }
213
214    allocate_guard_page(vdso_address, vdso_size)?;
215    allocate_guard_page(vvar_address, vvar_size)?;
216
217    Ok(())
218}
219
220#[cfg(not(target_os = "linux"))]
221pub fn remove_timer_mappings() -> Result<(), Error> {
222    Ok(())
223}
224
225#[cfg(not(target_os = "linux"))]
226pub fn replace_timer_mappings() -> Result<(), Error> {
227    Ok(())
228}