ramensoftware/windhawk · error · SettingsError

value name contains a NUL

Error message

value name {name:?} contains a NUL

What it means

The registry settings backend refuses value names that contain an embedded NUL character, because the Windows registry API uses NUL-terminated wide strings for names — an embedded NUL would silently truncate the name. The check runs before key-state checks so the same name always fails identically, matching the INI backend's behavior.

Solutions

  1. Trim at the first NUL before using the name: name.split('\0').next().unwrap_or("").
  2. Sanitize or reject the name at the input boundary with an explicit validation error.
  3. Fix the source producing the name (e.g. convert UTF-16 with proper truncation instead of raw fixed buffers).

Example fix

// before
let name = String::from_utf16_lossy(&raw_buf); // may contain \0
settings.set_string(&name, value)?;
// after
let name = String::from_utf16_lossy(&raw_buf);
let name = name.split('\0').next().unwrap_or("");
settings.set_string(name, value)?;
Defensive patterns

Strategy: validation

Validate before calling

fn assert_name_ok(name: &str) -> Result<(), String> {
    if name.contains('\0') { Err("value name contains NUL".into()) } else { Ok(()) }
}

Type guard

fn is_clean_name(name: &str) -> bool { !name.contains('\0') && !name.is_empty() }

Try / catch

match settings.remove(&name) {
    Err(e) if e.message().contains("NUL") => eprintln!("bad value name: {name:?}"),
    r => r?,
}

Prevention

When it happens

Trigger: Calling query_raw, set_raw, or remove (or any get/set/remove built on them) with a name string containing '\0', typically a name sliced from a fixed-size buffer or constructed from raw bytes.

Common situations: Building value names from C-style buffers that keep trailing/embedded NULs; interop code passing byte arrays instead of trimmed strings; migrating names from a format that allows NULs.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12). Data as JSON: /api/errors/0f24502f23dbd3c9. Report an issue: GitHub.

Appendix: source

Thrown at src/windhawk-core/windows/src/registry.rs:391

impl RegistryTree {
    fn hkey(&self) -> Option<HKEY> {
        self.key.as_ref().map(|k| k.0)
    }

    fn err(&self, op: &'static str, rc: u32, what: &str) -> SettingsError {
        registry_err(op, self.location.clone(), rc, what)
    }

    /// A value name reaches Win32 as a `PCWSTR`, which ends at its first NUL, so
    /// a name carrying one would read, write, or delete a DIFFERENT value - the
    /// prefix - with the call reporting success. Refuse it on every name-taking
    /// operation, as the INI backend refuses such a name and as `set_string`
    /// refuses a NUL in the value, so the same request fails the same way in
    /// both storage modes. Checked before the key state, so one name gets one
    /// answer whether or not the tree is open for write.
    fn check_name(&self, op: &'static str, name: &str) -> Result<(), SettingsError> {
        if name.contains('\0') {
            return Err(self.err(op, 0, &format!("value name {name:?} contains a NUL")));
        }
        Ok(())
    }

    /// Read a value's raw type + bytes: a size-query call (null data buffer)
    /// for the type and length, then one sized read. There is no
    /// `ERROR_MORE_DATA` grow loop - the size query returns the exact length,
    /// so the single follow-up read always fits.
    fn query_raw(&self, name: &str) -> Result<Option<(u32, Vec<u8>)>, SettingsError> {
        self.check_name("get", name)?;
        let Some(hkey) = self.hkey() else {
            return Ok(None);
        };
        let name_w = to_wide(name);
        let mut size: u32 = 0;
        let mut value_type: u32 = 0;
        // First call: type + size only.
        // SAFETY: name_w is NUL-terminated; out params are valid; lpData null

View on GitHub (pinned to 61d99ed8e1)