Skip to content

fuse: check O_LARGEFILE before masking it out of the open flags - #13957

Open
jbbakeng wants to merge 1 commit into
google:masterfrom
jbbakeng:jbbakeng/fuse-largefile-open
Open

fuse: check O_LARGEFILE before masking it out of the open flags#13957
jbbakeng wants to merge 1 commit into
google:masterfrom
jbbakeng:jbbakeng/fuse-largefile-open

Conversation

@jbbakeng

@jbbakeng jbbakeng commented Aug 3, 2026

Copy link
Copy Markdown

Files larger than 2GiB on a FUSE mount cannot be opened inside the sandbox: every
open() fails with EOVERFLOW ("Value too large for defined data type"), no matter what
flags the caller passes.

In pkg/sentry/fsimpl/fuse/inode.go, Open() masks the open flags against a whitelist
that does not include O_LARGEFILE on the line immediately before testing for it:

opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC |
	linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NONBLOCK | linux.O_NOCTTY |
	linux.O_APPEND | linux.O_DIRECT
i.attrMu.Lock()
defer i.attrMu.Unlock()
if opts.Flags&linux.O_LARGEFILE == 0 && i.size.Load() > linux.MAX_NON_LFS {
	return nil, linuxerr.EOVERFLOW
}

O_LARGEFILE is always clear by the time it is tested, so the guard is unconditional for
any file over MAX_NON_LFS. This is not an obscure path: openat(2) adds O_LARGEFILE
for all 64-bit callers (pkg/sentry/syscalls/linux/sys_file.go) and the VFS layer
preserves it (pkg/sentry/vfs/vfs.go), so the flag is set on essentially every open and
the check should essentially never fire. No other filesystem implementation has this
check, which is why only FUSE mounts are affected.

Present since 298b5f3 ("Refactor FUSE inode implementation.", 2023-02-01), so in every
release from release-20230214.0 onwards.

Reproduced with rclone's FUSE mount under runsc do: reading a 3GiB file through the
mount fails at open(), a 1GiB file on the same mount succeeds, and the same 3GiB file
read outside the mount succeeds.

The fix moves the mask below the check. TestFUSEOpenLargeFile covers both directions —
a file over MAX_NON_LFS opens with O_LARGEFILE and still returns EOVERFLOW without
it — and fails on the unpatched inode.go.

Assisted-by: Claude Code

inode.Open() masked opts.Flags against a whitelist that does not include
O_LARGEFILE on the line immediately before testing for it, so the EOVERFLOW
guard was taken on every open of a file larger than MAX_NON_LFS, whatever
flags the caller passed. As openat(2) sets O_LARGEFILE for all 64-bit
callers, files over 2GiB on a FUSE mount could not be opened at all.

Move the mask below the check.

Assisted-by: Claude Code

@EtiennePerot EtiennePerot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing a test, but this test looks rather complex for what is otherwise a trivial change. No need to keep it.

@jbbakeng
jbbakeng force-pushed the jbbakeng/fuse-largefile-open branch from 46a54b5 to 3115757 Compare August 3, 2026 18:30
@jbbakeng

jbbakeng commented Aug 3, 2026

Copy link
Copy Markdown
Author

Dropped the test. :)

@jbbakeng
jbbakeng requested a review from EtiennePerot August 3, 2026 19:08
copybara-service Bot pushed a commit that referenced this pull request Aug 3, 2026
Files larger than 2GiB on a FUSE mount cannot be opened inside the sandbox: every
`open()` fails with `EOVERFLOW` ("Value too large for defined data type"), no matter what
flags the caller passes.

In `pkg/sentry/fsimpl/fuse/inode.go`, `Open()` masks the open flags against a whitelist
that does not include `O_LARGEFILE` on the line immediately before testing for it:

```go
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC |
	linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NONBLOCK | linux.O_NOCTTY |
	linux.O_APPEND | linux.O_DIRECT
i.attrMu.Lock()
defer i.attrMu.Unlock()
if opts.Flags&linux.O_LARGEFILE == 0 && i.size.Load() > linux.MAX_NON_LFS {
	return nil, linuxerr.EOVERFLOW
}
```

`O_LARGEFILE` is always clear by the time it is tested, so the guard is unconditional for
any file over `MAX_NON_LFS`. This is not an obscure path: `openat(2)` adds `O_LARGEFILE`
for all 64-bit callers (`pkg/sentry/syscalls/linux/sys_file.go`) and the VFS layer
preserves it (`pkg/sentry/vfs/vfs.go`), so the flag is set on essentially every open and
the check should essentially never fire. No other filesystem implementation has this
check, which is why only FUSE mounts are affected.

Present since 298b5f3 ("Refactor FUSE inode implementation.", 2023-02-01), so in every
release from `release-20230214.0` onwards.

Reproduced with rclone's FUSE mount under `runsc do`: reading a 3GiB file through the
mount fails at `open()`, a 1GiB file on the same mount succeeds, and the same 3GiB file
read outside the mount succeeds.

The fix moves the mask below the check. `TestFUSEOpenLargeFile` covers both directions —
a file over `MAX_NON_LFS` opens with `O_LARGEFILE` and still returns `EOVERFLOW` without
it — and fails on the unpatched `inode.go`.

Assisted-by: Claude Code
FUTURE_COPYBARA_INTEGRATE_REVIEW=#13957 from jbbakeng:jbbakeng/fuse-largefile-open 3115757
PiperOrigin-RevId: 958610773
copybara-service Bot pushed a commit that referenced this pull request Aug 3, 2026
Files larger than 2GiB on a FUSE mount cannot be opened inside the sandbox: every
`open()` fails with `EOVERFLOW` ("Value too large for defined data type"), no matter what
flags the caller passes.

In `pkg/sentry/fsimpl/fuse/inode.go`, `Open()` masks the open flags against a whitelist
that does not include `O_LARGEFILE` on the line immediately before testing for it:

```go
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC |
	linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NONBLOCK | linux.O_NOCTTY |
	linux.O_APPEND | linux.O_DIRECT
i.attrMu.Lock()
defer i.attrMu.Unlock()
if opts.Flags&linux.O_LARGEFILE == 0 && i.size.Load() > linux.MAX_NON_LFS {
	return nil, linuxerr.EOVERFLOW
}
```

`O_LARGEFILE` is always clear by the time it is tested, so the guard is unconditional for
any file over `MAX_NON_LFS`. This is not an obscure path: `openat(2)` adds `O_LARGEFILE`
for all 64-bit callers (`pkg/sentry/syscalls/linux/sys_file.go`) and the VFS layer
preserves it (`pkg/sentry/vfs/vfs.go`), so the flag is set on essentially every open and
the check should essentially never fire. No other filesystem implementation has this
check, which is why only FUSE mounts are affected.

Present since 298b5f3 ("Refactor FUSE inode implementation.", 2023-02-01), so in every
release from `release-20230214.0` onwards.

Reproduced with rclone's FUSE mount under `runsc do`: reading a 3GiB file through the
mount fails at `open()`, a 1GiB file on the same mount succeeds, and the same 3GiB file
read outside the mount succeeds.

The fix moves the mask below the check. `TestFUSEOpenLargeFile` covers both directions —
a file over `MAX_NON_LFS` opens with `O_LARGEFILE` and still returns `EOVERFLOW` without
it — and fails on the unpatched `inode.go`.

Assisted-by: Claude Code
FUTURE_COPYBARA_INTEGRATE_REVIEW=#13957 from jbbakeng:jbbakeng/fuse-largefile-open 3115757
PiperOrigin-RevId: 958610773
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants