Severity by source
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Network-reachable with no authentication, interaction, or special conditions; sole impact is high availability loss via process abort with no scope change beyond the vulnerable component.
Primary rating from Vendor (https://github.com/erweixin/RaTeX).
CVSS VectorVendor: https://github.com/erweixin/RaTeX
Lifecycle Timeline
5DescriptionCVE.org
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:
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
<img width="1109" height="205" alt="image" src="https://github.com/user-attachments/assets/cd4bc6ae-23dd-458f-826c-6ce4e85c7005" />
$ printf '\\verb\xc3\xa9x\xc3\xa9\n' | ./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:
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.
AnalysisAI
Process-aborting denial of service in ratex-parser (Rust crate) allows any remote unauthenticated attacker to crash the entire hosting process by submitting a 9-byte LaTeX string containing a multibyte UTF-8 delimiter in a \verb command. The parser at parser.rs:910 slices the verbatim argument using byte indices rather than character indices; when the delimiter is a multibyte character such as é (U+00E9, two bytes), byte index 1 falls inside the character boundary, triggering a Rust panic that - due to panic = "abort" in the release profile - terminates the whole process rather than unwinding a single thread. A publicly available proof-of-concept demonstrates reliable reproduction, and vendor-released patch version 0.1.11 is available.
Technical ContextAI
RaTeX is a Rust-based LaTeX parser exposing a public entrypoint ratex_parser::parse(&str). The vulnerability resides in parse_symbol_inner within crates/ratex-parser/src/parser.rs at line 910, classified as CWE-248 (Uncaught Exception). The root cause is a byte-index string slice operation arg[1..arg.len() - 1] applied to a UTF-8 Rust &str. In Rust, string byte-index slicing panics at runtime if the requested index does not fall on a valid UTF-8 character boundary. The delimiter character é (U+00E9) encodes as two bytes (0xC3 0xA9), so byte index 1 falls inside the character, not at a boundary, triggering the panic. Critically, RaTeX's Cargo.toml (line 48) sets panic = "abort" in the release profile, meaning the panic does not unwind the thread stack but immediately calls abort(), killing the entire process. The lexer (lexer.rs lex_verb) already handles this correctly using character-aware semantics, making the inconsistency a localized parser bug. The affected package is pkg:rust/ratex-parser in all versions prior to 0.1.11.
RemediationAI
Vendor-released patch: 0.1.11. Upgrade ratex-parser to version 0.1.11 or later, which corrects the byte-index slicing in parse_symbol_inner to use character-boundary-aware indexing (chars().collect() and char-based slice operations). The advisory and patch are referenced at https://github.com/erweixin/RaTeX/security/advisories/GHSA-4hgp-59h5-gvrj. As an immediate compensating control prior to patching, services can apply input validation at the application layer to reject or sanitize any LaTeX input containing non-ASCII characters before passing it to ratex_parser::parse(); this limits exposure but may break legitimate internationalized formulas. A further defensive measure recommended by the advisory - regardless of patching - is wrapping parsing calls in std::panic::catch_unwind at all FFI and WASM boundaries, which would convert an abort into a recoverable error; however, this does not address the root cause and cannot prevent the abort in release builds where panic = "abort" overrides unwind semantics. Changing panic = "abort" to panic = "unwind" in the release profile would restore thread-level isolation but has binary-size and performance trade-offs and is a build-configuration change that requires recompilation.
Same weakness CWE-248 – Uncaught Exception
View allSame technique Denial Of Service
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-64179
GHSA-4hgp-59h5-gvrj