ratex-parser panics on `\verb` with a multibyte delimiter (UTF-8 byte-boundary slice)
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
예측 데이터 없음
실측 악용 기록 없음
계획된 패치 주기 내 조치(60일 이내)
CVSS 벡터 · 메트릭
CVSS 벡터 정보 없음
상세 설명
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:
1if let Some(stripped) = text.strip_prefix("\\verb") { // parser.rs:901 2 self.consume(); 3 let arg = stripped.to_string(); // e.g. "éxé" 4 let star = arg.starts_with('*'); 5 let arg = if star { &arg[1..] } else { &arg }; // parser.rs:905 (also byte-sliced) 6 if arg.len() < 2 { // byte length 7 return Err(ParseError::new("\\verb assertion failed", Some(&nucleus))); 8 } 9 let body = arg[1..arg.len() - 1].to_string(); // parser.rs:910 <-- PANIC on multibyte delimiter10 ...11}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" /> 1$ printf '\\verb\xc3\xa9x\xc3\xa9\n' | ./target/release/parse 2thread 'main' panicked at crates/ratex-parser/src/parser.rs:910:27: 3start byte index 1 is not a char boundary; it is inside 'é' (bytes 0..2 of string) 4Aborted (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:
1let chars: Vec<char> = arg.chars().collect(); 2if chars.len() < 2 { return Err(ParseError::new("\\verb assertion failed", Some(&nucleus))); } 3let 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.
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.