Skip to main content

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}