Skip to content

Fix Minja OOB & Parser Recursion Vulnerabilities - #1070

Merged
sayanshaw24 merged 6 commits into
mainfrom
sayanshaw/minja-oob
Jun 9, 2026
Merged

Fix Minja OOB & Parser Recursion Vulnerabilities#1070
sayanshaw24 merged 6 commits into
mainfrom
sayanshaw/minja-oob

Conversation

@sayanshaw24

@sayanshaw24 sayanshaw24 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Fix OOB read in minja string slice + stack overflow in recursive parser

Summary

Two security fixes for the minja template engine used by OrtxApplyChatTemplate:

  1. Heap OOB readSubscriptExpr::do_evaluate string slice with step != 1 and out-of-range indices could read past the string buffer, leaking adjacent memory into rendered output.
  2. Stack overflow — The recursive descent parser (parseExpression chain) had no depth limit, allowing a crafted template as small as 8 bytes to exhaust the call stack.

Problem 1: String slice OOB read

When a chat template applies a slice with step != 1 to a string (e.g. 'abc'[0:65536:2]), SubscriptExpr::do_evaluate uses a loop that indexes the string via std::string::operator[]. The wrap() helper only adjusts negative indices by adding len, but never clamps indices to [0, len). This means:

  • Positive indices exceeding the string length pass through unchanged
  • Negative indices where |i| > len remain negative after adjustment

The step == 1 fast-path safely uses substr, but any step >= 2 or negative step bypasses it and hits the unchecked s[i] loop — reading out-of-bounds memory.

Problem 2: Parser recursion stack overflow

The minja::Parser class implements expression parsing as a chain of ~12 mutually recursive functions (parseExpressionparseLogicalOr → ... → parseValueExpressionparseDictionaryparseExpression) with no recursion depth limit. A crafted template containing nested delimiters causes unbounded recursion, exhausting the stack and crashing the process.

Fix

Slice OOB — Added Python-style slice endpoint clamping in the string branch of SubscriptExpr::do_evaluate:

int64_t n = static_cast<int64_t>(s.size());
auto clamp = [](int64_t i, int64_t lo, int64_t hi) -> int64_t {
    if (i < lo) return lo;
    if (i > hi) return hi;
    return i;
};
start = clamp(start, step > 0 ? (int64_t)0 : (int64_t)-1, step > 0 ? n : n - 1);
end   = clamp(end,   step > 0 ? (int64_t)0 : (int64_t)-1, step > 0 ? n : n - 1);

Stack overflow — Added a recursion depth counter with RAII guard to the Parser class, checked at the entry of parseExpression:

static constexpr size_t MAX_PARSE_DEPTH = 100;
size_t parse_depth_ = 0;

struct DepthGuard {
    size_t& depth;
    DepthGuard(size_t& d, size_t max) : depth(d) {
        if (++depth > max)
            throw std::runtime_error("Template parsing exceeded maximum nesting depth");
    }
    ~DepthGuard() { --depth; }
};

Files changed

  • shared/api/minja.hpp — clamp slice endpoints before the indexing loop; add recursion depth guard to parser
  • test/pp_api_test/test_tokenizer_chat.cc — new tests MinjaStringSliceOOBClamped and MinjaParserRecursionDepthLimit

Testing

New tests:

  • MinjaStringSliceOOBClamped — verifies three cases that previously caused OOB reads:
    • 'abc'[0:65536:2]"ac" (positive step, end far beyond length)
    • 'abc'[100:0:-1]"cb" (negative step, start beyond length)
    • 'abc'[-100:2:1]"ab" (negative index that remains negative after wrap)
  • MinjaParserRecursionDepthLimit — verifies that a deeply nested template (200 levels of {) returns an error instead of crashing with a stack overflow.

All pp_api_test chat template tests pass.

Copilot AI review requested due to automatic review settings June 5, 2026 19:13
@sayanshaw24
sayanshaw24 requested a review from a team as a code owner June 5, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an out-of-bounds read in the embedded Minja template engine when applying Python-style string slicing with step != 1 and out-of-range indices, improving safety for chat-template rendering in onnxruntime-extensions.

Changes:

  • Clamp string slice start/end indices in minja::SubscriptExpr::do_evaluate before the stepped indexing loop.
  • Add a regression test covering previously OOB-inducing slice patterns in chat template evaluation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
shared/api/minja.hpp Clamps string slice endpoints to prevent OOB reads when stepping through std::string via indexing.
test/pp_api_test/test_tokenizer_chat.cc Adds a regression test that exercises out-of-range slice indices with non-unit step.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread shared/api/minja.hpp Outdated
@sayanshaw24 sayanshaw24 changed the title Fix Minja OOB Vulnerability Fix Minja OOB & Parser Recursion Vulnerabilities Jun 6, 2026
Comment thread shared/api/minja.hpp Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants