Skip to content

Silent data loss: non-standard bool values (i32 != 0/1) coerced to true #289

@r0mSec

Description

@r0mSec

Bug description

When parsing EVTX binary data, fields typed as BoolType are read as i32 in src/macros.rs (lines 126-140). If the raw value is 0, it maps to false; if 1, to true. But for any other i32 value,
the macro silently coerces it to true and logs a warning:

Ok(number) => {
    log::warn!(
        "{:} is an unknown value for bool, coercing to `true`",
        number
    );
    Ok(true)  // original value is lost
}

This causes silent data loss. The original value (e.g., 320060673, a NTFS attributes bitmask) is permanently discarded and replaced with true.

Real-world impact

When parsing Windows Event Logs containing NTFS-related events, many fields typed as bool in the EVTX binary schema actually hold bitmask values (file attributes, flags, etc.). These are meaningful data —
not just true/false toggles.

Example warning output during parsing:

[WARN] -1357602303 is an unknown value for bool, coercing to `true`
[WARN] 320051713 is an unknown value for bool, coercing to `true`
[WARN] 320060673 is an unknown value for bool, coercing to `true`
[WARN] 672368641 is an unknown value for bool, coercing to `true`

Each of these represents a distinct value that is irreversibly lost.

Existing test coverage

The sample samples/sample-with-irregular-bool-values.evtx and test test_parses_sample_with_irregular_boolean_values (3028 records) already exercise this code path, but the test only checks that parsing
doesn't error — it doesn't verify that values are preserved.

Suggested fix

1. Change BoolType(bool) to BoolType(i32) in src/binxml/value_variant.rs

// Before
BoolType(bool),
BoolArrayType(Vec<bool>),

// After
BoolType(i32),
BoolArrayType(Vec<i32>),

2. Simplify the macro in src/macros.rs

// Before (coerces to true)
($cursor: ident, bool) => {{
    let bool_value = try_read!($cursor, i32);
    match bool_value {
        Ok(0) => Ok(false),
        Ok(1) => Ok(true),
        Ok(number) => { log::warn!("..."); Ok(true) }
        Err(e) => Err(e),
    }
}};

// After (preserves raw value)
($cursor: ident, bool) => {{
    try_read!($cursor, i32)
}};

3. Update serialization to be backward-compatible for 0/1 values

JSON (From<BinXmlValue> for serde_json::Value):

BinXmlValue::BoolType(n) => match n {
    0 => json!(false),
    1 => json!(true),
    other => json!(other),  // preserve raw value
},

XML/String (as_cow_str()):

BinXmlValue::BoolType(n) => Cow::Owned(match n {
    0 => "false".to_string(),
    1 => "true".to_string(),
    other => other.to_string(),  // preserve raw value
}),

This ensures:

  • Standard bool values (0/1) produce identical JSON/XML output as before
  • Non-standard values emit the raw integer instead of being silently lost
  • The existing test suite should continue to pass (no parsing errors)

Environment

  • evtx crate version: 0.8.5 (also checked changelog up to 0.11.1 — issue appears unfixed)
  • Context: forensic EVTX ingestion pipeline

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