Skip to main content

reddb_server/storage/query/parser/
limits.rs

1//! Parser DoS limits.
2//!
3//! These limits are uniformly applied at parser entry points so a
4//! malicious query string can't exhaust recursion stack, RAM, or
5//! identifier bookkeeping. Limit values are documented in
6//! `docs/security/parser-limits.md` (issue #87).
7//!
8//! # Defaults
9//!
10//! | Limit                 | Default | Rationale                                       |
11//! |-----------------------|---------|-------------------------------------------------|
12//! | `max_depth`           | 128     | Recursive descent + Pratt; well above hand-     |
13//! |                       |         | written queries (typical ≤ 12).                  |
14//! | `max_input_bytes`     | 1 MiB   | Hard cap on the token stream input.              |
15//! | `max_identifier_chars`| 256     | Long enough for legitimate UUID-tagged names,    |
16//! |                       |         | short enough to bound HashMap pressure.          |
17//!
18//! Callers that need different limits (replication apply, admin DDL
19//! migrations) construct a custom [`ParserLimits`] and pass it to
20//! [`Parser::with_limits`](super::Parser::with_limits).
21
22/// Hard limits enforced by the parser.
23///
24/// The fields are public so the harness module (used by tests in
25/// `tests/support/parser_hardening`) can mutate them inline. Default
26/// values match production defaults.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct ParserLimits {
29    /// Maximum recursion depth across recursive descent points
30    /// (expressions, parenthesised sub-queries, JOIN chains).
31    pub max_depth: usize,
32    /// Maximum input length in bytes. Checked at the lexer entry
33    /// before tokenization begins.
34    pub max_input_bytes: usize,
35    /// Maximum identifier length in characters. Checked when an
36    /// identifier token is constructed in the lexer.
37    pub max_identifier_chars: usize,
38}
39
40impl Default for ParserLimits {
41    fn default() -> Self {
42        Self {
43            max_depth: 128,
44            max_input_bytes: 1024 * 1024, // 1 MiB
45            max_identifier_chars: 256,
46        }
47    }
48}
49
50impl ParserLimits {
51    /// Permissive limits for tests that intentionally probe deep
52    /// nesting or long inputs without tripping DoS guards.
53    pub fn permissive() -> Self {
54        Self {
55            max_depth: 1024,
56            max_input_bytes: 16 * 1024 * 1024,
57            max_identifier_chars: 4096,
58        }
59    }
60}
61
62/// Internal recursion-depth tracker. RAII-style: a guard
63/// [`DepthGuard`] increments on construction and decrements on
64/// drop, so early returns/`?` propagation can't leak depth.
65#[derive(Debug)]
66pub(crate) struct DepthCounter {
67    pub(crate) depth: usize,
68    pub(crate) max_depth: usize,
69}
70
71impl DepthCounter {
72    pub(crate) fn new(max_depth: usize) -> Self {
73        Self {
74            depth: 0,
75            max_depth,
76        }
77    }
78}