Skip to main content

wasmtime/runtime/vm/mpk/
sys.rs

1//! Expose the `pkey_*` Linux system calls. See the kernel documentation for
2//! more information:
3//! - [`pkeys`] overview
4//! - [`pkey_alloc`] (with `pkey_free`)
5//! - [`pkey_mprotect`]
6//! - `pkey_set` is implemented directly in assembly.
7//!
8//! [`pkey_alloc`]: https://man7.org/linux/man-pages/man2/pkey_alloc.2.html
9//! [`pkey_mprotect`]: https://man7.org/linux/man-pages/man2/pkey_mprotect.2.html
10//! [`pkeys`]: https://man7.org/linux/man-pages/man7/pkeys.7.html
11
12use crate::prelude::*;
13use crate::runtime::vm::host_page_size;
14use std::io::Error;
15
16/// Protection mask disallowing reads and writes of pkey-protected memory (see
17/// `prot` in [`pkey_mprotect`]); in Wasmtime we expect all MPK-protected memory
18/// to start as `PROT_NONE`.
19pub const PROT_NONE: u32 = libc::PROT_NONE as u32; // == 0b0000;
20
21/// Protection mask allowing reads of pkey-protected memory (see `prot` in
22/// [`pkey_mprotect`]).
23pub const PROT_READ: u32 = libc::PROT_READ as u32; // == 0b0001;
24
25/// Protection mask allowing writes of pkey-protected memory (see `prot` in
26/// [`pkey_mprotect`]).
27pub const PROT_WRITE: u32 = libc::PROT_WRITE as u32; // == 0b0010;
28
29/// Allocate a new protection key in the Linux kernel ([docs]); returns the
30/// key ID.
31///
32/// [docs]: https://man7.org/linux/man-pages/man2/pkey_alloc.2.html
33///
34/// Each process has its own separate pkey index; e.g., if process `m`
35/// allocates key 1, process `n` can as well.
36pub fn pkey_alloc(flags: u32, access_rights: u32) -> Result<u32> {
37    assert_eq!(flags, 0); // reserved for future use--must be 0.
38    let result = unsafe { libc::syscall(libc::SYS_pkey_alloc, flags, access_rights) };
39    if result >= 0 {
40        Ok(result
41            .try_into()
42            .expect("only pkey IDs between 0 and 15 are expected"))
43    } else {
44        debug_assert_eq!(result, -1); // only this error result is expected.
45        Err(Error::last_os_error().into())
46    }
47}
48
49/// Free a kernel protection key ([docs]).
50///
51/// [docs]: https://man7.org/linux/man-pages/man2/pkey_alloc.2.html
52#[cfg(test)]
53pub fn pkey_free(key: u32) -> Result<()> {
54    let result = unsafe { libc::syscall(libc::SYS_pkey_free, key) };
55    if result == 0 {
56        Ok(())
57    } else {
58        debug_assert_eq!(result, -1); // only this error result is expected.
59        Err(Error::last_os_error().into())
60    }
61}
62
63/// Change the access protections for a page-aligned memory region ([docs]).
64///
65/// [docs]: https://man7.org/linux/man-pages/man2/pkey_mprotect.2.html
66pub fn pkey_mprotect(addr: usize, len: usize, prot: u32, key: u32) -> Result<()> {
67    let page_size = host_page_size();
68    if addr % page_size != 0 {
69        log::warn!(
70            "memory must be page-aligned for MPK (addr = {addr:#x}, page size = {page_size}"
71        );
72    }
73    let result = unsafe { libc::syscall(libc::SYS_pkey_mprotect, addr, len, prot, key) };
74    if result == 0 {
75        Ok(())
76    } else {
77        debug_assert_eq!(result, -1); // only this error result is expected.
78        Err(Error::last_os_error().into())
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[ignore = "cannot be run when keys() has already allocated all keys"]
87    #[test]
88    fn check_allocate_and_free() {
89        let key = pkey_alloc(0, 0).unwrap();
90        assert_eq!(key, 1);
91        // It may seem strange to assert the key ID here, but we already
92        // make some assumptions:
93        //  1. we are running on Linux with `pku` enabled
94        //  2. Linux will allocate key 0 for itself
95        //  3. we are running this test in non-MPK mode and no one else is
96        //     using pkeys
97        // If these assumptions are incorrect, this test can be removed.
98        pkey_free(key).unwrap()
99    }
100
101    #[test]
102    fn check_invalid_free() {
103        let result = pkey_free(42);
104        assert!(result.is_err());
105        assert_eq!(
106            result.unwrap_err().to_string(),
107            "Invalid argument (os error 22)"
108        );
109    }
110
111    #[test]
112    #[should_panic]
113    fn check_invalid_alloc_flags() {
114        let _ = pkey_alloc(42, 0);
115    }
116
117    #[test]
118    fn check_invalid_alloc_rights() {
119        assert!(pkey_alloc(0, 42).is_err());
120    }
121}