oldj/SwitchHosts · error · std::io::Error
InvalidData
InvalidData
Error message
payload too large: {len} bytes What it means
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.
Source
Thrown at src-tauri/src/helper_proto/mod.rs:165
/// The system hosts file the daemon is allowed to write. A compile-time
/// constant: the IPC protocol carries NO path, so a client cannot
/// redirect the privileged write anywhere else.
#[cfg(unix)]
pub const SYSTEM_HOSTS_PATH: &str = "/etc/hosts";
/// Monotonic counter that makes each privileged write's temp file name
/// unique, so two concurrent writes within the daemon never share (and
/// clobber) one temp file.
#[cfg(unix)]
static TMP_SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
/// Validate `content` and atomically overwrite the system hosts file as
/// `root:wheel` mode `0644`. Intended to run inside the privileged
/// daemon (which is `root`). Returns `InvalidData` if validation fails.
#[cfg(unix)]
pub fn write_system_hosts(content: &[u8]) -> std::io::Result<()> {
validate_payload(content)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
write_atomic(
std::path::Path::new(SYSTEM_HOSTS_PATH),
content,
Some((0, 0)),
)
}
/// Atomic write core, factored out so it can be unit-tested
/// unprivileged: write to a sibling temp file, `fsync`, set mode `0644`,
/// optionally `chown` to `owner` (`Some((uid, gid))`; `None` skips the
/// chown so tests can run without root), then `rename(2)` over the
/// target. `rename` within the same directory is atomic, so a reader of
/// the hosts file always sees either the old or the new content, never a
/// partial write. On any failure the temp file is removed best-effort.
#[cfg(unix)]
fn write_atomic(
target: &std::path::Path,
content: &[u8],View on GitHub (pinned to 6ecea88d92)
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.
Example fix
// before
let res = helper_proto::write_system_hosts(&content); // may fail InvalidData at 5 MiB+
// after
if content.len() > helper_proto::MAX_PAYLOAD_BYTES {
anyhow::bail!("hosts payload {} bytes exceeds {} byte cap",
content.len(), helper_proto::MAX_PAYLOAD_BYTES);
}
helper_proto::write_system_hosts(&content)?; Defensive patterns
Strategy: validation
Validate before calling
use crate::helper_proto::{validate_payload, MAX_PAYLOAD_BYTES};
// Client-side pre-check before invoking the privileged write:
if content.len() > MAX_PAYLOAD_BYTES {
return Err(format!("hosts payload too large: {} bytes (cap {})",
content.len(), MAX_PAYLOAD_BYTES));
}
// Full parity check with the daemon's own validator:
validate_payload(&content)?; // catches TooLarge, ContainsNul, NotUtf8 early Type guard
// Narrow an io::Error back to this validation failure before deciding what to do
fn is_payload_too_large(err: &std::io::Error) -> bool {
err.kind() == std::io::ErrorKind::InvalidData
&& err.to_string().contains("payload too large")
} Try / catch
// Rust: map InvalidData from write_system_hosts distinctly from real IO failures
match helper_proto::write_system_hosts(&content) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
// payload rejected by validate_payload: fix/dedup content, never retry as-is
return Err(anyhow::anyhow!("hosts payload rejected: {e}"));
}
Err(e) => {
// genuine write failure (perms, disk) — may be retried after fixing cause
return Err(e.into());
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of oldj/SwitchHosts@6ecea88d92 (2026-08-16).
Data as JSON: /api/errors/a84ceb2fffd1d325.
Report an issue: GitHub.