YAML from a request body, an upload or another tenant can be built to exhaust memory or the stack. oneyaml enforces resource limits on every read; for input you don’t control, use the stricter Limits::untrusted() profile.
use oneyaml::{Limits, Settings, Value, code}; fn main() { let settings = Settings::new().limits(Limits::untrusted()); // A normal request body reads as usual. let ok: Result<Value, _> = oneyaml::from_str_with("name: web\nports: [80]\n", &settings); println!("ok: {}", ok.is_ok()); // "Billion laughs": nine levels of aliases, each repeated ten times. let mut bomb = String::from("l0: &l0 [x, x, x, x, x, x, x, x, x, x]\n"); for i in 1..9 { let refs = vec![format!("*l{}", i - 1); 10].join(", "); bomb.push_str(&format!("l{i}: &l{i} [{refs}]\n")); } let err = oneyaml::from_str_with::<Value>(&bomb, &settings).unwrap_err(); println!("refused: {} ({})", err.code() == code::ALIAS_BUDGET_EXCEEDED, err.code()); // Deep nesting is refused too, without overflowing the stack. let deep = "[".repeat(100_000); let err = oneyaml::from_str_with::<Value>(&deep, &settings).unwrap_err(); println!("deep: {}", err.code()); }
ok: true refused: true (Y2004) deep: Y2000
Good to know
- There are three profiles:
Limits::default()(for configuration files),Limits::untrusted()andLimits::unlimited(), the only one without alias budgets. - They bound nesting depth, alias expansion, document size, scalar length and document count. An alias whose whole expansion would exceed the budget is refused before it’s replayed, so the bomb above fails fast.
- Parsing is iterative, so deep nesting can’t overflow the stack, and no input makes oneyaml panic. The crate is fuzzed.
- The error’s help names the setting to raise. The values of each profile are on
Limits. - Compatibility presets such as
Compat::yaml_1_1()never change limits.
More in untrusted input.