Repository navigation
virtiofsd: avoid reopening regular files in getxattr - #2002
Conversation
Unshare CLONE_FS in each virtio-fs worker before processing requests. This prevents proc-fd operations that change the working directory from affecting other threads sharing the filesystem context. Assisted-by: Codex:GPT-6.1 Sol Signed-off-by: Aries441 <1518228705@qq.com>
Use the existing O_PATH descriptor through /proc/self/fd to read extended attributes without reopening the backing file. The extra open/close can undo O_TRUNC on s3fs and leave stale file lengths. Add regression tests for the proc-fd getxattr path. Fixes: TencentCloud#1991 Assisted-by: Codex:GPT-6.1 Sol Signed-off-by: Aries441 <1518228705@qq.com>
|
While investigating this issue, I reproduced the same problem in setxattr(), listxattr(), and removexattr(): each performs an extra open/close that can undo O_TRUNC on s3fs, even when the xattr call returns EOPNOTSUPP. The current PR focuses on getxattr(). I’ll open a separate issue to track the remaining three operations and follow up with a fix. |
| // Passthrough xattr operations temporarily change CWD through procfs. | ||
| // Give each worker its own filesystem state, as standalone virtiofsd does. | ||
| // Safe because unshare does not access userspace memory. | ||
| if unsafe { libc::unshare(libc::CLONE_FS) } < 0 { |
There was a problem hiding this comment.
Each virtio-fs queue thread now unshare(CLONE_FS)s unconditionally, and a failure is fatal: run() returns Err, spawn_virtio_thread signals exit_evt (VM shutdown) and only logs the generic Error running worker. Hosts/containers whose seccomp policy rejects unshare are a documented case here (see the startup probe and comment in hypervisor/virtiofsd/src/main.rs:157-166), so this converts an environment quirk into "virtio-fs cannot be used at all" — including configurations with xattr = false, where the proc-fd CWD dance is never executed (the set_working_directory paths are only reachable when cfg.xattr, cfg.security_label, or cfg.xattr_security_capability is set).
Consider gating the unshare on those settings and/or probing unshare(CLONE_FS) once in Fs::activate(), returning a descriptive ActivateError instead of failing asynchronously in every worker thread.
| // PassthroughFs changes CWD for proc-fd xattr calls and sets the umask. | ||
| // Keep these changes local to this test thread, as in the daemon workers. | ||
| // Safe because unshare does not access userspace memory. | ||
| assert_eq!(unsafe { libc::unshare(libc::CLONE_FS) }, 0); |
There was a problem hiding this comment.
Fixture::new assumes unshare(CLONE_FS) always succeeds, so all four tests panic (rather than skip) in sandboxes where that syscall is filtered — the exact Docker/Moby seccomp situation documented at hypervisor/virtiofsd/src/main.rs:157-166. These tests are about getxattr semantics, not sandbox policy; consider skipping with a clear message when unshare is unavailable so the suite stays usable in restricted CI environments.
Automated review — PR #2002
What the change does
Verification I performed
Findings
Notes
VerdictNo correctness defect found in the new |
Serialize tests that use the proc-fd CWD path with a process-wide mutex and restore the process umask. This keeps the xattr regression tests runnable in CI containers where unshare(CLONE_FS) is denied. Assisted-by: Codex:GPT-6.1 Sol Signed-off-by: Aries441 <1518228705@qq.com>
Gate per-worker unshare(CLONE_FS) on xattr, security label, or xattr mapping. Avoid failing virtio-fs activation when these proc-fd paths are disabled, and log a clear error when isolation is required but unavailable. Assisted-by: Codex:GPT-6.1 Sol Signed-off-by: Aries441 <1518228705@qq.com>
| // Safe because unshare does not access userspace memory. | ||
| // Only required when xattr, security_label, or xattrmap is enabled — | ||
| // those paths call fchdir through the proc-fd route. | ||
| if self.needs_cwd_isolation && unsafe { libc::unshare(libc::CLONE_FS) } < 0 { |
There was a problem hiding this comment.
This makes unshare(CLONE_FS) a hard requirement for the whole VM whenever xattr is enabled — and BackendFsConfig::xattr defaults to true (default_fsconfig_xattr), so this is the default configuration.
On failure run() returns Err, and spawn_virtio_thread reacts by writing to exit_evt (hypervisor/virtio-devices/src/thread_helper.rs:53-57), which is the VMM-wide shutdown eventfd. So a seccomp/LSM policy that rejects unshare no longer degrades to the previous racy-but-working behaviour; it tears down the entire VM. The standalone daemon guards against exactly this by probing unshare(CLONE_FS) once at startup and returning a dedicated UnshareCloneFs error, citing Docker/Moby seccomp profiles that reject it without CAP_SYS_ADMIN (hypervisor/virtiofsd/src/main.rs:157-166).
Would it be better to do the same one-shot probe in activate() (returning an ActivateError so the failure is a clean device-activation error), or to fall back to the previous reopen path with a warning when the syscall is unavailable? At minimum, the current failure semantics seem worth calling out in the PR description.
| /// path. The daemon workers call unshare(CLONE_FS) so each thread has its own | ||
| /// CWD and umask; tests hold this lock instead because unshare may be blocked | ||
| /// by CI container seccomp policies. | ||
| fn cwd_lock() -> &'static Mutex<()> { |
There was a problem hiding this comment.
The tests intentionally serialize with this mutex instead of calling unshare(CLONE_FS), so the production mechanism that actually makes the proc-fd route safe — per-worker unshare(CLONE_FS) in virtio-devices/src/fs.rs — has no coverage. Nothing here would catch a regression where two workers concurrently clobber each other's CWD (the exact bug this PR is fixing), and all the assertions run single-threaded.
Could you add one test that spawns two threads performing proc-fd xattr calls concurrently after the same unshare(CLONE_FS) setup the daemon uses (early-return if unshare is unavailable in the test container), and asserts both calls return the expected values? That would make the isolation claim testable rather than implicit.
|
Thanks. Confirmed: CI failed in the test fixture before the xattr assertions, while the same seccomp restriction could also make worker startup fail in production. I've added an activation-time unshare probe that returns a clear ActivateError, gated it on xattr/security_label/xattrmap, and kept the xattr tests portable with serialized fixtures plus a concurrent CWD-isolation test. I also verified the suite in a container profile that denies unshare. I'll push after tightening the worker-level skip handling. |
lisongqian
left a comment
There was a problem hiding this comment.
LGTM. I have run the full hypervisor CI and all of these passed.
Summary
Fixes #1991.
Use the existing O_PATH descriptor through /proc/self/fd in getxattr(),
avoiding an extra open/close that can undo O_TRUNC on s3fs.
Give each virtio-fs worker a separate filesystem context with
unshare(CLONE_FS) to prevent concurrent CWD changes from interfering
with proc-fd operations. Add regression tests.
Validation
These checks were performed before rebasing onto the latest master;
tests have not been rerun after the rebase.
Follow-up
The same reopen pattern was independently reproduced in setxattr(),
listxattr(), and removexattr(). Those operations will be tracked and
fixed separately.
Assisted-by: Codex:GPT-6.1 Sol