Skip to content

Error: storage error: Failed to read ownership xattr #1578

Open

Description

@XhstormR
└──❯ whoami
leo

└──❯ uname -a
Darwin l3xkxytj3q 25.6.0 Darwin Kernel Version 25.6.0: Sat Jul 11 15:27:27 PDT 2026; root:xnu-12377.161.13~4/RELEASE_ARM64_T6041 arm64 Darwin

└──❯ ./boxlite info
version: 0.10.2
homeDir: /Users/leo/.boxlite
virtualization: available
os: macos
arch: aarch64
boxesTotal: 0
boxesRunning: 0
boxesStopped: 0
boxesConfigured: 0
imagesCount: 2

└──❯ ./boxlite run --rm fedora:latest echo "hello from boxlite"
2026-09-21T09:00:24.329272Z  WARN boxlite::rootfs::builder: Copy-based mount failed: storage error: Failed to create symlink /Users/leo/.boxlite/tmp/.tmpfdzzHJ/merged/usr/lib64/libevent-2.1.so.7 -> libevent-2.1.so.7.0.1: Permission denied (os error 13), falling back to extraction-based mount
2026-09-21T09:00:25.898745Z ERROR boxlite::litebox::init::tasks: Task failed: storage error: Failed to read ownership xattr on /Users/leo/.boxlite/tmp/.tmpfdzzHJ/merged/usr/bin/sudoreplay: Permission denied (os error 13) box_id=vSKOHJ8dYUh2 task=container_rootfs_prep
2026-09-21T09:00:25.898770Z  WARN boxlite::litebox::init::types: Box initialization failed, cleaning up box_id=vSKOHJ8dYUh2 reason=storage error: Failed to read ownership xattr on /Users/leo/.boxlite/tmp/.tmpfdzzHJ/merged/usr/bin/sudoreplay: Permission denied (os error 13)
2026-09-21T09:00:25.898774Z ERROR boxlite::litebox::init::types: Box failed. Diagnostic files preserved at:
  /Users/leo/.boxlite/boxes/vSKOHJ8dYUh2

To destroy: issue DESTROY_SANDBOX or `boxlite rm vSKOHJ8dYUh2`
Error: storage error: Failed to read ownership xattr on /Users/leo/.boxlite/tmp/.tmpfdzzHJ/merged/usr/bin/sudoreplay: Permission denied (os error 13)

Activity

  1. DorianZheng commented on Sep 22, 2026

    @DorianZheng
    Member

    @boxliteai investigate the issue

  2. boxliteai commented on Sep 22, 2026

    @boxliteai
    Contributor

    @DorianZheng I traced this to permission ordering in fix_rootfs_permissions; it is not a corrupt Fedora xattr.

    fedora:latest arm64 contains:

    ---x--x--x 0/0 136976 ... usr/bin/sudoreplay
    

    The extractor writes user.containers.override_stat before applying the final 0111 mode. Later, fix_rootfs_permissions does the reverse of what macOS requires:

    1. Reads the ownership xattr while the file is still 0111.
    2. macOS authorizes getxattr with KAUTH_VNODE_READ_EXTATTRIBUTES (XNU source).
    3. The read returns EACCES.
    4. Only afterward would BoxLite temporarily add write permission.

    The copy-mount symlink error is separate and already triggers the intended extraction fallback.

    Recommended fix: temporarily grant owner read/write before both xattr operations, then restore the original mode on every exit path:

     let mode = metadata.permissions().mode() & 0o7777;
    +let needs_access = mode & 0o600 != 0o600;
    +if needs_access {
    +    set_mode(path, mode | 0o600)?;
    +}
    
    -let recorded = OverrideStat::read_xattr(path)?;
    +let operation_result = (|| {
    +    let recorded = OverrideStat::read_xattr(path)?;
    +    // Preserve recorded uid/gid and update the xattr as today.
    +    ...
    +})();
    +
    +if needs_access {
    +    set_mode(path, mode)?;
    +}
    +operation_result?;

    This should retain the current protection against malformed ownership records; treating EACCES as “xattr absent” would risk replacing the only recorded UID/GID with 0:0.

    A macOS regression test should create an xattr-bearing file, chmod it to 0111, call fix_rootfs_permissions, and verify both the ownership record and 0111 mode survive.

    Commands supporting the diagnosis:

    curl ... registry.fedoraproject.org/v2/fedora/... | tar -tzvvf - | rg usr/bin/sudoreplay
    git blame -L 145,225 src/boxlite/src/rootfs/operations.rs
    git clone --depth 1 https://github.com/apple-oss-distributions/xnu.git

    I could not run the Rust tests because this investigation VM does not have make installed. No repository files were changed.

    📦 Ran in an isolated BoxLite microVM · reply with a mention to follow up

  3. DorianZheng commented on Sep 22, 2026

    @DorianZheng
    Member

    @boxliteai in short

  4. boxliteai commented on Sep 22, 2026

    @boxliteai
    Contributor

    @DorianZheng Fedora’s sudoreplay is execute-only (0111). BoxLite reads its ownership xattr before adding temporary read permission, matching the macOS Permission denied failure.

    Proposed fix: grant owner read/write before accessing xattrs, preserve ownership, and restore the original permissions afterward. The diagnosis still needs a macOS reproduction.

    📦 Ran in an isolated BoxLite microVM · reply with a mention to follow up

  5. DorianZheng commented on Sep 22, 2026

    @DorianZheng
    Member

    @boxliteai we already have similar fix

  6. boxliteai commented on Sep 22, 2026

    @boxliteai
    Contributor

    @DorianZheng Yes—#1177 already handles this in the ext4 source scan: grant owner read permission before reading xattrs, then restore the original mode.

    The gap is the earlier fix_rootfs_permissions traversal, which still reads xattrs before adjusting permissions. This failure occurs before the existing fix can help. I missed that precedent in my first reply.

    📦 Ran in an isolated BoxLite microVM · reply with a mention to follow up

  7. DorianZheng commented on Sep 22, 2026

    @DorianZheng
    Member

    @boxliteai illustrate the workflow and denote these 2 site

  8. boxliteai commented on Sep 22, 2026

    @boxliteai
    Contributor

    @DorianZheng For a new image disk, running as a regular user:

    flowchart TD
        A["RootfsBuilder.prepare"] --> B["Copy cached layers"]
        B -->|success| E
        B -->|"failure: reported symlink error"| C["Fallback: extract layers and finalize modes"]
        C --> D["① fix_rootfs_permissions<br/>Read ownership xattr BEFORE chmod"]
        D -->|"reported EACCES on sudoreplay"| X["Abort initialization"]
        D -->|success| E["create_ext4_from_dir"]
        E --> F["② scan_source_tree — existing fix<br/>Grant owner read BEFORE reading xattrs"]
        F --> G["mke2fs populates ext4"]
        G --> H["Restore intended image ownership/modes<br/>and original host modes"]
    
    Loading
    • ① Remaining gap: operations.rs:188 reads the xattr first. Its later permission adjustment adds write only, so it does not protect this read.
    • ② Existing fix: ext4.rs:271–281 grants owner read before accessing ownership xattrs.

    The reported fallback fails at ①, so ② is never reached.

    📦 Ran in an isolated BoxLite microVM · reply with a mention to follow up

  9. DorianZheng commented on Sep 22, 2026

    @DorianZheng
    Member

    @boxliteai can we extract the fix from 2? grant before 1 and restore in 2. make a simple code design here.

  10. boxliteai commented on Sep 22, 2026

    @boxliteai
    Contributor

    @DorianZheng Yes. Extract SourceModeGuard and its permission-widening logic into a shared helper. Let PreparedRootfs own the guard across both stages.

    struct PreparedRootfs {
        path: PathBuf,
        modes: SourceModeGuard, // Records originals; Drop restores host modes.
    }
    
    // RootfsBuilder: after extraction and finalize, before ①
    let prepared = PreparedRootfs::grant_access(dest)?;
    fix_rootfs_permissions(&prepared)?; // ① Uses saved ORIGINAL modes.
    return Ok(prepared);
    
    // ImageDiskManager moves the whole prepared value into spawn_blocking.
    // Ext4 builder consumes it:
    fn create_ext4_from_rootfs(source: PreparedRootfs, ...) -> Result<Disk> {
        let scan = scan_source_tree(&source.path)?; // ② No longer grants access.
        let disk = populate_with_mke2fs(&source.path, ...)?;
        normalize_inodes_with_debugfs(&disk, &scan, &source.modes)?;
        Ok(disk)
        // source drops here: restores host modes.
    }

    Three details matter:

    • Grant access top-down: add owner read/write to files and read/write/search to directories; skip symlinks. Record each original mode before changing it.
    • ① must write the saved original mode into the xattr, otherwise temporary permissions become guest permissions.
    • ② restores image modes through debugfs; the guard restores host modes bottom-up. The guard also restores on failure before reaching ②.

    Copy-success and direct-directory ext4 callers should construct the same guarded input. This keeps one permission implementation and one owner of its lifetime.

    📦 Ran in an isolated BoxLite microVM · reply with a mention to follow up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions