{"record":{"id":"4dce419bc946c011","repo":"astrid-runtime/astrid","slug":"winfsp-callback-response-exceeds-limit","errorCode":null,"errorMessage":"WinFsp callback response exceeds limit","messagePattern":"WinFsp callback response exceeds limit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-winfsp/src/win.rs","lineNumber":359,"sourceCode":"        .context(\"connect WinFsp lease callback\")?;\n    let request = StorageFilesystemRequestV2 {\n        protocol_version: STORAGE_FILESYSTEM_PROTOCOL_V2,\n        request_id: format!(\"winfsp-service-{}\", launch.lease.mount_id),\n        lease_token: launch.lease.lease_token.clone(),\n        operation: StorageFilesystemOperationV2::Stat {\n            path: String::new(),\n        },\n    };\n    let bytes = serde_json::to_vec(&request).context(\"encode WinFsp callback probe\")?;\n    let length = u32::try_from(bytes.len()).context(\"WinFsp callback probe is too large\")?;\n    stream.write_all(&length.to_be_bytes()).await?;\n    stream.write_all(&bytes).await?;\n    stream.flush().await?;\n    let mut response_length = [0_u8; 4];\n    stream.read_exact(&mut response_length).await?;\n    let length = u32::from_be_bytes(response_length) as usize;\n    if length == 0 || length > SERVICE_MAX_CALLBACK_BYTES {\n        bail!(\"WinFsp callback response exceeds limit\");\n    }\n    let mut response_bytes = vec![0_u8; length];\n    stream.read_exact(&mut response_bytes).await?;\n    let response: StorageFilesystemResponseV2 =\n        serde_json::from_slice(&response_bytes).context(\"decode WinFsp callback probe\")?;\n    if response.protocol_version != STORAGE_FILESYSTEM_PROTOCOL_V2 {\n        bail!(\"WinFsp callback probe protocol mismatch\");\n    }\n    if response.request_id != request.request_id {\n        bail!(\"WinFsp callback probe correlation mismatch\");\n    }\n    match response.outcome {\n        StorageFilesystemOutcomeV2::Success(_) => Ok(()),\n        StorageFilesystemOutcomeV2::Failure(StorageFilesystemFailureV1 { code, message }) => {\n            bail!(\"WinFsp callback probe failed [{code}]: {message}\")\n        },\n    }\n}","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-winfsp/src/win.rs#L341-L377","documentation":"probe_callback sends a length-prefixed callback probe over the local control transport and enforces a size cap of SERVICE_MAX_CALLBACK_BYTES (8 MiB). A response of length 0 or greater than 8 MiB is rejected as corrupt or malicious, since the daemon cannot trust arbitrarily large callback replies.","triggerScenarios":"run_private_service -> probe_callback when the callback responder writes a zero-length response, or writes a 4-byte big-endian length exceeding 8 MiB before the JSON body.","commonSituations":"A buggy or foreign responder on callback_path returning garbage bytes; a protocol where the length field is little-endian (making huge u32 values); a compromised or misbehaving local process answering the probe; truncated framing swapping length/body order.","solutions":["Fix the callback responder to frame replies with a correct big-endian u32 length in (1..=8 MiB] followed by the JSON body.","Verify byte order (big-endian) in the responder's length prefix.","Confirm nothing else is bound to callback_path and answering the probe.","Reduce the payload returned by the callback if it legitimately exceeds 8 MiB."],"exampleFix":"// before (responder)\nstream.write_all(&(bytes.len() as u32).to_le_bytes()).await?;\n// after\nstream.write_all(&(bytes.len() as u32).to_be_bytes()).await?;","handlingStrategy":"type-guard","validationCode":"const SERVICE_MAX_CALLBACK_BYTES: usize = 8 * 1024 * 1024;\nfn response_length_is_valid(len: u32) -> bool {\n    (len as usize) > 0 && (len as usize) <= SERVICE_MAX_CALLBACK_BYTES\n}","typeGuard":"fn is_valid_framed_response(bytes: &[u8]) -> bool {\n    bytes.len() >= 4 && {\n        let len = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;\n        len > 0 && len <= 8 * 1024 * 1024 && bytes.len() - 4 == len\n    }\n}","tryCatchPattern":"match probe_callback(&launch, &request).await {\n    Err(e) if e.to_string().contains(\"exceeds limit\") => {\n        log::error!(\"callback responder framing bug (big-endian u32, <=8MiB)\");\n        Err(e)\n    },\n    other => other,\n}","preventionTips":["Frame responses with a big-endian u32 length prefix kept under 8 MiB.","Dedicate the callback socket to a single known responder process.","Add a responder integration test asserting the framing matches the daemon's expectations."],"tags":["protocol","limit-exceeded","winfsp"],"backgroundTag":"payload-too-large","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}