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…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…