{"record":{"id":"a84ceb2fffd1d325","repo":"oldj/SwitchHosts","slug":"invaliddata","errorCode":"InvalidData","errorMessage":"payload too large: {len} bytes","messagePattern":"payload too large: (.+?) bytes","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/helper_proto/mod.rs","lineNumber":165,"sourceCode":"/// The system hosts file the daemon is allowed to write. A compile-time\n/// constant: the IPC protocol carries NO path, so a client cannot\n/// redirect the privileged write anywhere else.\n#[cfg(unix)]\npub const SYSTEM_HOSTS_PATH: &str = \"/etc/hosts\";\n\n/// Monotonic counter that makes each privileged write's temp file name\n/// unique, so two concurrent writes within the daemon never share (and\n/// clobber) one temp file.\n#[cfg(unix)]\nstatic TMP_SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);\n\n/// Validate `content` and atomically overwrite the system hosts file as\n/// `root:wheel` mode `0644`. Intended to run inside the privileged\n/// daemon (which is `root`). Returns `InvalidData` if validation fails.\n#[cfg(unix)]\npub fn write_system_hosts(content: &[u8]) -> std::io::Result<()> {\n    validate_payload(content)\n        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;\n    write_atomic(\n        std::path::Path::new(SYSTEM_HOSTS_PATH),\n        content,\n        Some((0, 0)),\n    )\n}\n\n/// Atomic write core, factored out so it can be unit-tested\n/// unprivileged: write to a sibling temp file, `fsync`, set mode `0644`,\n/// optionally `chown` to `owner` (`Some((uid, gid))`; `None` skips the\n/// chown so tests can run without root), then `rename(2)` over the\n/// target. `rename` within the same directory is atomic, so a reader of\n/// the hosts file always sees either the old or the new content, never a\n/// partial write. On any failure the temp file is removed best-effort.\n#[cfg(unix)]\nfn write_atomic(\n    target: &std::path::Path,\n    content: &[u8],","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/oldj/SwitchHosts/blob/6ecea88d9291e0b127cfe745921738a66829a1ba/src-tauri/src/helper_proto/mod.rs#L147-L183","documentation":"helper_proto::validate_payload (src-tauri/src/helper_proto/mod.rs:132) rejects any hosts payload larger than MAX_PAYLOAD_BYTES (5 * 1024 * 1024, defined at mod.rs:48) before the privileged daemon writes /etc/hosts. write_system_hosts maps ProtoError::TooLarge into std::io::Error with ErrorKind::InvalidData and the message 'payload too large: {len} bytes'. It is a cheap-to-run DoS guard on a deliberately dumb byte-sink daemon: the size check runs first, before NUL and UTF-8 scans.","triggerScenarios":"Calling helper_proto::write_system_hosts(content) (or sending the corresponding XPC/IPC write request from the app) with a payload whose bytes.len() > 5 MiB. Typical producers: merging large ad-block/tracker hosts blocklists into the final hosts content, a generation bug that duplicates entries, or accidentally feeding binary/non-hosts data into the payload.","commonSituations":"Users enabling very large remote hosts lists (common ad-block lists exceed 5 MB when combined); a loop in merge/apply code that appends entries repeatedly until the cap is exceeded; feed the daemon a file that isn't a hosts file (wrong path passed) so len balloons or content fails other checks after the size one.","solutions":["Check payload size before the privileged write: if content.len() > helper_proto::MAX_PAYLOAD_BYTES, split, trim, or refuse in the UI instead of sending to the daemon.","Shrink the generated hosts file: deduplicate hostnames/IP pairs and drop comments/blank lines before applying; most blocklists compress far below 5 MiB after dedup.","If 5 MiB is genuinely too small for your build, raise MAX_PAYLOAD_BYTES in src-tauri/src/helper_proto/mod.rs:48 and rebuild both the app and the swh_helper daemon (both sides must agree on the limit).","Audit how the payload is assembled if it unexpectedly exceeds the cap — an entry-duplication bug in the merge code is a common root cause."],"exampleFix":"// before\nlet res = helper_proto::write_system_hosts(&content); // may fail InvalidData at 5 MiB+\n\n// after\nif content.len() > helper_proto::MAX_PAYLOAD_BYTES {\n    anyhow::bail!(\"hosts payload {} bytes exceeds {} byte cap\",\n        content.len(), helper_proto::MAX_PAYLOAD_BYTES);\n}\nhelper_proto::write_system_hosts(&content)?;","handlingStrategy":"validation","validationCode":"use crate::helper_proto::{validate_payload, MAX_PAYLOAD_BYTES};\n\n// Client-side pre-check before invoking the privileged write:\nif content.len() > MAX_PAYLOAD_BYTES {\n    return Err(format!(\"hosts payload too large: {} bytes (cap {})\",\n        content.len(), MAX_PAYLOAD_BYTES));\n}\n// Full parity check with the daemon's own validator:\nvalidate_payload(&content)?; // catches TooLarge, ContainsNul, NotUtf8 early","typeGuard":"// Narrow an io::Error back to this validation failure before deciding what to do\nfn is_payload_too_large(err: &std::io::Error) -> bool {\n    err.kind() == std::io::ErrorKind::InvalidData\n        && err.to_string().contains(\"payload too large\")\n}","tryCatchPattern":"// Rust: map InvalidData from write_system_hosts distinctly from real IO failures\nmatch helper_proto::write_system_hosts(&content) {\n    Ok(()) => {}\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {\n        // payload rejected by validate_payload: fix/dedup content, never retry as-is\n        return Err(anyhow::anyhow!(\"hosts payload rejected: {e}\"));\n    }\n    Err(e) => {\n        // genuine write failure (perms, disk) — may be retried after fixing cause\n        return Err(e.into());\n    }\n}","preventionTips":["Always call validate_payload (or at least a len check against MAX_PAYLOAD_BYTES) in the app before sending bytes to the daemon.","Dedupe and minimize merged blocklists when building the final hosts content; log the final byte size on every apply.","Treat any InvalidData from write_system_hosts as a permanent payload problem — never blind-retry, the same bytes will fail again.","Keep both sides (app and swh_helper daemon) rebuilt together if the 5 MiB constant is ever changed."],"tags":["rust","input-validation","size-limit","hosts-file","privileged-daemon","ipc"],"backgroundTag":"payload-size-limit-exceeded","analyzedSha":"6ecea88d9291e0b127cfe745921738a66829a1ba","analyzedAt":"2026-08-16T21:20:57.238Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}