{"record":{"id":"a908397dd7a855e7","repo":"ramensoftware/windhawk","slug":"value-contains-a-nul-character","errorCode":null,"errorMessage":"value contains a NUL character","messagePattern":"value contains a NUL character","errorType":"validation","errorClass":"SettingsError","httpStatus":null,"severity":"error","filePath":"src/windhawk-core/windows/src/ini.rs","lineNumber":187,"sourceCode":"\nimpl SettingsTree for IniTree {\n    fn get_string(&self, name: &str) -> Result<Option<String>, SettingsError> {\n        self.check_name(\"get\", name)?;\n        get_profile_string(&self.file, &self.section, name)\n            .map_err(|os| self.err(\"get\", os, \"GetPrivateProfileString\"))\n    }\n\n    fn set_string(&mut self, name: &str, value: &str) -> Result<(), SettingsError> {\n        if let Some(why) = unrepresentable_name(name) {\n            return Err(self.err(\"set\", 0, &format!(\"value name {name:?} {why}\")));\n        }\n        // `WritePrivateProfileStringW` takes the value as a NUL-terminated\n        // string, so an embedded NUL ends it: everything after it is dropped and\n        // the call still reports success. `escape_ini_value` cannot rescue that\n        // (the INI line format has no encoding for a NUL), so refuse the write\n        // rather than store a silently truncated value.\n        if value.contains('\\0') {\n            return Err(self.err(\"set\", 0, \"value contains a NUL character\"));\n        }\n        // An INI entry ends at the line break, so a value carrying one cannot\n        // be stored either: it would read back cut at the break, with the rest\n        // of it parsed as further lines of a file that also holds the mod's\n        // `[Mod]` config. Refused for the same reason as a NUL - the registry\n        // backend stores both halves of such a value faithfully, and a write\n        // that cannot keep the value is better refused than reported as done.\n        if value.contains(['\\r', '\\n']) {\n            return Err(self.err(\"set\", 0, \"value contains a line break\"));\n        }\n        let escaped = escape_ini_value(value);\n        write_profile(&self.file, &self.section, Some(name), Some(&escaped))\n    }\n\n    fn get_int(&self, name: &str) -> Result<Option<i32>, SettingsError> {\n        Ok(self.get_string(name)?.map(|s| parse_c_int(&s)))\n    }\n","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk-core/windows/src/ini.rs#L169-L205","documentation":"IniTree::set_string rejects values containing an embedded NUL ('\\0'). WritePrivateProfileStringW takes a NUL-terminated string, so everything after an embedded NUL would be silently dropped while the call still reports success, and the INI line format offers no encoding to escape a NUL. The write is refused rather than storing a silently truncated value.","triggerScenarios":"Calling set_string (or set_int/set_binary which delegate to it) with a value argument containing '\\0' — typically binary data or strings copied from C buffers passed as text.","commonSituations":"Storing binary blobs as strings; values assembled from fixed-size buffers without trimming at the terminator; serializing structures that embed NUL padding into settings.","solutions":["Strip the value at the first NUL before writing: value.split('\\0').next().unwrap_or(\"\") — if truncation is acceptable.","Store binary data through a binary-safe mechanism (e.g. base64-encode the value, or use set_binary/registry backend as appropriate).","Validate/normalize inputs at the boundary so NUL-containing values never reach the settings API.","Handle the SettingsError explicitly and inform the user the value cannot be stored in INI format."],"exampleFix":"// before\nlet raw = c_buffer_to_string(); // \"abc\\0def\"\nsettings.set_string(\"blob\", raw)?; // Err: value contains a NUL character\n\n// after\nlet clean = raw.split('\\0').next().unwrap_or_default();\nsettings.set_string(\"blob\", &base64_encode(clean.as_bytes()))?;","handlingStrategy":"validation","validationCode":"fn value_is_ini_safe(v: &str) -> bool { !v.contains('\\0') && !v.contains('\\n') && !v.contains('\\r') }\nassert!(value_is_ini_safe(&value), \"value not storable in INI\");","typeGuard":"fn ini_safe_value(v: &str) -> Option<&str> {\n    (!v.contains('\\0') && !v.contains('\\n') && !v.contains('\\r')).then_some(v)\n}","tryCatchPattern":"match ini.set_string(\"key\", &value) {\n    Err(e) if e.to_string().contains(\"NUL\") => {\n        let encoded = base64_encode(value.as_bytes());\n        ini.set_string(\"key\", &encoded)?;\n    },\n    r => r?,\n}","preventionTips":["Base64-encode or otherwise encode any value that may contain non-text bytes before storing.","Never pass C buffers or binary blobs directly as INI string values.","Validate values for NUL/newlines before every write, mirroring the library's checks.","Keep values destined for INI strictly textual; use a binary-capable store for anything else."],"tags":["ini","nul-character","validation","windows","value-truncation"],"backgroundTag":"invalid-argument-value","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}