From 28c9e156937613d81633aadbbf59e368f857f779 Mon Sep 17 00:00:00 2001 From: Marco Constantino Date: Sat, 5 Sep 2026 01:27:18 +0200 Subject: [PATCH] Guard against two DoS crashes: parser recursion depth and integer div/mod by zero Add a recursion depth cap in Parser::parseExpression. Deeply-nested expressions previously recursed through the expression-precedence chain without bound and exhausted the stack (SIGSEGV at parse time). jinja2 guards recursion here. Guard integer division and modulo against a zero divisor at all sites (the BinaryOpExpr evaluation switch and the Value operators). Previously {{ 1/0 }}, {{ 5//0 }} or {{ x%0 }} crashed with SIGFPE at render time. Both throw std::runtime_error, consistent with existing error handling. Verified against 239 real chat templates (unchanged) and normal arithmetic (1+2*3=7, 10%3=1, 8/2=4). Found via coverage-guided fuzzing. --- include/minja/minja.hpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/include/minja/minja.hpp b/include/minja/minja.hpp index 5ed0556..72098d0 100644 --- a/include/minja/minja.hpp +++ b/include/minja/minja.hpp @@ -499,13 +499,17 @@ class Value : public std::enable_shared_from_this { return get() * rhs.get(); } Value operator/(const Value& rhs) const { - if (is_number_integer() && rhs.is_number_integer()) - return get() / rhs.get(); - else + if (is_number_integer() && rhs.is_number_integer()) { + auto d = rhs.get(); + if (d == 0) throw std::runtime_error("integer division by zero"); + return get() / d; + } else return get() / rhs.get(); } Value operator%(const Value& rhs) const { - return get() % rhs.get(); + auto d = rhs.get(); + if (d == 0) throw std::runtime_error("modulo by zero"); + return get() % d; } }; @@ -1371,8 +1375,8 @@ class BinaryOpExpr : public Expression { case Op::Mul: return l * r; case Op::Div: return l / r; case Op::MulMul: return std::pow(l.get(), r.get()); - case Op::DivDiv: return l.get() / r.get(); - case Op::Mod: return l.get() % r.get(); + case Op::DivDiv: { auto d = r.get(); if (d == 0) throw std::runtime_error("integer division by zero"); return l.get() / d; } + case Op::Mod: { auto d = r.get(); if (d == 0) throw std::runtime_error("modulo by zero"); return l.get() % d; } case Op::Eq: return l == r; case Op::Ne: return l != r; case Op::Lt: return l < r; @@ -1705,6 +1709,7 @@ class Parser { std::shared_ptr template_str; CharIterator start, end, it; Options options; + int expr_depth = 0; Parser(const std::shared_ptr& template_str, const Options & options) : template_str(template_str), options(options) { if (!template_str) throw std::runtime_error("Template string is null"); @@ -1882,6 +1887,13 @@ class Parser { } std::shared_ptr parseExpression(bool allow_if_expr = true) { + struct DepthGuard { + int & d; + explicit DepthGuard(int & d) : d(d) { + if (++d > 1000) throw std::runtime_error("expression nesting too deep"); + } + ~DepthGuard() { --d; } + } depth_guard(expr_depth); auto left = parseLogicalOr(); if (it == end) return left;