libxml_rs/xslt/security/mod.rs
1//! XSLT security preferences (§33, §85 Phase 8; R-000125 closure).
2//!
3//! Implements the `xsltSecurityPrefs` API for controlling what operations are
4//! permitted during XSLT transformations.
5//!
6//! # Upstream mapping
7//!
8//! | Function | Header |
9//! |---|---|
10//! | `xsltNewSecurityPrefs` | libxslt/security.h |
11//! | `xsltFreeSecurityPrefs` | libxslt/security.h |
12//! | `xsltSetSecurityPrefs` | libxslt/security.h |
13//! | `xsltGetSecurityPrefs` | libxslt/security.h |
14//! | `xsltSetDefaultSecurityPrefs` | libxslt/security.h |
15//! | `xsltGetDefaultSecurityPrefs` | libxslt/security.h |
16//!
17//! # UPSTREAM-PARITY (R-000125)
18//!
19//! The security model is **callback-based**, not value-based. Upstream
20//! `xsltSecurityPrefs` holds five `xsltSecurityCheck` function pointers
21//! (readFile / createFile / createDir / readNet / writeNet) and:
22//!
23//! - `xsltNewSecurityPrefs()` returns a **zeroed** block (all callbacks NULL);
24//! - `xsltSetSecurityPrefs(sec, option, func)` stores the callback, with the
25//! upstream quirk that `XSLT_SECPREF_WRITE_FILE` writes the **createFile**
26//! slot (`security.c` `xsltSetSecurityPrefs` case);
27//! - `xsltGetSecurityPrefs(sec, option)` returns the stored callback or NULL;
28//! - the callbacks are never invoked by libxslt 1.1.42 itself — the surface is
29//! registration-only, exercised by consumers such as `xsltproc --nowrite`
30//! which registers `xsltSecurityForbid`.
31//!
32//! Earlier revisions of this module implemented a divergent int allow/deny
33//! model (`xsltSetSecurityPrefs(sec, option, value: c_int)`); the module was
34//! reimplemented to the upstream contract (R-000125, closed 11.1-G/H).
35
36#![allow(
37 clippy::missing_inline_in_public_items,
38 clippy::must_use_candidate,
39 clippy::missing_safety_doc
40)]
41
42use crate::abi::structs::*;
43use crate::abi::types::*;
44use std::ffi::{c_char, c_void};
45use std::os::raw::c_int;
46use std::ptr;
47use std::sync::Mutex;
48
49/// Security option: read a file from the filesystem.
50pub const XSLT_SECPREF_READ_FILE: c_int = 1;
51
52/// Security option: write a file to the filesystem.
53pub const XSLT_SECPREF_WRITE_FILE: c_int = 2;
54
55/// Security option: create a directory on the filesystem.
56pub const XSLT_SECPREF_CREATE_DIRECTORY: c_int = 3;
57
58/// Security option: read a network resource.
59pub const XSLT_SECPREF_READ_NETWORK: c_int = 4;
60
61/// Security option: write to a network resource.
62pub const XSLT_SECPREF_WRITE_NETWORK: c_int = 5;
63
64/// Security check callback (upstream `xsltSecurityCheck`):
65/// `int (*)(xsltSecurityPrefsPtr sec, xsltTransformContextPtr ctxt,
66/// const char *value)` — returns non-zero to allow, 0 to deny.
67pub type xsltSecurityCheck = unsafe extern "C" fn(*mut c_void, *mut c_void, *const c_char) -> c_int;
68
69/// Internal security preferences structure — five check callbacks, one per
70/// controllable option (upstream `xsltSecurityPrefs` in security.c).
71///
72/// # ABI
73/// The structure is private in upstream (defined only in security.c); the
74/// Rust representation is opaque to C consumers, so the layout is internal.
75#[repr(C)]
76pub struct XsltSecurityPrefs {
77 pub(crate) readFile: Option<xsltSecurityCheck>,
78 pub(crate) createFile: Option<xsltSecurityCheck>,
79 pub(crate) createDir: Option<xsltSecurityCheck>,
80 pub(crate) readNet: Option<xsltSecurityCheck>,
81 pub(crate) writeNet: Option<xsltSecurityCheck>,
82}
83
84/// Wrapper around `*mut c_void` that implements `Send` so it can be stored
85/// in a `Mutex`.
86///
87/// # Safety
88///
89/// The caller is responsible for ensuring that the pointed-to value is
90/// accessed in a thread-safe manner. The global default is only ever written
91/// or read through the `Mutex` guard, so concurrent access is serialized.
92#[repr(transparent)]
93struct SecurityPrefsPtr(*mut c_void);
94
95// SAFETY: Access to the wrapped pointer is serialized via Mutex, making
96// it safe to send between threads.
97unsafe impl Send for SecurityPrefsPtr {}
98
99/// Global default security preferences, stored as a raw pointer behind a
100/// [`Mutex`] for thread-safe access.
101static DEFAULT_SECURITY_PREFS: Mutex<Option<SecurityPrefsPtr>> = Mutex::new(None);
102
103/// Create new security preferences with the upstream default: a **zeroed**
104/// block whose five check callbacks are all NULL.
105///
106/// # Returns
107///
108/// A non-null pointer to the newly allocated security preferences on success.
109/// The caller is responsible for freeing it with [`xsltFreeSecurityPrefs`].
110///
111/// # Safety
112///
113/// The returned pointer must eventually be freed with
114/// [`xsltFreeSecurityPrefs`] to avoid memory leaks.
115#[no_mangle]
116pub unsafe extern "C" fn xsltNewSecurityPrefs() -> *mut c_void {
117 let prefs = Box::new(XsltSecurityPrefs {
118 readFile: None,
119 createFile: None,
120 createDir: None,
121 readNet: None,
122 writeNet: None,
123 });
124 Box::into_raw(prefs) as *mut c_void
125}
126
127/// Free security preferences previously allocated by [`xsltNewSecurityPrefs`].
128///
129/// # Safety
130///
131/// - `sec` must be a pointer returned by [`xsltNewSecurityPrefs`] that has
132/// not yet been freed.
133/// - After this call, `sec` is dangling and must not be dereferenced.
134#[no_mangle]
135pub unsafe extern "C" fn xsltFreeSecurityPrefs(sec: *mut c_void) {
136 if !sec.is_null() {
137 let _ = Box::from_raw(sec as *mut XsltSecurityPrefs);
138 }
139}
140
141/// Update a security option to use the given check callback.
142///
143/// UPSTREAM-PARITY: mirrors `security.c` `xsltSetSecurityPrefs`, including
144/// the quirk that `XSLT_SECPREF_WRITE_FILE` stores into the `createFile`
145/// slot. `option` uses the `xsltSecurityOption` enum values (1-5).
146///
147/// # Returns
148///
149/// 0 on success, -1 if `sec` is NULL or `option` is out of range.
150///
151/// # Safety
152///
153/// `sec` must point to a valid [`XsltSecurityPrefs`] obtained from
154/// [`xsltNewSecurityPrefs`] that has not yet been freed.
155#[no_mangle]
156pub unsafe extern "C" fn xsltSetSecurityPrefs(
157 sec: *mut c_void,
158 option: c_int,
159 func: Option<xsltSecurityCheck>,
160) -> c_int {
161 if sec.is_null() {
162 return -1;
163 }
164 let prefs = &mut *(sec as *mut XsltSecurityPrefs);
165 match option {
166 XSLT_SECPREF_READ_FILE => prefs.readFile = func,
167 // UPSTREAM-PARITY: WRITE_FILE writes createFile (upstream quirk).
168 XSLT_SECPREF_WRITE_FILE => prefs.createFile = func,
169 XSLT_SECPREF_CREATE_DIRECTORY => prefs.createDir = func,
170 XSLT_SECPREF_READ_NETWORK => prefs.readNet = func,
171 XSLT_SECPREF_WRITE_NETWORK => prefs.writeNet = func,
172 _ => return -1,
173 }
174 0
175}
176
177/// Look up the check callback configured for a security option.
178///
179/// # Returns
180///
181/// The stored callback, or NULL if `sec` is NULL or `option` is invalid or
182/// unset (upstream `xsltGetSecurityPrefs` returns NULL in those cases).
183///
184/// # Safety
185///
186/// If `sec` is non-null, it must point to a valid [`XsltSecurityPrefs`]
187/// obtained from [`xsltNewSecurityPrefs`] that has not yet been freed.
188#[no_mangle]
189pub unsafe extern "C" fn xsltGetSecurityPrefs(
190 sec: *mut c_void,
191 option: c_int,
192) -> Option<xsltSecurityCheck> {
193 if sec.is_null() {
194 return None;
195 }
196 let prefs = &*(sec as *mut XsltSecurityPrefs);
197 match option {
198 XSLT_SECPREF_READ_FILE => prefs.readFile,
199 XSLT_SECPREF_WRITE_FILE => prefs.createFile,
200 XSLT_SECPREF_CREATE_DIRECTORY => prefs.createDir,
201 XSLT_SECPREF_READ_NETWORK => prefs.readNet,
202 XSLT_SECPREF_WRITE_NETWORK => prefs.writeNet,
203 _ => None,
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`] that remains valid for
215/// the duration it is set as the default (i.e., until replaced by another
216/// 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 NULL 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 /// The security API surface matches the upstream callback contract:
246 /// set/get round-trip callbacks and default to NULL.
247 #[test]
248 fn test_set_get_callback_roundtrip() {
249 unsafe {
250 let prefs = xsltNewSecurityPrefs();
251 assert!(!prefs.is_null());
252 // Freshly created prefs have no callbacks configured.
253 assert!(xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE).is_none());
254 assert!(xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE).is_none());
255 assert!(xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY).is_none());
256 assert!(xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK).is_none());
257 assert!(xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK).is_none());
258 xsltFreeSecurityPrefs(prefs);
259 }
260 }
261
262 /// Registering a callback per option and reading it back works, and the
263 /// upstream WRITE_FILE -> createFile slot quirk is preserved.
264 #[test]
265 fn test_set_get_registered_callback() {
266 unsafe extern "C" fn forbid(
267 _sec: *mut c_void,
268 _ctxt: *mut c_void,
269 _value: *const c_char,
270 ) -> c_int {
271 0
272 }
273 let forbid: xsltSecurityCheck = forbid;
274 unsafe {
275 let prefs = xsltNewSecurityPrefs();
276 assert_eq!(
277 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE, Some(forbid)),
278 0
279 );
280 assert_eq!(
281 xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE, Some(forbid)),
282 0
283 );
284 assert_eq!(
285 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
286 Some(forbid)
287 );
288 // WRITE_FILE reads back from the createFile slot (upstream quirk).
289 assert_eq!(
290 xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
291 Some(forbid)
292 );
293 xsltFreeSecurityPrefs(prefs);
294 }
295 }
296
297 /// Invalid options and NULL prefs return -1 / NULL like upstream.
298 #[test]
299 fn test_invalid_option_and_null() {
300 unsafe {
301 assert_eq!(
302 xsltSetSecurityPrefs(ptr::null_mut(), XSLT_SECPREF_READ_FILE, None),
303 -1
304 );
305 let prefs = xsltNewSecurityPrefs();
306 assert_eq!(xsltSetSecurityPrefs(prefs, 99, None), -1);
307 assert!(xsltGetSecurityPrefs(prefs, 99).is_none());
308 assert!(xsltGetSecurityPrefs(ptr::null_mut(), XSLT_SECPREF_READ_FILE).is_none());
309 xsltFreeSecurityPrefs(prefs);
310 }
311 }
312
313 /// Verify that creating and freeing security prefs works.
314 #[test]
315 fn test_new_free_security_prefs() {
316 unsafe {
317 let prefs = xsltNewSecurityPrefs();
318 assert!(!prefs.is_null());
319 xsltFreeSecurityPrefs(prefs);
320 }
321 }
322
323 /// Verify that freeing a null pointer is a no-op.
324 #[test]
325 fn test_free_null() {
326 unsafe {
327 // Should not panic or crash.
328 xsltFreeSecurityPrefs(ptr::null_mut());
329 }
330 }
331}