Skip to main content

libddwaf/
context.rs

1use std::error;
2use std::fmt;
3use std::time::Duration;
4
5use crate::object::get_default_allocator;
6use crate::object::WafOwnedOutputAllocator;
7use crate::object::{AsRawMutObject, Keyed, WafArray, WafMap, WafObject};
8
9/// A WAF Context that can be used to evaluate the configured ruleset against address data.
10///
11/// This is obtained by calling [`Handle::new_context`][crate::Handle::new_context], and a given [`Context`] should only
12/// be used to handle data for a single request.
13pub struct Context {
14    pub(crate) raw: libddwaf_sys::ddwaf_context,
15}
16
17/// Subcontexts are type of [`Context`] that inherit the data from their parents,
18/// but evaluations do not affect the parent's data.
19///
20/// Subcontexts can outlive their parent contexts.
21///
22/// They are obtained by calling [`Context::new_subcontext`][crate::Context::new_subcontext].
23pub struct Subcontext {
24    pub(crate) raw: libddwaf_sys::ddwaf_subcontext,
25}
26
27/// Common waf evaluation interface for [`Context`] and [`Subcontext`].
28pub trait RunnableContext {
29    /// Evaluates the configured ruleset against the provided address data, and returns the result
30    /// of this evaluation.
31    ///
32    /// # Errors
33    /// Returns an error if the WAF encountered an internal error, invalid object, or invalid argument while processing
34    /// the request.
35    fn run(&mut self, data: WafMap, timeout: Duration) -> Result<RunResult, RunError>;
36
37    /// Evaluates multiple batches of address data in sequence, and returns a combined result.
38    ///
39    /// Each element in `data` must be a [`WafMap`] containing address data. Addresses from earlier
40    /// batches remain available while later batches are evaluated.
41    ///
42    /// # Errors
43    /// Returns an error if the WAF encountered an internal error, invalid object, or invalid argument while processing
44    /// the request.
45    fn run_batches(&mut self, data: WafArray, timeout: Duration) -> Result<RunResult, RunError>;
46}
47
48type RunFunc<S> = unsafe extern "C" fn(
49    S,
50    *mut libddwaf_sys::ddwaf_object,
51    libddwaf_sys::ddwaf_allocator,
52    *mut libddwaf_sys::ddwaf_object,
53    u64,
54) -> libddwaf_sys::DDWAF_RET_CODE;
55
56fn run<S>(
57    raw_self: S,
58    func: RunFunc<S>,
59    func_name: &'static str,
60    mut data: impl AsRawMutObject,
61    timeout: Duration,
62) -> Result<RunResult, RunError> {
63    let mut res = std::mem::MaybeUninit::<RunOutput>::uninit();
64
65    let data_ptr = unsafe { data.as_raw_mut() };
66
67    let status = unsafe {
68        func(
69            raw_self,
70            data_ptr,
71            get_default_allocator().into(),
72            res.as_mut_ptr().cast(),
73            timeout.as_micros().try_into().unwrap_or(u64::MAX),
74        )
75    };
76    match status {
77        libddwaf_sys::DDWAF_ERR_INTERNAL => {
78            // It's unclear whether the persistent data needs to be kept alive or not, so we
79            // keep it alive to be on the safe side.
80            std::mem::forget(data);
81            Err(RunError::InternalError)
82        }
83        libddwaf_sys::DDWAF_ERR_INVALID_OBJECT => {
84            // The C API frees invalid object data using the allocator passed above.
85            std::mem::forget(data);
86            Err(RunError::InvalidObject)
87        }
88        libddwaf_sys::DDWAF_ERR_INVALID_ARGUMENT => Err(RunError::InvalidArgument),
89        libddwaf_sys::DDWAF_OK => {
90            // We need to keep the persistent data alive (now owned by the WAF)
91            std::mem::forget(data);
92            Ok(RunResult::NoMatch(unsafe { res.assume_init() }))
93        }
94        libddwaf_sys::DDWAF_MATCH => {
95            // We need to keep the persistent data alive (now owned by the WAF)
96            std::mem::forget(data);
97            Ok(RunResult::Match(unsafe { res.assume_init() }))
98        }
99        unknown => unreachable!(
100            "Unexpected value returned by {}: 0x{:02X}",
101            func_name,
102            unknown
103        ),
104    }
105}
106impl RunnableContext for Context {
107    fn run(&mut self, data: WafMap, timeout: Duration) -> Result<RunResult, RunError> {
108        run(
109            self.raw,
110            libddwaf_sys::ddwaf_context_eval,
111            stringify!(libddwaf_sys::ddwaf_context_eval),
112            data,
113            timeout,
114        )
115    }
116
117    fn run_batches(&mut self, data: WafArray, timeout: Duration) -> Result<RunResult, RunError> {
118        run(
119            self.raw,
120            libddwaf_sys::ddwaf_context_multieval,
121            stringify!(libddwaf_sys::ddwaf_context_multieval),
122            data,
123            timeout,
124        )
125    }
126}
127impl Context {
128    /// Creates a new [`Subcontext`] from this [`Context`].
129    ///
130    /// # Errors
131    /// Returns an error if the WAF encountered an internal error while creating the subcontext.
132    /// This will not happen unless there is a bug in the WAF.
133    pub fn new_subcontext(&self) -> Result<Subcontext, InternalError> {
134        let raw = unsafe { libddwaf_sys::ddwaf_subcontext_init(self.raw) };
135        if raw.is_null() {
136            Err(InternalError {})
137        } else {
138            Ok(Subcontext { raw })
139        }
140    }
141}
142impl RunnableContext for Subcontext {
143    fn run(&mut self, data: WafMap, timeout: Duration) -> Result<RunResult, RunError> {
144        run(
145            self.raw,
146            libddwaf_sys::ddwaf_subcontext_eval,
147            stringify!(libddwaf_sys::ddwaf_subcontext_eval),
148            data,
149            timeout,
150        )
151    }
152
153    fn run_batches(&mut self, data: WafArray, timeout: Duration) -> Result<RunResult, RunError> {
154        run(
155            self.raw,
156            libddwaf_sys::ddwaf_subcontext_multieval,
157            stringify!(libddwaf_sys::ddwaf_subcontext_multieval),
158            data,
159            timeout,
160        )
161    }
162}
163impl Drop for Context {
164    fn drop(&mut self) {
165        unsafe { libddwaf_sys::ddwaf_context_destroy(self.raw) }
166    }
167}
168impl Drop for Subcontext {
169    fn drop(&mut self) {
170        unsafe { libddwaf_sys::ddwaf_subcontext_destroy(self.raw) }
171    }
172}
173
174/// Safety: [`Context`] is [`Send`] because it doesn't depend on thread local
175/// data and its pointer is not leaked or otherwise shared with other owning
176/// instances.
177unsafe impl Send for Context {}
178/// Safety: [`Context`] is [`Sync`] because having a shared reference does not
179/// allow for changing state (except for atomically increasing reference count
180/// of some elements in the context, like that of the context store)
181unsafe impl Sync for Context {}
182
183/// Safety: The same considerations apply to [`Subcontext`] as to [`Context`].
184unsafe impl Send for Subcontext {}
185/// Safety: The only method available takes an exclusive borrow.
186unsafe impl Sync for Subcontext {}
187
188/// The result of the [`RunnableContext::run`] operation.
189#[derive(Debug)]
190pub enum RunResult {
191    /// The WAF successfully processed the request, and produced no match.
192    NoMatch(RunOutput),
193    /// The WAF successfully processed the request and some event rules matched
194    /// some of the supplied address data.
195    Match(RunOutput),
196}
197
198/// The error that can occur during a [`RunnableContext::run`] operation.
199#[non_exhaustive]
200#[derive(Debug)]
201pub enum RunError {
202    /// The WAF encountered an internal error while processing the request.
203    InternalError,
204    /// The WAF encountered an invalid object while processing the request.
205    InvalidObject,
206    /// The WAF encountered an invalid argument while processing the request.
207    InvalidArgument,
208}
209impl fmt::Display for RunError {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self {
212            RunError::InternalError => write!(f, "The WAF encountered an internal error"),
213            RunError::InvalidObject => write!(f, "The WAF encountered an invalid object"),
214            RunError::InvalidArgument => write!(f, "The WAF encountered an invalid argument"),
215        }
216    }
217}
218impl error::Error for RunError {}
219
220/// An unexpected internal error in the WAF from functions other than [`RunnableContext::run`].
221#[derive(Debug)]
222pub struct InternalError {}
223impl fmt::Display for InternalError {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        write!(
226            f,
227            "An unexpected internal error occurred in the WAF; check the error logs"
228        )
229    }
230}
231impl error::Error for InternalError {}
232
233/// The data produced by a [`Context::run`] operation.
234#[repr(transparent)]
235pub struct RunOutput {
236    data: WafOwnedOutputAllocator<WafMap>,
237}
238impl RunOutput {
239    /// Returns true if the WAF did not have enough time to process all the address data that was
240    /// being evaluated.
241    #[must_use]
242    pub fn timeout(&self) -> bool {
243        debug_assert!(self.data.is_valid());
244        self.data
245            .get_bstr(b"timeout")
246            .and_then(|o| o.to_bool())
247            .unwrap_or_default()
248    }
249
250    /// Returns true if the WAF determined the trace for this request should have its priority
251    /// overridden to ensure it is not dropped by the sampler.
252    #[must_use]
253    pub fn keep(&self) -> bool {
254        debug_assert!(self.data.is_valid());
255        self.data
256            .get_bstr(b"keep")
257            .and_then(|o| o.to_bool())
258            .unwrap_or_default()
259    }
260
261    /// Returns the total time spent processing the request; excluding bindings overhead (which
262    /// ought to be trivial).
263    pub fn duration(&self) -> Duration {
264        debug_assert!(self.data.is_valid());
265        self.data
266            .get_bstr(b"duration")
267            .and_then(|o| o.to_u64())
268            .map(Duration::from_nanos)
269            .unwrap_or_default()
270    }
271
272    /// Returns the number of input batches fully evaluated by the WAF.
273    #[must_use]
274    pub fn evaluated(&self) -> u64 {
275        debug_assert!(self.data.is_valid());
276        self.data
277            .get_bstr(b"evaluated")
278            .and_then(|o| o.to_u64())
279            .unwrap_or_default()
280    }
281
282    /// Returns the list of events that were produced by this WAF run.
283    ///
284    /// This is only expected to be populated when [`Context::run`] returns [`RunResult::Match`].
285    pub fn events(&self) -> Option<&Keyed<WafArray>> {
286        debug_assert!(self.data.is_valid());
287        self.data
288            .get_bstr(b"events")
289            .and_then(Keyed::<WafObject>::as_type)
290    }
291
292    /// Returns the list of actions that were produced by this WAF run.
293    ///
294    /// This is only expected to be populated when [`Context::run`] returns [`RunResult::Match`].
295    pub fn actions(&self) -> Option<&Keyed<WafMap>> {
296        debug_assert!(self.data.is_valid());
297        self.data
298            .get_bstr(b"actions")
299            .and_then(Keyed::<WafObject>::as_type)
300    }
301
302    /// Returns the list of attributes that were produced by this WAF run, and which should be
303    /// attached to the surrounding trace.
304    pub fn attributes(&self) -> Option<&Keyed<WafMap>> {
305        debug_assert!(self.data.is_valid());
306        self.data
307            .get_bstr(b"attributes")
308            .and_then(Keyed::<WafObject>::as_type)
309    }
310}
311impl fmt::Debug for RunOutput {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        f.debug_struct("RunOutput")
314            .field("timeout", &self.timeout())
315            .field("keep", &self.keep())
316            .field("duration", &self.duration())
317            .field("evaluated", &self.evaluated())
318            .field("events", &self.events())
319            .field("actions", &self.actions())
320            .field("attributes", &self.attributes())
321            .finish()
322    }
323}