From: Miroslav Sulc <fordfrog@gentoo.org>
Subject: [PATCH] linux-sandbox: handle Btrfs subvolume device numbers

Btrfs getattr reports the subvolume anonymous device, but /proc/self/mountinfo
reports the superblock device. Rejecting that difference prevents every
sandbox command on Btrfs homes. Use the mountinfo device of the opened
mount ID for Btrfs only, retaining the existing alias, ancestry, and nested
mount validation. Missing IDs still fail conservatively.

Regression tests cover the device mismatch, subvolume aliases, direct socket
and directory aliases, nested mounts, and unchanged non-Btrfs rejection.

--- a/linux-sandbox/src/daemon_mounts.rs
+++ b/linux-sandbox/src/daemon_mounts.rs
@@ -53,6 +53,24 @@
     masked_root: Option<&Path>,
 ) -> io::Result<BTreeSet<PathBuf>> {
     let invalid = || io::Error::other("cannot establish app-server socket mount isolation");
+    // Btrfs reports a per-subvolume st_dev from getattr, while mountinfo uses
+    // the superblock device. The ID from the open directory identifies the
+    // exact mount even when those device numbers differ. Keep using mountinfo
+    // coordinates for all aliases on that filesystem; without an ID, retain
+    // the conservative device-based fallback below.
+    let socket_device = mount_id
+        .and_then(|mount_id| {
+            mountinfo.split(|byte| *byte == b'\n').find_map(|line| {
+                let mut fields = line.split(|byte| *byte == b' ');
+                let id = fields.next()?;
+                fields.next()?; // parent
+                let device = fields.next()?;
+                (id == mount_id.as_bytes()
+                    && fields.skip_while(|field| *field != b"-").nth(1) == Some(b"btrfs"))
+                .then_some(device)
+            })
+        })
+        .unwrap_or(device.as_bytes());
     let mut mounts = Vec::new();
     for line in mountinfo
         .split(|byte| *byte == b'\n')
@@ -66,7 +84,7 @@
         // Only roots on the socket filesystem can identify aliases. Other
         // filesystems can use non-path roots such as nsfs `mnt:[inode]`, but
         // their destinations still matter for ancestry and nested-mount checks.
-        let root = (*mount_device == device.as_bytes())
+        let root = (*mount_device == socket_device)
             .then(|| mount_path(root))
             .transpose()?;
         mounts.push((*id, *parent, *mount_device, root, destination));
@@ -79,7 +97,7 @@
             .find(|(id, ..)| *id == mount_id.as_bytes())
             .ok_or_else(invalid)?;
         let (_, _, mount_device, root, destination) = selected;
-        if *mount_device != device.as_bytes() {
+        if *mount_device != socket_device {
             return Err(invalid());
         }
         let root = root.as_ref().ok_or_else(invalid)?;
--- a/linux-sandbox/src/daemon_mounts_tests.rs
+++ b/linux-sandbox/src/daemon_mounts_tests.rs
@@ -225,3 +225,58 @@
         );
     }
 }
+
+// btrfs_getattr returns root->anon_dev, not the superblock device recorded in
+// mountinfo. A precise mount ID must still preserve all socket-directory masks.
+#[test]
+fn btrfs_subvolume_device_uses_the_identified_mount() {
+    let directory = Path::new("/home/user/.codex");
+    let mounts = b"1 0 0:28 / / rw - btrfs /dev/nvme0n1p2 rw\n";
+    assert_eq!(
+        check_mounts(directory, "0:29", Some("1"), mounts).unwrap(),
+        BTreeSet::from([directory.to_path_buf()])
+    );
+    assert!(check_mounts(directory, "0:29", None, mounts).is_err());
+    assert!(check_mounts(directory, "0:29", Some("missing"), mounts).is_err());
+}
+
+#[test]
+fn btrfs_subvolume_device_preserves_ancestor_alias_masks() {
+    let directory = Path::new("/home/user/.codex");
+    let mounts = b"1 0 0:28 /@ / rw - btrfs /dev/nvme0n1p2 rw\n\
+                   2 1 0:28 /@home /home rw - btrfs /dev/nvme0n1p2 rw\n\
+                   3 1 0:28 / /mnt/top rw - btrfs /dev/nvme0n1p2 rw\n";
+    assert_eq!(
+        check_mounts(directory, "0:29", Some("2"), mounts).unwrap(),
+        BTreeSet::from([
+            directory.to_path_buf(),
+            PathBuf::from("/mnt/top/@home/user/.codex"),
+        ])
+    );
+}
+
+#[test_case("/home/user/.codex", "/alias"; "directory alias")]
+#[test_case("/home/user/.codex/rpc.sock", "/alias.sock"; "socket alias")]
+#[test_case("/other", "/home/user/.codex/nested"; "nested mount")]
+fn btrfs_subvolume_device_still_rejects_unmaskable_mounts(root: &str, destination: &str) {
+    let mounts = format!(
+        "1 0 0:28 / / rw - btrfs /dev/nvme0n1p2 rw\n\
+         2 1 0:28 {root} {destination} rw - btrfs /dev/nvme0n1p2 rw\n"
+    );
+    assert_eq!(
+        check_mounts(
+            Path::new("/home/user/.codex"),
+            "0:29",
+            Some("1"),
+            mounts.as_bytes(),
+        )
+        .map_err(|error| error.kind()),
+        Err(io::ErrorKind::PermissionDenied)
+    );
+}
+
+#[test]
+fn non_btrfs_device_mismatch_is_still_rejected() {
+    let mounts = b"1 0 0:28 / / rw - ext4 /dev/nvme0n1p2 rw\n";
+    assert!(check_mounts(Path::new("/home/user/.codex"), "0:29", Some("1"), mounts).is_err());
+}
