GHSA-4HGP-59H5-GVRJ

Vulnerability from github – Published: 2026-07-07 23:39 – Updated: 2026-07-07 23:39
VLAI
Summary
ratex-parser panics on `\verb` with a multibyte delimiter (UTF-8 byte-boundary slice)
Details

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

image

$ 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:

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.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "ratex-parser"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.11"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-53530"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1285",
      "CWE-248",
      "CWE-400"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-07T23:39:12Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nThe public parser entrypoint `ratex_parser::parse(\u0026str)` panics on the **9-byte** input `\\verb\u00e9x\u00e9` (i.e. `\\verb` followed by the non-ASCII delimiter `\u00e9`). 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 *\u201cbyte index 1 is not a char boundary\u201d*. Because RaTeX\u2019s release profile sets `panic = \"abort\"` (`Cargo.toml:48`), the panic aborts the **entire process** \u2014 not just the current request/thread \u2014 making this a hard denial of service for any service that renders untrusted LaTeX.\n\n\n\n### Details\n\n\n## Affected code\n\n`crates/ratex-parser/src/parser.rs`, `parse_symbol_inner`:\n\n```rust\nif let Some(stripped) = text.strip_prefix(\"\\\\verb\") {       // parser.rs:901\n    self.consume();\n    let arg = stripped.to_string();                         // e.g. \"\u00e9x\u00e9\"\n    let star = arg.starts_with(\u0027*\u0027);\n    let arg = if star { \u0026arg[1..] } else { \u0026arg };          // parser.rs:905  (also byte-sliced)\n    if arg.len() \u003c 2 {                                      // byte length\n        return Err(ParseError::new(\"\\\\verb assertion failed\", Some(\u0026nucleus)));\n    }\n    let body = arg[1..arg.len() - 1].to_string();           // parser.rs:910  \u003c-- PANIC on multibyte delimiter\n    ...\n}\n```\n\nFor input `\\verb\u00e9x\u00e9`: `arg = \"\u00e9x\u00e9\"`, where `\u00e9` = `U+00E9` (bytes `C3 A9`). `arg.len()` is the **byte** length (5), the `\u003c 2` guard passes, and `arg[1..4]` starts at byte index 1 \u2014 inside the first `\u00e9` (bytes 0..2) \u2014 so the slice panics. The lexer groups `\\verb\u003cdelim\u003e\u2026\u003cdelim\u003e` correctly with char semantics (`lexer.rs` `lex_verb`); only the parser mishandles it.\n\n### PoC\n\n\u003cimg width=\"1109\" height=\"205\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cd4bc6ae-23dd-458f-826c-6ce4e85c7005\" /\u003e\n\n\n```\n$ printf \u0027\\\\verb\\xc3\\xa9x\\xc3\\xa9\\n\u0027 | ./target/release/parse\nthread \u0027main\u0027 panicked at crates/ratex-parser/src/parser.rs:910:27:\nstart byte index 1 is not a char boundary; it is inside \u0027\u00e9\u0027 (bytes 0..2 of string)\nAborted (core dumped)            # exit 134 \u2014 panic=abort kills the whole process\n```\n\n### Impact\n\nAny application that renders untrusted LaTeX through RaTeX (web \u201crender this math\u201d 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).\n\n## Remediation\n\nSlice by character boundaries instead of byte indices, mirroring the UTF-8-correct logic the lexer already uses. For example:\n\n```rust\nlet chars: Vec\u003cchar\u003e = arg.chars().collect();\nif chars.len() \u003c 2 { return Err(ParseError::new(\"\\\\verb assertion failed\", Some(\u0026nucleus))); }\nlet body: String = chars[1..chars.len() - 1].iter().collect();\n```\n\n(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 \u2014 but the byte-slice fix is the direct correction.",
  "id": "GHSA-4hgp-59h5-gvrj",
  "modified": "2026-07-07T23:39:12Z",
  "published": "2026-07-07T23:39:12Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/erweixin/RaTeX/security/advisories/GHSA-4hgp-59h5-gvrj"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/erweixin/RaTeX"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "ratex-parser panics on `\\verb` with a multibyte delimiter (UTF-8 byte-boundary slice)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…