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. 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)] 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 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] 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 } 114