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