Skip to content

grass(0.13.4) Possible Panic via UTF-8 boundary panic in parse-error reporting #116

@Yunzez

Description

@Yunzez

When I fuzz this crate using cargo fuzz, it seems when the parser encounters a syntax error, it constructs a diagnostic via grass_compiler::raw_to_parse_error, which hands the byte-offset error span to the codemap crate for line/column resolution. If the error span boundary falls inside a multi-byte UTF-8 character, codemap::File::find_line_col attempts &source[start..end] on a non-char-boundary index and Rust panics with "byte index N is not a char boundary; it is inside ...".

The panic propagates up through the public grass::from_string API and aborts the process.

Affected versions

grass <= 0.13.4 (all released versions; latest on crates.io as of 2026-06-02).

Both the binary CLI (grass) and library API (grass::from_string) are affected.

Proof of concept

:v(url(:1B\\\\\cb16 2BqƮx\1\ٯ&<NUL>{}

Minimal Rust program:

fn main() {
    // 35-byte SCSS source (from SBOMFuzz Round-3 corpus, byte-for-byte minimal).
    // Contains:
    //   - Multi-byte UTF-8 chars: U+01AE (Ʈ, 2 bytes) and U+066F (ٯ, 2 bytes)
    //   - A NUL byte (0x00)
    //   - A CSS hex-escape `\cb16` plus other syntactically-invalid constructs.
    let repro: &[u8] = &[
        0x3a, 0x76, 0x28, 0x75, 0x72, 0x6c, 0x28, 0x3a, 0x31, 0x42, 0x5c, 0x5c,
        0x5c, 0x5c, 0x5c, 0x63, 0x62, 0x31, 0x36, 0x20, 0x32, 0x42, 0x71, 0xc6,
        0xae, 0x78, 0x5c, 0x31, 0x5c, 0xd9, 0xaf, 0x26, 0x00, 0x7b, 0x7d,
    ];
    let source = std::str::from_utf8(repro).expect("repro is valid UTF-8");
    println!("compiling {} bytes of SCSS...", source.len());

    // We expect Err here — instead the compiler aborts the process.
    let result = grass::from_string(source.to_string(), &grass::Options::default());

    println!("UNEXPECTED: compiler did not panic. result = {:?}", result);
}

Output:

compiling 35 bytes of SCSS...

thread 'main' (639240) panicked at /home/yzzhao3/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/codemap-0.1.3/src/lib.rs:257:50:
byte index 30 is not a char boundary; it is inside 'ٯ' (bytes 29..31) of `:v(url(:1B\\\\\cb16 2BqƮx\1\ٯ&{}`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions