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
38type RunFunc<S> = unsafe extern "C" fn(
39    S,
40    *mut libddwaf_sys::ddwaf_object,
41    libddwaf_sys::ddwaf_allocator,
42    *mut libddwaf_sys::ddwaf_object,
43    u64,
44) -> libddwaf_sys::DDWAF_RET_CODE;
45
46fn run<S>(
47    raw_self: S,
48    func: RunFunc<S>,
49    mut data: WafMap,
50    timeout: Duration,
51) -> Result<RunResult, RunError> {
52    let mut res = std::mem::MaybeUninit::<RunOutput>::uninit();
53
54    let data_ptr = unsafe { data.as_raw_mut() };
55
56    let status = unsafe {
57        func(
58            raw_self,
59            data_ptr,
60            get_default_allocator().into(),
61            res.as_mut_ptr().cast(),
62            timeout.as_micros().try_into().unwrap_or(u64::MAX),
63        )
64    };
65    match status {
66        libddwaf_sys::DDWAF_ERR_INTERNAL => {
67            // It's unclear whether the persistent data needs to be kept alive or not, so we
68            // keep it alive to be on the safe side.
69            std::mem::forget(data);
70            Err(RunError::InternalError)
71        }
72        libddwaf_sys::DDWAF_ERR_INVALID_OBJECT => Err(RunError::InvalidObject),
73        libddwaf_sys::DDWAF_ERR_INVALID_ARGUMENT => Err(RunError::InvalidArgument),
74        libddwaf_sys::DDWAF_OK => {
75            // We need to keep the persistent data alive (now owned by the WAF)
76            std::mem::forget(data);
77            Ok(RunResult::NoMatch(unsafe { res.assume_init() }))
78        }
79        libddwaf_sys::DDWAF_MATCH => {
80            // We need to keep the persistent data alive (now owned by the WAF)
81            std::mem::forget(data);
82            Ok(RunResult::Match(unsafe { res.assume_init() }))
83        }
84        unknown => unreachable!(
85            "Unexpected value returned by {}: 0x{:02X}",
86            stringify!(libddwaf_sys::ddwaf_run),
87            unknown
88        ),
89    }
90}
91impl RunnableContext for Context {
92    fn run(&mut self, data: WafMap, timeout: Duration) -> Result<RunResult, RunError> {
93        run(self.raw, libddwaf_sys::ddwaf_context_eval, data, timeout)
94    }
95}
96impl Context {
97    /// Creates a new [`Subcontext`] from this [`Context`].
98    ///
99    /// # Errors
100    /// Returns an error if the WAF encountered an internal error while creating the subcontext.
101    /// This will not happen unless there is a bug in the WAF.
102    pub fn new_subcontext(&self) -> Result<Subcontext, InternalError> {
103        let raw = unsafe { libddwaf_sys::ddwaf_subcontext_init(self.raw) };
104        if raw.is_null() {
105            Err(InternalError {})
106        } else {
107            Ok(Subcontext { raw })
108        }
109    }
110}
111impl RunnableContext for Subcontext {
112    fn run(&mut self, data: WafMap, timeout: Duration) -> Result<RunResult, RunError> {
113        run(self.raw, libddwaf_sys::ddwaf_subcontext_eval, data, timeout)
114    }
115}
116impl Drop for Context {
117    fn drop(&mut self) {
118        unsafe { libddwaf_sys::ddwaf_context_destroy(self.raw) }
119    }
120}
121impl Drop for Subcontext {
122    fn drop(&mut self) {
123        unsafe { libddwaf_sys::ddwaf_subcontext_destroy(self.raw) }
124    }
125}
126
127/// Safety: [`Context`] is [`Send`] because it doesn't depend on thread local
128/// data and its pointer is not leaked or otherwise shared with other owning
129/// instances.
130unsafe impl Send for Context {}
131/// Safety: [`Context`] is [`Sync`] because having a shared reference does not
132/// allow for changing state (except for atomically increasing reference count
133/// of some elements in the context, like that of the context store)
134unsafe impl Sync for Context {}
135
136/// Safety: The same considerations apply to [`Subcontext`] as to [`Context`].
137unsafe impl Send for Subcontext {}
138/// Safety: The only method available takes an exclusive borrow.
139unsafe impl Sync for Subcontext {}
140
141/// The result of the [`RunnableContext::run`] operation.
142#[derive(Debug)]
143pub enum RunResult {
144    /// The WAF successfully processed the request, and produced no match.
145    NoMatch(RunOutput),
146    /// The WAF successfully processed the request and some event rules matched
147    /// some of the supplied address data.
148    Match(RunOutput),
149}
150
151/// The error that can occur during a [`RunnableContext::run`] operation.
152#[non_exhaustive]
153#[derive(Debug)]
154pub enum RunError {
155    /// The WAF encountered an internal error while processing the request.
156    InternalError,
157    /// The WAF encountered an invalid object while processing the request.
158    InvalidObject,
159    /// The WAF encountered an invalid argument while processing the request.
160    InvalidArgument,
161}
162impl fmt::Display for RunError {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            RunError::InternalError => write!(f, "The WAF encountered an internal error"),
166            RunError::InvalidObject => write!(f, "The WAF encountered an invalid object"),
167            RunError::InvalidArgument => write!(f, "The WAF encountered an invalid argument"),
168        }
169    }
170}
171impl error::Error for RunError {}
172
173/// An unexpected internal error in the WAF from functions other than [`RunnableContext::run`].
174#[derive(Debug)]
175pub struct InternalError {}
176impl fmt::Display for InternalError {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        write!(
179            f,
180            "An unexpected internal error occurred in the WAF; check the error logs"
181        )
182    }
183}
184impl error::Error for InternalError {}
185
186/// The data produced by a [`Context::run`] operation.
187#[repr(transparent)]
188pub struct RunOutput {
189    data: WafOwnedOutputAllocator<WafMap>,
190}
191impl RunOutput {
192    /// Returns true if the WAF did not have enough time to process all the address data that was
193    /// being evaluated.
194    #[must_use]
195    pub fn timeout(&self) -> bool {
196        debug_assert!(self.data.is_valid());
197        self.data
198            .get_bstr(b"timeout")
199            .and_then(|o| o.to_bool())
200            .unwrap_or_default()
201    }
202
203    /// Returns true if the WAF determined the trace for this request should have its priority
204    /// overridden to ensure it is not dropped by the sampler.
205    #[must_use]
206    pub fn keep(&self) -> bool {
207        debug_assert!(self.data.is_valid());
208        self.data
209            .get_bstr(b"keep")
210            .and_then(|o| o.to_bool())
211            .unwrap_or_default()
212    }
213
214    /// Returns the total time spent processing the request; excluding bindings overhead (which
215    /// ought to be trivial).
216    pub fn duration(&self) -> Duration {
217        debug_assert!(self.data.is_valid());
218        self.data
219            .get_bstr(b"duration")
220            .and_then(|o| o.to_u64())
221            .map(Duration::from_nanos)
222            .unwrap_or_default()
223    }
224
225    /// Returns the list of events that were produced by this WAF run.
226    ///
227    /// This is only expected to be populated when [`Context::run`] returns [`RunResult::Match`].
228    pub fn events(&self) -> Option<&Keyed<WafArray>> {
229        debug_assert!(self.data.is_valid());
230        self.data
231            .get_bstr(b"events")
232            .and_then(Keyed::<WafObject>::as_type)
233    }
234
235    /// Returns the list of actions that were produced by this WAF run.
236    ///
237    /// This is only expected to be populated when [`Context::run`] returns [`RunResult::Match`].
238    pub fn actions(&self) -> Option<&Keyed<WafMap>> {
239        debug_assert!(self.data.is_valid());
240        self.data
241            .get_bstr(b"actions")
242            .and_then(Keyed::<WafObject>::as_type)
243    }
244
245    /// Returns the list of attributes that were produced by this WAF run, and which should be
246    /// attached to the surrounding trace.
247    pub fn attributes(&self) -> Option<&Keyed<WafMap>> {
248        debug_assert!(self.data.is_valid());
249        self.data
250            .get_bstr(b"attributes")
251            .and_then(Keyed::<WafObject>::as_type)
252    }
253}
254impl fmt::Debug for RunOutput {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        f.debug_struct("RunOutput")
257            .field("timeout", &self.timeout())
258            .field("keep", &self.keep())
259            .field("duration", &self.duration())
260            .field("events", &self.events())
261            .field("actions", &self.actions())
262            .field("attributes", &self.attributes())
263            .finish()
264    }
265}