syn icon indicating copy to clipboard operation
syn copied to clipboard

Found slow units on syn::parse_str using cargofuzz

Open Yunzez opened this issue 6 months ago • 2 comments

Some slow parsing behaviors observed during fuzz testing. Not a crash or a critical bug, but addressing these cases can help improve the robustness and efficiency of the parsing logic.


Test on version: v2.0.100


Harness

#![no_main]
extern crate libfuzzer_sys;
extern crate syn;

use libfuzzer_sys::fuzz_target;
use syn::spanned::Spanned;
use syn::parse_str;

fuzz_target!(|data: &str| {
    if let Ok(value) = parse_str::<syn::Expr>(data) {
        let _ = value.span();
    }
});

Problematic input 1

tb|kP--&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kq-&tb|kP--&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kq-&t*..t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kq-&tb|kP--&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4P
|*..4|kP--&t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kq-&t*..t*..4|qkHPk=1%0|s

Execution Output:

Executed fuzz/artifacts_cluster/syn_span2_fuzz/slow-unit-1da6724f047297669763f0d258c9b27c6b92c037 in 162148 ms

Problematic input 2

tb|kP--&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kq-&tb|kP--&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kqt*..4P
|*..4|q-&t*..4P
|kq-&tb|kP--&t*..4|kP--&t&t*..4|kP--&t*..4|q-&t*..4P
|*..4|q-&t*..4P
|kq-&t*..-&tkHPk=1%0|t*..-&tkHPk=1%0|s

Execution output:

Executed fuzz/artifacts_cluster/syn_span2_fuzz/slow-unit-e02f19b7534f6d394c94700f5038d5f0f3cfa945 in 30809 ms

Environment:

  • OS: macOS (aarch64-apple-darwin)
  • Rust Version: rustc 1.83.0-nightly

The pattern repetition and inclusion of non-printable characters likely cause the syn parser to repeatedly attempt matching. The combination of nested patterns and malformed syntax may be triggering deep recursion or inefficient parsing paths.

possibly originated from this recursion: src/macros.rs

generate_to_tokens!(
    ($($arms)* $(#[cfg $cfg_attr])* $name::$variant(_e) => _e.to_tokens($tokens),)

Yunzez avatar May 08 '25 23:05 Yunzez

Thanks. Formatted and without libfuzzer-sys dependency:

// [dependencies]
// syn = { version = "2", default-features = false, features = ["full", "parsing", "printing"] }
// quote = { version = "1", default-features = false }

const INPUT: &str = r"
0 | 0 - -&0 * ..0 | 0 - -&0 & 0 * ..0 | 0 - -&0 * ..0 | 0 - &0 * ..0
| *..0 | 0 - &0 * ..0
| 0 - &0 | 0 - -&0 * ..0 | 0 - -&0 & 0 * ..0 | 0 - -&0 * ..0 | 0 - &0 * ..0
| *..0 | 0 - &0 * ..0
| 0 - &0 * ..0 * ..0 | 0 - &0 * ..0
| *..0 | 0 - &0 * ..0
| 0 - &0 | 0 - -&0 * ..0 | 0 - -&0 & 0 * ..0 | 0 - -&0 * ..0 | 0 - &0 * ..0
| *..0 | 0 - -&0 * ..0 | 0 - &0 * ..0
| *..0 | 0 - &0 * ..0
| 0 - &0 * ..0 * ..0 | 0 = 0 % 0 | 0
";

fn main() {
    let expr = syn::parse_str::<syn::Expr>(INPUT).unwrap();
    let tokens = quote::ToTokens::to_token_stream(&expr);
    println!("{}", tokens);
}

dtolnay avatar Jun 10 '25 02:06 dtolnay

Minimized further:

const INPUT: &str = r"
0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0
| ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 | ..0 = 0
";

dtolnay avatar Jun 10 '25 03:06 dtolnay