GHSA-4hgp-59h5-gvrj
ratex-parser panics on `\verb` with a multibyte delimiter (UTF-8 byte-boundary slice)
Packages
| Ecosystem | Package | Affected | Fixed in |
|---|---|---|---|
| crates.io | ratex-parser | < 0.1.11 | 0.1.11 |
Details
As written in the advisory (Markdown source, shown as plain text).
### 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
<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 process
```
### Impact
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.
References
External links from the advisory. They lead to third-party sites.