Skip to main content

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}