libxml_rs/xslt/security/mod.rs
1//! XSLT security preferences (§33, §85 Phase 8).
2//!
3//! Implements the xsltSecurityPrefs API for controlling what operations
4//! are permitted during XSLT transformations.
5//!
6//! # Upstream mapping
7//!
8//! | Function | Header |
9//! |---|---|
10//! | `xsltNewSecurityPrefs` | xslt.h |
11//! | `xsltFreeSecurityPrefs` | xslt.h |
12//! | `xsltSetSecurityPrefs` | xslt.h |
13//! | `xsltGetSecurityPrefs` | xslt.h |
14//! | `xsltSetDefaultSecurityPrefs` | xslt.h |
15//! | `xsltGetDefaultSecurityPrefs` | xslt.h |
16
17#![allow(
18 clippy::missing_inline_in_public_items,
19 clippy::must_use_candidate,
20 clippy::missing_safety_doc
21)]
22
23use crate::abi::structs::*;
24use crate::abi::types::*;
25use std::ffi::c_void;
26use std::os::raw::c_int;
27use std::ptr;
28use std::sync::Mutex;
29
30/// Security option: read a file from the filesystem.
31pub const XSLT_SECPREF_READ_FILE: c_int = 1;
32
33/// Security option: write a file to the filesystem.
34pub const XSLT_SECPREF_WRITE_FILE: c_int = 2;
35
36/// Security option: create a directory on the filesystem.
37pub const XSLT_SECPREF_CREATE_DIRECTORY: c_int = 3;
38
39/// Security option: read a network resource.
40pub const XSLT_SECPREF_READ_NETWORK: c_int = 4;
41
42/// Security option: write to a network resource.
43pub const XSLT_SECPREF_WRITE_NETWORK: c_int = 5;
44
45/// Default security preference value (alias for [`XSLT_SECPREF_DENY`]).
46pub const XSLT_SECPREF_DEFAULT: c_int = 0;
47
48/// Security preference value: deny the operation.
49pub const XSLT_SECPREF_DENY: c_int = 0;
50
51/// Security preference value: allow the operation.
52pub const XSLT_SECPREF_ALLOW: c_int = 1;
53
54/// Internal security preferences structure.
55///
56/// Stores the current allow/deny setting for each of the five
57/// controllable security options.
58#[repr(C)]
59pub struct XsltSecurityPrefs {
60 /// Allow reading files from the filesystem.
61 pub readFile: c_int,
62 /// Allow writing files to the filesystem.
63 pub writeFile: c_int,
64 /// Allow creating directories on the filesystem.
65 pub createDirectory: c_int,
66 /// Allow reading network resources.
67 pub readNetwork: c_int,
68 /// Allow writing to network resources.
69 pub writeNetwork: c_int,
70}
71
72/// Wrapper around `*mut c_void` that implements `Send` so it can be stored
73/// in a `Mutex`.
74///
75/// # Safety
76///
77/// The caller is responsible for ensuring that the pointed-to value is
78/// accessed in a thread-safe manner. The global default is only ever written
79/// or read through the `Mutex` guard, so concurrent access is serialized.
80#[repr(transparent)]
81struct SecurityPrefsPtr(*mut c_void);
82
83// SAFETY: Access to the wrapped pointer is serialized via Mutex, making
84// it safe to send between threads.
85unsafe impl Send for SecurityPrefsPtr {}
86
87/// Global default security preferences, stored as a raw pointer behind a
88/// [`Mutex`] for thread-safe access.
89static DEFAULT_SECURITY_PREFS: Mutex<Option<SecurityPrefsPtr>> = Mutex::new(None);
90
91/// Create new security preferences with default (allow) settings.
92///
93/// Returns a raw pointer to a heap-allocated [`XsltSecurityPrefs`] with all
94/// options set to [`XSLT_SECPREF_ALLOW`]. The caller is responsible for
95/// freeing the returned pointer via [`xsltFreeSecurityPrefs`].
96///
97/// # Returns
98///
99/// A non-null pointer to the newly allocated security preferences on success.
100///
101/// # Safety
102///
103/// The caller must ensure the returned pointer is eventually freed with
104/// [`xsltFreeSecurityPrefs`] to avoid memory leaks.
105#[no_mangle]
106pub unsafe extern "C" fn xsltNewSecurityPrefs() -> *mut c_void {
107 let prefs = Box::new(XsltSecurityPrefs {
108 readFile: XSLT_SECPREF_ALLOW,
109 writeFile: XSLT_SECPREF_ALLOW,
110 createDirectory: XSLT_SECPREF_ALLOW,
111 readNetwork: XSLT_SECPREF_ALLOW,
112 writeNetwork: XSLT_SECPREF_ALLOW,
113 });
114 Box::into_raw(prefs) as *mut c_void
115}
116
117/// Free security preferences previously allocated by [`xsltNewSecurityPrefs`].
118///
119/// # Safety
120///
121/// - `sec` must be a pointer returned by [`xsltNewSecurityPrefs`] that has
122/// not yet been freed.
123/// - After this call, `sec` is dangling and must not be dereferenced.
124#[no_mangle]
125pub unsafe extern "C" fn xsltFreeSecurityPrefs(sec: *mut c_void) {
126 if !sec.is_null() {
127 let _ = Box::from_raw(sec as *mut XsltSecurityPrefs);
128 }
129}
130
131/// Set a security preference for the given options structure.
132///
133/// # Arguments
134///
135/// * `sec` - Pointer to security preferences (must be non-null).
136/// * `option` - One of `XSLT_SECPREF_READ_FILE`, `XSLT_SECPREF_WRITE_FILE`,
137/// `XSLT_SECPREF_CREATE_DIRECTORY`, `XSLT_SECPREF_READ_NETWORK`, or
138/// `XSLT_SECPREF_WRITE_NETWORK`.
139/// * `value` - The value to set (typically [`XSLT_SECPREF_ALLOW`] or
140/// [`XSLT_SECPREF_DENY`]).
141///
142/// # Returns
143///
144/// `0` on success, or `-1` if `sec` is null or `option` is invalid.
145///
146/// # Safety
147///
148/// `sec` must point to a valid [`XsltSecurityPrefs`] structure obtained from
149/// [`xsltNewSecurityPrefs`] that has not yet been freed.
150#[no_mangle]
151pub unsafe extern "C" fn xsltSetSecurityPrefs(
152 sec: *mut c_void,
153 option: c_int,
154 value: c_int,
155) -> c_int {
156 if sec.is_null() {
157 return -1;
158 }
159 let prefs = &mut *(sec as *mut XsltSecurityPrefs);
160 match option {
161 XSLT_SECPREF_READ_FILE => prefs.readFile = value,
162 XSLT_SECPREF_WRITE_FILE => prefs.writeFile = value,
163 XSLT_SECPREF_CREATE_DIRECTORY => prefs.createDirectory = value,
164 XSLT_SECPREF_READ_NETWORK => prefs.readNetwork = value,
165 XSLT_SECPREF_WRITE_NETWORK => prefs.writeNetwork = value,
166 _ => return -1,
167 }
168 0
169}
170
171/// Get the current value of a security preference.
172///
173/// # Arguments
174///
175/// * `sec` - Pointer to security preferences. If null, returns
176/// [`XSLT_SECPREF_DENY`] for all options.
177/// * `option` - One of `XSLT_SECPREF_READ_FILE`, `XSLT_SECPREF_WRITE_FILE`,
178/// `XSLT_SECPREF_CREATE_DIRECTORY`, `XSLT_SECPREF_READ_NETWORK`, or
179/// `XSLT_SECPREF_WRITE_NETWORK`.
180///
181/// # Returns
182///
183/// The current preference value, or [`XSLT_SECPREF_DENY`] if `sec` is null
184/// or `option` is invalid.
185///
186/// # Safety
187///
188/// If `sec` is non-null, it must point to a valid [`XsltSecurityPrefs`]
189/// structure obtained from [`xsltNewSecurityPrefs`] that has not yet been
190/// freed.
191#[no_mangle]
192pub unsafe extern "C" fn xsltGetSecurityPrefs(sec: *mut c_void, option: c_int) -> c_int {
193 if sec.is_null() {
194 return XSLT_SECPREF_DENY;
195 }
196 let prefs = &*(sec as *mut XsltSecurityPrefs);
197 match option {
198 XSLT_SECPREF_READ_FILE => prefs.readFile,
199 XSLT_SECPREF_WRITE_FILE => prefs.writeFile,
200 XSLT_SECPREF_CREATE_DIRECTORY => prefs.createDirectory,
201 XSLT_SECPREF_READ_NETWORK => prefs.readNetwork,
202 XSLT_SECPREF_WRITE_NETWORK => prefs.writeNetwork,
203 _ => XSLT_SECPREF_DENY,
204 }
205}
206
207/// Set the default security preferences used by new transformations.
208///
209/// The provided pointer is stored as the global default. It is the caller's
210/// responsibility to manage the lifetime of the pointed-to preferences.
211///
212/// # Safety
213///
214/// `sec` must point to a valid [`XsltSecurityPrefs`] structure that remains
215/// valid for the duration it is set as the default (i.e., until replaced by
216/// another call to this function).
217#[no_mangle]
218pub unsafe extern "C" fn xsltSetDefaultSecurityPrefs(sec: *mut c_void) {
219 let mut guard = DEFAULT_SECURITY_PREFS.lock().unwrap();
220 *guard = Some(SecurityPrefsPtr(sec));
221}
222
223/// Get the current default security preferences.
224///
225/// # Returns
226///
227/// A pointer to the default security preferences previously set with
228/// [`xsltSetDefaultSecurityPrefs`], or a null pointer if none have been set.
229///
230/// # Safety
231///
232/// The returned pointer is only valid as long as no other call to
233/// [`xsltSetDefaultSecurityPrefs`] has replaced it and the original
234/// [`XsltSecurityPrefs`] has not been freed.
235#[no_mangle]
236pub unsafe extern "C" fn xsltGetDefaultSecurityPrefs() -> *mut c_void {
237 let guard = DEFAULT_SECURITY_PREFS.lock().unwrap();
238 guard.as_ref().map_or(ptr::null_mut(), |p| p.0)
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 /// Verify that creating and freeing security prefs works.
246 #[test]
247 fn test_new_free_security_prefs() {
248 unsafe {
249 let prefs = xsltNewSecurityPrefs();
250 assert!(!prefs.is_null());
251 xsltFreeSecurityPrefs(prefs);
252 }
253 }
254
255 /// Verify that freeing a null pointer is a no-op.
256 #[test]
257 fn test_free_null() {
258 unsafe {
259 // Should not panic or crash.
260 xsltFreeSecurityPrefs(ptr::null_mut());
261 }
262 }
263
264 /// Verify that newly created prefs have all options set to ALLOW.
265 #[test]
266 fn test_new_prefs_defaults_allow() {
267 unsafe {
268 let prefs = xsltNewSecurityPrefs();
269 assert_eq!(
270 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
271 XSLT_SECPREF_ALLOW
272 );
273 assert_eq!(
274 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
275 XSLT_SECPREF_ALLOW
276 );
277 assert_eq!(
278 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY),
279 XSLT_SECPREF_ALLOW
280 );
281 assert_eq!(
282 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK),
283 XSLT_SECPREF_ALLOW
284 );
285 assert_eq!(
286 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK),
287 XSLT_SECPREF_ALLOW
288 );
289 xsltFreeSecurityPrefs(prefs);
290 }
291 }
292
293 /// Verify that setting and getting each option round-trips correctly.
294 #[test]
295 fn test_set_and_get() {
296 unsafe {
297 let prefs = xsltNewSecurityPrefs();
298
299 // Set all to DENY
300 assert_eq!(
301 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE, XSLT_SECPREF_DENY),
302 0
303 );
304 assert_eq!(
305 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE, XSLT_SECPREF_DENY),
306 0
307 );
308 assert_eq!(
309 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY, XSLT_SECPREF_DENY),
310 0
311 );
312 assert_eq!(
313 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK, XSLT_SECPREF_DENY),
314 0
315 );
316 assert_eq!(
317 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK, XSLT_SECPREF_DENY),
318 0
319 );
320
321 // Verify all are DENY
322 assert_eq!(
323 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
324 XSLT_SECPREF_DENY
325 );
326 assert_eq!(
327 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
328 XSLT_SECPREF_DENY
329 );
330 assert_eq!(
331 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY),
332 XSLT_SECPREF_DENY
333 );
334 assert_eq!(
335 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK),
336 XSLT_SECPREF_DENY
337 );
338 assert_eq!(
339 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK),
340 XSLT_SECPREF_DENY
341 );
342
343 // Set each individually back to ALLOW
344 assert_eq!(
345 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE, XSLT_SECPREF_ALLOW),
346 0
347 );
348 assert_eq!(
349 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
350 XSLT_SECPREF_ALLOW
351 );
352 assert_eq!(
353 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
354 XSLT_SECPREF_DENY
355 );
356
357 assert_eq!(
358 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE, XSLT_SECPREF_ALLOW),
359 0
360 );
361 assert_eq!(
362 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
363 XSLT_SECPREF_ALLOW
364 );
365
366 assert_eq!(
367 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY, XSLT_SECPREF_ALLOW),
368 0
369 );
370 assert_eq!(
371 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY),
372 XSLT_SECPREF_ALLOW
373 );
374
375 assert_eq!(
376 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK, XSLT_SECPREF_ALLOW),
377 0
378 );
379 assert_eq!(
380 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK),
381 XSLT_SECPREF_ALLOW
382 );
383
384 assert_eq!(
385 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK, XSLT_SECPREF_ALLOW),
386 0
387 );
388 assert_eq!(
389 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK),
390 XSLT_SECPREF_ALLOW
391 );
392
393 xsltFreeSecurityPrefs(prefs);
394 }
395 }
396
397 /// Verify that setting/getting with null pointer returns error/default.
398 #[test]
399 fn test_null_pointer() {
400 unsafe {
401 assert_eq!(
402 xsltSetSecurityPrefs(ptr::null_mut(), XSLT_SECPREF_READ_FILE, XSLT_SECPREF_ALLOW),
403 -1
404 );
405 assert_eq!(
406 xsltGetSecurityPrefs(ptr::null_mut(), XSLT_SECPREF_READ_FILE),
407 XSLT_SECPREF_DENY
408 );
409 }
410 }
411
412 /// Verify that an invalid option returns error/default.
413 #[test]
414 fn test_invalid_option() {
415 unsafe {
416 let prefs = xsltNewSecurityPrefs();
417 assert_eq!(xsltSetSecurityPrefs(prefs, 99, XSLT_SECPREF_ALLOW), -1);
418 assert_eq!(xsltGetSecurityPrefs(prefs, 99), XSLT_SECPREF_DENY);
419 xsltFreeSecurityPrefs(prefs);
420 }
421 }
422
423 /// Verify that default security prefs set/get round-trip correctly.
424 #[test]
425 fn test_default_security_prefs() {
426 unsafe {
427 // Initially null
428 assert!(xsltGetDefaultSecurityPrefs().is_null());
429
430 // Create prefs and set as default
431 let prefs = xsltNewSecurityPrefs();
432 xsltSetDefaultSecurityPrefs(prefs);
433
434 // Retrieve and verify
435 let retrieved = xsltGetDefaultSecurityPrefs();
436 assert_eq!(retrieved, prefs);
437
438 // Verify we can read from the default
439 assert_eq!(
440 xsltGetSecurityPrefs(retrieved, XSLT_SECPREF_READ_FILE),
441 XSLT_SECPREF_ALLOW
442 );
443
444 // Clear the default by setting null
445 xsltSetDefaultSecurityPrefs(ptr::null_mut());
446 assert!(xsltGetDefaultSecurityPrefs().is_null());
447
448 // Free the original prefs
449 xsltFreeSecurityPrefs(prefs);
450 }
451 }
452}