astrid-runtime/astrid · error

WinFsp daemon lease exceeds limit

Error message

WinFsp daemon lease exceeds limit

What it means

The WinFsp daemon reads its startup lease (JSON with lease, mountpoint, callback_path) from stdin, capped at MAX_LEASE_BYTES via take(MAX_LEASE_BYTES + 1). If the byte count read exceeds the cap, daemon_main bails rather than parsing an unbounded payload. This is a defensive bound against oversized or malformed IPC input from the parent process.

Source

Thrown at crates/astrid-storage-provider-winfsp/src/win.rs:57

const DAEMON_STOP_TIMEOUT: Duration = Duration::from_secs(30);
const MOUNTPOINT_READY_TIMEOUT: Duration = Duration::from_secs(10);
const MAX_LEASE_BYTES: u64 = 64 * 1024;

#[derive(serde::Deserialize, serde::Serialize)]
struct DaemonStart {
    lease: StorageMountLeaseV1,
    mountpoint: PathBuf,
}

pub(crate) fn daemon_main() -> Result<()> {
    let mut bytes = Vec::new();
    std::io::stdin()
        .lock()
        .take(MAX_LEASE_BYTES + 1)
        .read_to_end(&mut bytes)
        .context("read WinFsp daemon lease")?;
    if bytes.len() as u64 > MAX_LEASE_BYTES {
        bail!("WinFsp daemon lease exceeds limit");
    }
    let start: DaemonStart =
        serde_json::from_slice(&bytes).context("decode WinFsp daemon lease")?;
    let lease = start.lease;
    if (!start.mountpoint.is_absolute() && !is_drive_designator(&start.mountpoint))
        || !lease.callback_path.is_absolute()
    {
        bail!("WinFsp daemon lease contains a relative endpoint");
    }

    let runtime = Arc::new(
        tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .context("start WinFsp callback runtime")?,
    );
    let callback = CallbackFs::new(lease.clone(), Arc::clone(&runtime))
        .map_err(|failure| anyhow::anyhow!("build WinFsp callback filesystem: {failure:?}"))?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Shrink the lease payload the parent writes to stdin (trim large fields, keep only schema-required data)
  2. Ensure the parent writes exactly one bounded JSON document and closes stdin
  3. Verify parent and WinFsp crate versions match so lease layout is what daemon_main expects
  4. If the workload legitimately needs larger leases, raise MAX_LEASE_BYTES in the crate and rebuild both sides together

Example fix

// before (parent)
write_all(serde_json::to_vec(&lease_with_huge_token)?)
// after
lease.parent.token = compact_token(); // trim to the minimum needed
write_all(serde_json::to_vec(&lease)?)
Defensive patterns

Strategy: validation

Validate before calling

let bytes = serde_json::to_vec(&lease)?;
if bytes.len() as u64 > MAX_LEASE_BYTES {
    return Err(format!("lease payload {} bytes exceeds limit {}", bytes.len(), MAX_LEASE_BYTES));
}

Type guard

fn lease_within_limit(lease: &DaemonStart, max: u64) -> bool {
    serde_json::to_vec(lease).map(|b| (b.len() as u64) <= max).unwrap_or(false)
}

Try / catch

match daemon_err {
    Err(e) if e.to_string().contains("lease exceeds limit") => {
        eprintln!("shrink lease payload or raise MAX_LEASE_BYTES");
    }
    other => other?,
}

Prevention

When it happens

Trigger: The parent process (or any writer to the daemon's stdin) writes a lease document larger than MAX_LEASE_BYTES, e.g. a lease containing huge tokens, deeply nested or extraneous fields, or corrupted/non-JSON garbage of excessive length.

Common situations: Embedding very long control paths or auth tokens into the lease; a parent/child version mismatch where the child expects a leaner lease schema; a bug causing stdin to receive data beyond the lease (e.g. logging written to stdin).

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/f9159c5fba7be120. Report an issue: GitHub.