pager_lang/protection.rs
1//! Access permissions for a region of memory.
2
3/// The access a page range permits: which of read, write, and execute the CPU will allow.
4///
5/// A [`Region`](crate::Region) is created read/write so code or data can be written into it,
6/// then moved to another protection with [`Region::protect`](crate::Region::protect). The
7/// pattern a JIT follows is *write xor execute* (W^X): fill the region while it is
8/// [`ReadWrite`](Self::ReadWrite), then flip it to [`ReadExecute`](Self::ReadExecute) before
9/// running the code. Memory that is writable and executable at the same time is a standing
10/// invitation to turn a stray write into code execution, so [`ReadWriteExecute`](Self::ReadWriteExecute)
11/// exists for the rare cases that need it but should be avoided where the two-step flip works.
12///
13/// The five cases map directly onto the platform primitives: `PROT_*` bits for `mprotect`
14/// on Unix, `PAGE_*` constants for `VirtualProtect` on Windows.
15///
16/// # Examples
17///
18/// ```
19/// use pager_lang::Protection;
20///
21/// assert!(Protection::ReadWrite.is_writable());
22/// assert!(!Protection::ReadWrite.is_executable());
23///
24/// assert!(Protection::ReadExecute.is_executable());
25/// assert!(!Protection::ReadExecute.is_writable());
26///
27/// // A guard page allows nothing at all.
28/// assert!(!Protection::None.is_readable());
29/// ```
30#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum Protection {
33 /// No access. Any read, write, or execute through the range faults. This is what guard
34 /// pages carry.
35 None,
36 /// Read-only. Suitable for constant data that must not change once written.
37 Read,
38 /// Read and write — the protection a freshly created [`Region`](crate::Region) starts
39 /// with, so machine code or data can be written into it.
40 ReadWrite,
41 /// Read and execute. The target for finished JIT code: readable and runnable, but no
42 /// longer writable, completing the W^X flip.
43 ReadExecute,
44 /// Read, write, and execute simultaneously. Convenient but it defeats W^X; prefer
45 /// writing under [`ReadWrite`](Self::ReadWrite) and flipping to
46 /// [`ReadExecute`](Self::ReadExecute). Some hardened platforms refuse this protection
47 /// outright.
48 ReadWriteExecute,
49}
50
51impl Protection {
52 /// Whether reads are permitted. True for every protection except [`None`](Self::None).
53 ///
54 /// # Examples
55 ///
56 /// ```
57 /// use pager_lang::Protection;
58 ///
59 /// assert!(Protection::Read.is_readable());
60 /// assert!(Protection::ReadExecute.is_readable());
61 /// assert!(!Protection::None.is_readable());
62 /// ```
63 #[must_use]
64 pub const fn is_readable(self) -> bool {
65 !matches!(self, Protection::None)
66 }
67
68 /// Whether writes are permitted. True for [`ReadWrite`](Self::ReadWrite) and
69 /// [`ReadWriteExecute`](Self::ReadWriteExecute).
70 ///
71 /// [`Region::write`](crate::Region::write) and
72 /// [`Region::as_mut_slice`](crate::Region::as_mut_slice) succeed exactly when the
73 /// region's protection reports this true.
74 ///
75 /// # Examples
76 ///
77 /// ```
78 /// use pager_lang::Protection;
79 ///
80 /// assert!(Protection::ReadWrite.is_writable());
81 /// assert!(!Protection::ReadExecute.is_writable());
82 /// ```
83 #[must_use]
84 pub const fn is_writable(self) -> bool {
85 matches!(self, Protection::ReadWrite | Protection::ReadWriteExecute)
86 }
87
88 /// Whether execution is permitted. True for [`ReadExecute`](Self::ReadExecute) and
89 /// [`ReadWriteExecute`](Self::ReadWriteExecute).
90 ///
91 /// # Examples
92 ///
93 /// ```
94 /// use pager_lang::Protection;
95 ///
96 /// assert!(Protection::ReadExecute.is_executable());
97 /// assert!(!Protection::ReadWrite.is_executable());
98 /// ```
99 #[must_use]
100 pub const fn is_executable(self) -> bool {
101 matches!(self, Protection::ReadExecute | Protection::ReadWriteExecute)
102 }
103}
104
105#[cfg(test)]
106#[allow(
107 clippy::unwrap_used,
108 clippy::expect_used,
109 clippy::panic,
110 reason = "tests assert on specific outcomes; a wrong outcome should fail the test loudly"
111)]
112mod tests {
113 use super::Protection;
114
115 #[test]
116 fn test_is_readable_true_for_all_but_none() {
117 assert!(!Protection::None.is_readable());
118 for prot in [
119 Protection::Read,
120 Protection::ReadWrite,
121 Protection::ReadExecute,
122 Protection::ReadWriteExecute,
123 ] {
124 assert!(prot.is_readable(), "{prot:?} should be readable");
125 }
126 }
127
128 #[test]
129 fn test_is_writable_only_for_write_protections() {
130 assert!(Protection::ReadWrite.is_writable());
131 assert!(Protection::ReadWriteExecute.is_writable());
132 assert!(!Protection::None.is_writable());
133 assert!(!Protection::Read.is_writable());
134 assert!(!Protection::ReadExecute.is_writable());
135 }
136
137 #[test]
138 fn test_is_executable_only_for_execute_protections() {
139 assert!(Protection::ReadExecute.is_executable());
140 assert!(Protection::ReadWriteExecute.is_executable());
141 assert!(!Protection::None.is_executable());
142 assert!(!Protection::Read.is_executable());
143 assert!(!Protection::ReadWrite.is_executable());
144 }
145}