bytecodealliance / bytecodealliance/cap-std

Linux: support RESOLVE_NO_XDEV when openat2 is unavailable

Đang mở
#423 5 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Rust
Star
821
Fork
57
Merge trung bình
1 giờ 16 phút
Pull request đã merge (30 ngày)
4

Mô tả

**TL;DR:** On Linux, cap-primitives falls back to its manual path resolver when
`openat2()` is unavailable, but the fallback cannot enforce
`RESOLVE_NO_XDEV`. Downstream users therefore cannot safely reject mount
crossings on systems where `openat2()` returns `ENOSYS`.

**Context:** This surfaced through bootc's `open_dir_noxdev()` usage under QEMU
linux-user, but the missing behavior is general and is not specific to bootc or
QEMU. The downstream report is
https://github.com/bootc-dev/bootc/issues/1481.

## Current behavior

`cap-primitives/src/rustix/linux/fs/open_impl.rs` uses `openat2()` for its fast
path and falls back to `manually::open()` when the syscall is unavailable. The
manual resolver preserves capability containment and resolves symlinks one
component at a time, but it has no `NO_XDEV` policy.

As a result, cap-std-ext implements `open_dir_noxdev()` with a direct
`openat2()` call. It handles `EXDEV`, but propagates `ENOSYS` instead of using a
capability-safe fallback.

## Reproduction

| Component | Value |
| ----------------- | ---------------------- |
| Host and VM | x86_64, Fedora 44 |
| Container | aarch64 Fedora bootc |
| Affected emulator | QEMU linux-user 8.2.9 |
| Working emulator | QEMU linux-user 10.2.2 |
| cap-primitives | 4.0.3 |
| cap-std-ext | 5.1.2 |

With QEMU 8.2.9 registered through `binfmt_misc`:

```console
$ podman run --rm --arch arm64 \
quay.io/fedora/fedora-bootc:latest \
bootc container lint
error: Linting: Function not implemented (os error 38)
```

The trace reports unsupported AArch64 syscall 437 immediately before the
failure. That syscall is `openat2`, same image passes under QEMU 10.2.2,
which implements `openat2`.

## Required behavior

The fallback must reject crossing any mount, including same filesystem bind
mounts and mounts reached through symlinks, while retaining the manual
resolver's containment and race-resistance properties.

A final `st_dev` comparison is insufficient because a bind mount may have the
same device and inode as its source. Checking only the final descriptor is also
insufficient: an intermediate component or symlink may cross a mount even when
the final component is not a mount root. Each opened component must therefore
be checked before symlink interpretation or descent.

If reliable mount information is unavailable, the operation should return an
explicit unsupported error rather than silently perform a normal open.

## Proposed direction

1. Keep `openat2()` as the fast path and add `RESOLVE_NO_XDEV` when requested.
2. On `ENOSYS`, reuse the existing manual component resolver rather than add a
separate path walker.
3. Check every newly opened descriptor for a mount crossing before following a
symlink or descending into that component.
4. Return `EXDEV` for a detected crossing and fail closed when mount identity
cannot be determined reliably.
5. Expose this as an existing-directory operation through the public layer the
maintainers consider appropriate, such as `cap-fs-ext`. Existing opens
should remain unchanged when the policy is not requested.

`STATX_ATTR_MOUNT_ROOT` appears suitable for the fallback, under QEMU 8.2.9 it
correctly identified all 153 mount points visible in `/proc/self/mountinfo`, a
same filesystem bind mount, and descriptors held across mount and detach
operations. `STATX_MNT_ID` was not reliable in this environment because QEMU
reported the field as available but returned zero for tested paths.

The initial API should be limited to opening existing directories, applying a
general `OpenOptions` flag to effectful operations such as truncation would
require a broader design because validating the result after opening is too
late.

## References

- Downstream report: https://github.com/bootc-dev/bootc/issues/1481
- `openat2(2)`: https://man7.org/linux/man-pages/man2/openat2.2.html
- `statx(2)`: https://man7.org/linux/man-pages/man2/statx.2.html
- cap-std-ext API: https://docs.rs/cap-std-ext/latest/cap_std_ext/dirext/trait.CapStdExtDirExt.html

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

Bắt đầu với cap-primitives/src/rustix/linux/fs/open_impl.rs và resolver manually::open() hiện có, sau đó lần theo đường dẫn open_dir_noxdev() của cap-std-ext và API công khai cap-fs-ext. Xác minh rằng fallback kiểm tra từng thành phần đã mở trước khi duyệt symlink hoặc đi xuống, trả về EXDEV hoặc một lỗi không được hỗ trợ rõ ràng khi cần, và giữ nguyên các thao tác mở không có policy này.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
linux, rust
Lĩnh vực
operating-systems, security
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
45/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.