Skip to content

Latest commit

 

History

History
257 lines (180 loc) · 6.14 KB

File metadata and controls

257 lines (180 loc) · 6.14 KB

Security Guide for rust-yaml

Overview

rust-yaml implements comprehensive security measures to protect against common YAML parsing vulnerabilities, including the billion laughs attack, resource exhaustion, and malformed document attacks.

Built-in Protection

1. Resource Limits

rust-yaml enforces configurable resource limits to prevent resource exhaustion attacks:

  • Max Depth: Limits nesting depth of collections (default: 1000)
  • Max Anchors: Limits number of anchors per document (default: 10,000)
  • Max Document Size: Limits total document size in bytes (default: 100MB)
  • Max String Length: Limits individual string length (default: 10MB)
  • Max Alias Depth: Limits alias expansion depth (default: 100)
  • Max Collection Size: Limits items in a single collection (default: 1,000,000)
  • Max Complexity Score: Limits overall document complexity (default: 1,000,000)

2. Protection Against Billion Laughs Attack

The billion laughs attack (exponential entity expansion) is prevented through:

  • Alias expansion depth tracking
  • Cyclic reference detection
  • Complexity scoring for nested structures
  • Collection size limits

Example of prevented attack:

# This would expand exponentially without protection
a: &a ["lol", "lol", "lol", "lol", "lol", "lol", "lol", "lol", "lol"]
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a]
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b]

# ... would expand to 9^n items

3. Cyclic Reference Detection

rust-yaml detects and prevents cyclic alias references:

# This cyclic reference is detected and rejected
a: &a
  b: *b
b: &b
  a: *a

Configuration Presets

Strict Mode (Untrusted Input)

use rust_yaml::{Yaml, YamlConfig, Limits};

let mut config = YamlConfig::default();
config.limits = Limits::strict();
let yaml = Yaml::with_config(config);

Strict limits:

  • Max depth: 50
  • Max anchors: 100
  • Max document size: 1MB
  • Max string length: 64KB
  • Max alias depth: 5
  • Max collection size: 10,000
  • Timeout: 5 seconds

Secure Mode

let config = YamlConfig::secure();
let yaml = Yaml::with_config(config);

Balanced security for production use with reasonable limits.

Permissive Mode (Trusted Input)

let mut config = YamlConfig::default();
config.limits = Limits::permissive();

Higher limits for trusted sources:

  • Max depth: 10,000
  • Max anchors: 100,000
  • Max document size: 1GB
  • Max string length: 100MB

Custom Limits

use rust_yaml::{Limits, YamlConfig};
use std::time::Duration;

let mut limits = Limits::default();
limits.max_depth = 100;
limits.max_anchors = 500;
limits.max_document_size = 5 * 1024 * 1024; // 5MB
limits.timeout = Some(Duration::from_secs(10));

let mut config = YamlConfig::default();
config.limits = limits;

Best Practices

1. Always Use Limits for Untrusted Input

Never parse untrusted YAML without resource limits:

// BAD - No protection
let yaml = Yaml::new();
let result = yaml.load_str(untrusted_input); // Vulnerable!

// GOOD - Protected parsing
let config = YamlConfig::secure();
let yaml = Yaml::with_config(config);
let result = yaml.load_str(untrusted_input); // Protected

2. Choose Appropriate Limits

Select limits based on your use case:

  • Configuration files: Use strict() limits
  • User-generated content: Use strict() with custom timeout
  • Internal data: Use default() or permissive()
  • Large datasets: Use custom limits with appropriate sizes

3. Handle Errors Gracefully

Resource limit errors should be handled appropriately:

match yaml.load_str(input) {
    Ok(value) => process_value(value),
    Err(e) if e.to_string().contains("limit") => {
        // Log security event
        log::warn!("Resource limit exceeded: {}", e);
        // Return safe error to user
        return Err("Document too complex");
    }
    Err(e) => {
        // Handle other parsing errors
        return Err(format!("Invalid YAML: {}", e));
    }
}

4. Monitor Resource Usage

Use the ResourceTracker to monitor actual usage:

let tracker = ResourceTracker::new();
// After parsing, check statistics
let stats = tracker.stats();
log::debug!("Max depth: {}, Anchors: {}, Complexity: {}",
    stats.max_depth, stats.anchor_count, stats.complexity_score);

5. Validate Content After Parsing

Even with security limits, validate parsed content:

let value = yaml.load_str(input)?;

// Validate expected structure
if !validate_schema(&value) {
    return Err("Invalid document structure");
}

// Sanitize values if needed
let sanitized = sanitize_values(value);

Common Attack Vectors

1. Exponential Expansion

  • Attack: Nested aliases causing exponential growth
  • Protection: Alias depth limits, complexity scoring

2. Deep Nesting

  • Attack: Deeply nested structures causing stack overflow
  • Protection: Max depth limits

3. Large Collections

  • Attack: Huge arrays/maps consuming memory
  • Protection: Collection size limits

4. Long Strings

  • Attack: Multi-gigabyte strings
  • Protection: String length limits

5. Anchor Bombs

  • Attack: Thousands of anchors slowing parsing
  • Protection: Anchor count limits

Testing Security

Run security tests to verify protection:

cargo test --test security_limits

Tests include:

  • A Billion laughs attack prevention
  • Cyclic reference detection
  • Resource limit enforcement
  • Timeout handling

Reporting Security Issues

If you discover a security vulnerability:

  1. Do not create a public issue
  2. Email security concerns to the maintainers
  3. Include:
    • Description of the vulnerability
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)

Updates and Patches

  • Security updates are released as patch versions
  • Critical vulnerabilities trigger immediate releases
  • Subscribe to security advisories for notifications

Additional Resources


Last updated: 2025-08-16