exarch_core/security/
permissions.rs1use crate::SecurityConfig;
4use crate::config::Validated;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct SanitizedMode(u32);
38
39impl SanitizedMode {
40 #[inline]
42 #[must_use]
43 pub fn as_u32(self) -> u32 {
44 self.0
45 }
46}
47
48#[must_use]
97pub fn sanitize_permissions(mode: u32, config: &SecurityConfig<Validated>) -> SanitizedMode {
98 let mut sanitized = mode;
99
100 sanitized &= !0o4000;
102
103 sanitized &= !0o2000;
105
106 if !config.allowed.world_writable {
108 sanitized &= !0o002;
109 }
110
111 SanitizedMode(sanitized)
112}
113
114#[cfg(test)]
115#[allow(clippy::expect_used)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_sanitize_permissions_normal() {
121 let config = SecurityConfig::default().validate().expect("valid config");
122 assert_eq!(sanitize_permissions(0o644, &config).as_u32(), 0o644);
123 }
124
125 #[test]
126 fn test_sanitize_permissions_executable() {
127 let config = SecurityConfig::default().validate().expect("valid config");
128 assert_eq!(sanitize_permissions(0o755, &config).as_u32(), 0o755);
129 }
130
131 #[test]
132 fn test_sanitize_permissions_strip_setuid() {
133 let config = SecurityConfig::default().validate().expect("valid config");
134 assert_eq!(sanitize_permissions(0o4755, &config).as_u32(), 0o755);
135 }
136
137 #[test]
138 fn test_sanitize_permissions_strip_setgid() {
139 let config = SecurityConfig::default().validate().expect("valid config");
140 assert_eq!(sanitize_permissions(0o2755, &config).as_u32(), 0o755);
141 }
142
143 #[test]
144 fn test_sanitize_permissions_strip_both() {
145 let config = SecurityConfig::default().validate().expect("valid config");
146 assert_eq!(sanitize_permissions(0o6755, &config).as_u32(), 0o755);
147 }
148
149 #[test]
150 fn test_sanitize_permissions_strip_world_writable() {
151 let config = SecurityConfig::default().validate().expect("valid config");
152 assert_eq!(sanitize_permissions(0o777, &config).as_u32(), 0o775);
153 }
154
155 #[test]
156 fn test_sanitize_permissions_world_readable_ok() {
157 let config = SecurityConfig::default().validate().expect("valid config");
158 assert_eq!(sanitize_permissions(0o644, &config).as_u32(), 0o644);
159 }
160
161 #[test]
162 fn test_sanitize_permissions_owner_writable_ok() {
163 let config = SecurityConfig::default().validate().expect("valid config");
164 assert_eq!(sanitize_permissions(0o600, &config).as_u32(), 0o600);
165 }
166
167 #[test]
168 fn test_sanitize_permissions_group_writable_ok() {
169 let config = SecurityConfig::default().validate().expect("valid config");
170 assert_eq!(sanitize_permissions(0o664, &config).as_u32(), 0o664);
171 }
172
173 #[test]
174 fn test_sanitize_permissions_edge_case_zero() {
175 let config = SecurityConfig::default().validate().expect("valid config");
176 assert_eq!(sanitize_permissions(0o000, &config).as_u32(), 0o000);
177 }
178
179 #[test]
180 fn test_sticky_bit_preservation() {
181 let config = SecurityConfig::default().validate().expect("valid config");
182 let sanitized = sanitize_permissions(0o1755, &config).as_u32();
183 assert_eq!(sanitized & 0o1000, 0o1000, "sticky bit should be preserved");
184 assert_eq!(sanitized, 0o1755, "full mode should be preserved");
185 }
186
187 #[test]
188 fn test_sticky_bit_with_setuid_stripped() {
189 let config = SecurityConfig::default().validate().expect("valid config");
190 let sanitized = sanitize_permissions(0o7755, &config).as_u32();
191 assert_eq!(sanitized & 0o1000, 0o1000, "sticky bit should remain");
192 assert_eq!(sanitized & 0o4000, 0, "setuid should be stripped");
193 assert_eq!(sanitized & 0o2000, 0, "setgid should be stripped");
194 assert_eq!(sanitized, 0o1755, "result should be sticky + rwxr-xr-x");
195 }
196
197 #[test]
199 fn test_world_writable_allowed_with_config() {
200 let mut config = SecurityConfig::default();
201 config.allowed.world_writable = true;
202 let config = config.validate().expect("valid config");
203 let sanitized = sanitize_permissions(0o777, &config).as_u32();
204 assert_eq!(
205 sanitized & 0o002,
206 0o002,
207 "world-writable bit should be preserved"
208 );
209 assert_eq!(sanitized & 0o4000, 0, "setuid should be stripped");
210 assert_eq!(sanitized & 0o2000, 0, "setgid should be stripped");
211 assert_eq!(sanitized, 0o777, "result should be rwxrwxrwx");
212 }
213
214 #[test]
215 fn test_world_writable_stripped_by_default() {
216 let config = SecurityConfig::default().validate().expect("valid config");
217 assert_eq!(
218 sanitize_permissions(0o777, &config).as_u32(),
219 0o775,
220 "world-writable bit should be stripped by default"
221 );
222 }
223
224 #[test]
225 fn test_world_writable_bit_only_stripped() {
226 let config = SecurityConfig::default().validate().expect("valid config");
227 assert_eq!(
230 sanitize_permissions(0o666, &config).as_u32(),
231 0o664,
232 "only world-writable bit should be stripped, not group-write"
233 );
234 }
235}