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/// 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.
28pub 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#[allow(dead_code)]
45pub 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
58pub 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)]
75mod tests {
76 use super::*;
77
78 #[ignore = "cannot be run when keys() has already allocated all keys"]
79 #[test]
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]
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]
105 fn check_invalid_alloc_flags() {
106 let _ = pkey_alloc(42, 0);
107 }
108
109 #[test]
110 fn check_invalid_alloc_rights() {
111 assert!(pkey_alloc(0, 42).is_err());
112 }
113}