astrid-runtime/astrid · error

WinFsp launch lease does not match the kernel manifest

Error message

WinFsp launch lease does not match the kernel manifest

What it means

This is an anti-tampering check: the lease manifest persisted on disk (lease.json, decoded into StorageMountLeaseV1) must exactly equal the lease embedded in the launch descriptor passed to the service. If they differ, the launch is aborted because the parent is presenting a lease that was not the one admitted to the private resource directory — indicating stale files, concurrent re-issues, or attempted privilege escalation.

Source

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

        || lease.callback_path != lease.resource_path.join("control.endpoint")
    {
        bail!("WinFsp lease paths are malformed");
    }
    platform_fs::validate_private_directory(&lease.resource_path)
        .context("validate private WinFsp lease resource")?;
    platform_fs::verify_no_redirects(&lease.resource_path)
        .context("reject redirected WinFsp lease resource")?;
    let manifest_path = lease.resource_path.join("lease.json");
    platform_fs::validate_private_file(&manifest_path)
        .context("validate private WinFsp lease manifest")?;
    let manifest = std::fs::read(&manifest_path).context("read WinFsp lease manifest")?;
    if manifest.len() > 64 * 1024 {
        bail!("WinFsp lease manifest exceeds the bounded size");
    }
    let admitted: StorageMountLeaseV1 =
        serde_json::from_slice(&manifest).context("decode WinFsp lease manifest")?;
    if admitted != *lease {
        bail!("WinFsp launch lease does not match the kernel manifest");
    }
    if !launch.mountpoint.is_absolute()
        || launch
            .mountpoint
            .components()
            .any(|component| matches!(component, std::path::Component::ParentDir))
    {
        bail!("WinFsp service mountpoint is malformed");
    }
    if is_public_mountpoint(&launch.mountpoint)
        || launch.mountpoint.parent().is_none()
        || launch.mountpoint == lease.resource_path
        || launch.mountpoint.starts_with(&lease.resource_path)
        || lease.resource_path.starts_with(&launch.mountpoint)
    {
        bail!("WinFsp service mountpoint is public or overlaps the lease resource");
    }
    platform_fs::validate_private_directory(&launch.mountpoint)

View on GitHub (pinned to affd8760f4)

Solutions

  1. Regenerate the launch descriptor and the lease.json together in one atomic step from the same StorageMountLeaseV1 value.
  2. Ensure each mount uses a unique resource_path so concurrent leases never overwrite each other's lease.json.
  3. If the lease was renewed, re-issue the launch with the updated lease rather than launching with the pre-renewal snapshot.
  4. Verify the issuer writes lease.json after finalizing all fields (token, expiry) so the on-disk manifest matches what goes into the descriptor.

Example fix

// before
write_lease_json(&dir, &old_lease);
spawn_service(launch_with(new_lease));
// after
write_lease_json(&dir, &lease);
spawn_service(launch_with(lease.clone()));
Defensive patterns

Strategy: validation

Validate before calling

let on_disk: StorageMountLeaseV1 = serde_json::from_slice(&std::fs::read(resource_path.join("lease.json"))?)?;
if on_disk != launch.lease {
    return Err(anyhow!("launch lease differs from on-disk lease.json — regenerate both together"));
}

Try / catch

match validate_launch(&launch) {
    Err(e) if e.to_string().contains("does not match the kernel manifest") => {
        let fresh = reissue_lease_for(&launch.lease.mount_id)?;
        start_with(fresh)
    }
    other => other.map(|_| ()),
}

Prevention

When it happens

Trigger: service_main -> validate_service_launch where serde_json parses lease.json successfully but admitted != *launch.lease — any differing field (mount_id, lease_token, paths, access, expires_at_epoch_secs, schema) triggers it.

Common situations: Reusing a launch descriptor from a previous mount while a new lease.json was issued for the same directory; two mounts sharing/overwriting the same resource_path; the issuer renewed the lease (new expiry/token) after the launch descriptor snapshot was taken; hand-editing one side but not the other; comparing structurally-equal but textually different JSON is fine (serde equality), so a mismatch is always a real field difference.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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