{"record":{"id":"53a016a306f39278","repo":"openai/codex","slug":"permissiondenied","errorCode":"PermissionDenied","errorMessage":"descriptor-backed mount does not match its destination: {}","messagePattern":"descriptor-backed mount does not match its destination: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/linux-sandbox/src/fd_mount.rs","lineNumber":72,"sourceCode":"        }\n\n        let destination = Path::new(destination);\n        if !destination.is_absolute() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\n                    \"descriptor-backed mount destination must be absolute: {}\",\n                    destination.display()\n                ),\n            ));\n        }\n\n        let descriptor_metadata = file.metadata()?;\n        let destination_metadata = fs::symlink_metadata(destination)?;\n        if (descriptor_metadata.dev(), descriptor_metadata.ino())\n            != (destination_metadata.dev(), destination_metadata.ino())\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                format!(\n                    \"descriptor-backed mount does not match its destination: {}\",\n                    destination.display()\n                ),\n            ));\n        }\n\n        // Closing immediately prevents a writable host directory descriptor\n        // from reaching bridge workers or the sandboxed command.\n        drop(file);\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\n#[path = \"fd_mount_tests.rs\"]","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/linux-sandbox/src/fd_mount.rs#L54-L90","documentation":"This is the anti-tamper core of fd mount authentication: the file opened on the descriptor must be the very same inode the destination path names. verify_fd_mounts compares (dev, ino) from the descriptor's metadata against symlink_metadata of the destination; a mismatch means descriptor and destination disagree, which is treated as tampering and rejected with ErrorKind::PermissionDenied.","triggerScenarios":"An fd opened from a different file than the destination names; the destination renamed or replaced between fd creation and verification; the destination being a symlink, since symlink_metadata stats the link itself and can never match a regular file's inode.","commonSituations":"A daemon recreates its socket or tmpdir after the launcher captured the fd; test harnesses that swap files via rename(); pointing the fd at a staged copy while the destination names the original.","solutions":["Open the destination itself and pass that exact handle (File::open(dest)) so identity is guaranteed","Re-create the fd after any rename or replacement of the destination","Do not use a symlink as DEST when the fd refers to the target file; resolve symlinks first"],"exampleFix":"// before -- fd from a staged copy, destination is the real path\nlet file = File::open(\"/tmp/socket-root.staged\")?;\nlet spec = format!(\"{}:/tmp/socket-root\", file.as_raw_fd());\n\n// after -- open the destination itself so dev/ino match\nlet dest = \"/tmp/socket-root\";\nlet file = File::open(dest)?;\nlet spec = format!(\"{}:{}\", file.as_raw_fd(), dest);","handlingStrategy":"validation","validationCode":"fn fd_matches_destination(file: &std::fs::File, dest: &std::path::Path) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    match (file.metadata(), std::fs::symlink_metadata(dest)) {\n        (Ok(a), Ok(b)) => (a.dev(), a.ino()) == (b.dev(), b.ino()),\n        _ => false,\n    }\n}\n\nfor (file, dest) in &pairs {\n    if !fd_matches_destination(file, dest) {\n        return Err(format!(\"fd/destination identity mismatch: {}\", dest.display()));\n    }\n}","typeGuard":"fn fd_matches_destination(file: &std::fs::File, dest: &std::path::Path) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    match (file.metadata(), std::fs::symlink_metadata(dest)) {\n        (Ok(a), Ok(b)) => (a.dev(), a.ino()) == (b.dev(), b.ino()),\n        _ => false,\n    }\n}","tryCatchPattern":"match verify_fd_mounts(&mounts) {\n    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {\n        // destination was replaced: re-open it and rebuild the specs\n        rebuild_specs_from_destinations()?;\n    }\n    other => other?,\n}","preventionTips":["Create the fd and the destination string from the same open() call","Never rename or replace a mount destination while a sandbox launch is in flight","Treat PermissionDenied from this check as a security signal, not a transient error"],"tags":["linux-sandbox","file-descriptors","security","bubblewrap","rust"],"backgroundTag":"fd-destination-inode-mismatch","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}