ramensoftware/windhawk · error · SettingsError
value contains a NUL character
Error message
value contains a NUL character
What it means
set_string refuses string values containing an embedded NUL, because REG_SZ is defined up to its first NUL: everything after it would be silently lost on read-back by this library, the C++ engine, and regedit alike. Refusing the write keeps INI and registry backends failing the same way instead of silently truncating.
Solutions
- Trim at the first NUL before storing: value.split('\0').next().unwrap_or("").
- Sanitize the input at the boundary (reject or strip NULs with an explicit validation error).
- If binary data with NULs must be stored, use set_binary (hex-encoded) instead of set_string.
- Fix the producer of the string to not include terminators (e.g. convert UTF-16 with correct length).
Example fix
// before
let s = String::from_utf16_lossy(&wbuf); // may embed \0
settings.set_string("val", &s)?; // Err
// after
let s = String::from_utf16_lossy(&wbuf);
let s = s.split('\0').next().unwrap_or("").to_string();
settings.set_string("val", &s)?; Defensive patterns
Strategy: validation
Validate before calling
fn assert_no_nul(v: &str) -> Result<(), String> {
if v.contains('\0') { Err("value contains NUL".into()) } else { Ok(()) }
} Type guard
fn is_nul_free(v: &str) -> bool { !v.contains('\0') } Try / catch
match settings.set_string("k", &v) {
Err(e) if e.message().contains("NUL") => eprintln!("strip NULs before storing"),
r => r?,
} Prevention
- Convert wide buffers with explicit lengths, not including terminators.
- Route binary data (which may contain NULs) to set_binary instead of set_string.
- Sanitize strings at system boundaries (interop, IPC, decryption).
- Test round-trips with strings containing control characters.
When it happens
Trigger: Calling set_string with a string containing '\0', usually from wide-string/UTF-16 conversions, fixed-size buffers converted to String without trimming, or concatenating raw bytes into a string.
Common situations: Interop code turning WCHAR buffers (with padding NULs) into Strings; values assembled from C strings with terminator included; decrypting/decompressing data that yields embedded NULs.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- value name contains a NUL
- value is too large for the registry
- Initial settings arrays must contain at least one template…
- Invalid object array schema definition.
- Unknown setting type for value
AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12).
Data as JSON: /api/errors/4ceb59e113cbde1c.
Report an issue: GitHub.
Appendix: source
Thrown at src/windhawk-core/windows/src/registry.rs:503
}
}
impl SettingsTree for RegistryTree {
fn get_string(&self, name: &str) -> Result<Option<String>, SettingsError> {
Ok(match self.query_raw(name)? {
Some((t, bytes)) if t == REG_SZ => Some(decode_sz(&bytes)),
_ => None,
})
}
fn set_string(&mut self, name: &str, value: &str) -> Result<(), SettingsError> {
// A REG_SZ is defined up to its first NUL, so a value carrying an
// embedded one reads back truncated - here (`decode_sz`), in the C++
// engine, and in regedit alike. Refuse it, as the INI backend does, so
// the same write fails the same way in both storage modes instead of
// losing the tail in one of them.
if value.contains('\0') {
return Err(self.err("set", 0, "value contains a NUL character"));
}
// REG_SZ includes the terminating NUL, matching the C++
// (wcslen+1)*sizeof(WCHAR) write.
let mut wide = to_wide(value);
let bytes: Vec<u8> = std::mem::take(&mut wide)
.into_iter()
.flat_map(u16::to_le_bytes)
.collect();
self.set_raw(name, REG_SZ, &bytes)
}
fn get_int(&self, name: &str) -> Result<Option<i32>, SettingsError> {
Ok(match self.query_raw(name)? {
Some((t, bytes)) if t == REG_DWORD => decode_dword(&bytes),
_ => None,
})
}
View on GitHub (pinned to 61d99ed8e1)