PT-2026-56303 · Crates.Io · Ratex-Parser
Published
2026-07-07
·
Updated
2026-07-07
·
CVE-2026-53530
CVSS v4.0
8.7
High
| Vector | AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N |
Summary
The public parser entrypoint
ratex parser::parse(&str) panics on the 9-byte input verbéxé (i.e. verb followed by the non-ASCII delimiter é). When handling a verb command, the parser slices the verbatim argument with byte indices (arg[1..arg.len() - 1]); if the delimiter character is multibyte UTF-8, index 1 lands inside that character and Rust panics with “byte index 1 is not a char boundary”. Because RaTeX’s release profile sets panic = "abort" (Cargo.toml:48), the panic aborts the entire process — not just the current request/thread — making this a hard denial of service for any service that renders untrusted LaTeX.Details
Affected code
crates/ratex-parser/src/parser.rs, parse symbol inner:rust
if let Some(stripped) = text.strip prefix("verb") { // parser.rs:901
self.consume();
let arg = stripped.to string(); // e.g. "éxé"
let star = arg.starts with('*');
let arg = if star { &arg[1..] } else { &arg }; // parser.rs:905 (also byte-sliced)
if arg.len() < 2 { // byte length
return Err(ParseError::new("verb assertion failed", Some(&nucleus)));
}
let body = arg[1..arg.len() - 1].to string(); // parser.rs:910 <-- PANIC on multibyte delimiter
...
}For input
verbéxé: arg = "éxé", where é = U+00E9 (bytes C3 A9). arg.len() is the byte length (5), the < 2 guard passes, and arg[1..4] starts at byte index 1 — inside the first é (bytes 0..2) — so the slice panics. The lexer groups verb<delim>…<delim> correctly with char semantics (lexer.rs lex verb); only the parser mishandles it.PoC
$ printf 'verbxc3xa9xxc3xa9
' | ./target/release/parse
thread 'main' panicked at crates/ratex-parser/src/parser.rs:910:27:
start byte index 1 is not a char boundary; it is inside 'é' (bytes 0..2 of string)
Aborted (core dumped) # exit 134 — panic=abort kills the whole processImpact
Any application that renders untrusted LaTeX through RaTeX (web “render this math” endpoint, WASM in-browser use, the FFI embedded in another app) can be crashed by a tiny string. With
panic = "abort" in release builds, the crash takes down the whole process / server, so a single malicious formula causes a full-service DoS (and, in batch pipelines, drops all queued work).Remediation
Slice by character boundaries instead of byte indices, mirroring the UTF-8-correct logic the lexer already uses. For example:
rust
let chars: Vec<char> = arg.chars().collect();
if chars.len() < 2 { return Err(ParseError::new("verb assertion failed", Some(&nucleus))); }
let body: String = chars[1..chars.len() - 1].iter().collect();(Apply the same char-aware handling to the
* strip at parser.rs:905.) More broadly, consider not using panic = "abort" for builds embedded in long-running services, and/or wrapping parsing in catch unwind at the FFI/WASM boundary — but the byte-slice fix is the direct correction.Fix
Resource Exhaustion
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Ratex-Parser