mirror of
https://github.com/torvalds/linux.git
synced 2026-10-17 06:10:08 +02:00
master
108670 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
214897acc8
|
Revert "namei: don't reveal overmounted entries in refwalk"
This reverts commit
|
||
|
|
69fbe94f80
|
namespace: a mount that copy_tree() leaves out can't take over a lock
Since commit |
||
|
|
98b6cf102b
|
namei: test for a negative dentry the way traverse_mounts() does
lookup_fast_for_open() only hands an O_CREAT open a cached dentry if
it's positive. Otherwise the open goes on to lookup_open(), which takes
the directory lock and creates the file. It tests for that with
!dentry->d_inode, but traverse_mounts() later decides with the type in
d_flags. And when a dentry is instantiated __d_set_inode_and_type()
stores ->d_inode first and only then releases the type into ->d_flags.
So a refwalk that took its reference to a negative dentry right before
another task instantiates it can find ->d_inode already set in
lookup_fast_for_open(), go on to step_into(), and still find the
negative type in traverse_mounts(). The open then fails with -ENOENT,
which an O_CREAT open must never do. rcuwalk doesn't get that far, the
d_seq check in step_into() catches the instantiation.
Use d_is_negative() so both look at the type. While the type is still
negative the open takes lookup_open() and waits for the directory lock
like any other O_CREAT open of a negative dentry.
On its own the window is a couple of instructions in the creating task,
so it takes an interrupt or a preempted vcpu in exactly the right spot.
I haven't caught it without help in about 600 million racing O_CREAT
opens.
The debug patch below stalls the creating task between the two stores
for names starting with "ocrace", the way an interrupt landing there
would. The reproducer below opens "ocrace" with O_CREAT through
symlinks on a strictatime tmpfs: following them updates their atime,
which drops the walk out of rcuwalk, so the name is always looked up in
refwalk. With
echo 20 > /proc/sys/fs/ocrace_instantiate_us
./ocreat_enoent -t 60
on a KASAN kernel open() failed with -ENOENT 43 times in 4.9 million
calls. With this patch it never failed in 4.8 million calls. Tracing
step_into_slowpath() during such a run, each of the 98 -ENOENTs it
returned came right after the same task had entered it with a dentry
that was still typed negative.
// SPDX-License-Identifier: GPL-2.0
/*
* ocreat_enoent: open(O_CREAT) racing the creation of the same name.
*
* Writers unlink "ocrace" and create it again. Readers open it with
* O_CREAT through a symlink of their own on a strictatime tmpfs.
* Following the symlink updates its atime, which drops the walk out of
* rcu mode, so the name is always looked up in refwalk. An O_CREAT open
* must never fail with ENOENT.
*
* -p arms a cpu-clock perf timer with that period in ns on every writer,
* so that interrupts land at random points in it.
*
* usage: ocreat_enoent [-t secs] [-w writers] [-r readers] [-p ns]
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/perf_event.h>
#define DIR "/tmp/oc"
static atomic_int stop;
static atomic_long opens, creates, enoent, other;
static long period;
static void die(const char *what)
{
fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno));
exit(2);
}
static void *writer(void *arg)
{
(void)arg;
if (period) {
struct perf_event_attr attr = {
.type = PERF_TYPE_SOFTWARE,
.size = sizeof(attr),
.config = PERF_COUNT_SW_CPU_CLOCK,
.sample_period = period,
};
if (syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0) < 0)
die("perf_event_open");
}
while (!atomic_load(&stop)) {
int fd;
unlink(DIR "/ocrace");
fd = open(DIR "/ocrace", O_CREAT | O_RDWR | O_CLOEXEC, 0644);
if (fd >= 0)
close(fd);
atomic_fetch_add(&creates, 1);
}
return NULL;
}
static void *reader(void *arg)
{
char lnk[64];
snprintf(lnk, sizeof(lnk), DIR "/lnk.%ld", (long)arg);
while (!atomic_load(&stop)) {
int fd = open(lnk, O_CREAT | O_RDWR | O_CLOEXEC, 0644);
if (fd >= 0)
close(fd);
else if (errno == ENOENT)
atomic_fetch_add(&enoent, 1);
else
atomic_fetch_add(&other, 1);
atomic_fetch_add(&opens, 1);
}
return NULL;
}
int main(int argc, char **argv)
{
int secs = 60, nw = 4, nr = 12, c, n = 0;
pthread_t t[256];
while ((c = getopt(argc, argv, "t:w:r:p:")) != -1) {
switch (c) {
case 't': secs = atoi(optarg); break;
case 'w': nw = atoi(optarg); break;
case 'r': nr = atoi(optarg); break;
case 'p': period = atol(optarg); break;
default: return 2;
}
}
if (nw + nr > 256)
return 2;
mkdir(DIR, 0755);
if (mount("oc", DIR, "tmpfs", MS_STRICTATIME, "mode=0755"))
die("tmpfs");
for (long i = 0; i < nr; i++) {
char lnk[64];
snprintf(lnk, sizeof(lnk), DIR "/lnk.%ld", i);
if (symlink("ocrace", lnk))
die("symlink");
}
for (long i = 0; i < nw; i++)
if (pthread_create(&t[n++], NULL, writer, NULL))
die("writer");
for (long i = 0; i < nr; i++)
if (pthread_create(&t[n++], NULL, reader, (void *)i))
die("reader");
sleep(secs);
atomic_store(&stop, 1);
for (int i = 0; i < n; i++)
pthread_join(t[i], NULL);
printf("%ds: %ld opens, %ld creates, ENOENT %ld, other errors %ld\n",
secs, atomic_load(&opens), atomic_load(&creates),
atomic_load(&enoent), atomic_load(&other));
umount2(DIR, MNT_DETACH);
return atomic_load(&enoent) ? 1 : 0;
}
diff --git a/fs/dcache.c b/fs/dcache.c
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -32,9 +32,13 @@
#include <linux/bit_spinlock.h>
#include <linux/rculist_bl.h>
#include <linux/list_lru.h>
+#include <linux/delay.h>
#include "internal.h"
#include "mount.h"
+/* debug: stall "ocrace*" names between ->d_inode and the type */
+static int sysctl_ocrace_instantiate_us;
+
#include <asm/runtime-const.h>
/*
@@ -200,6 +204,13 @@
}
static const struct ctl_table fs_dcache_sysctls[] = {
+ {
+ .procname = "ocrace_instantiate_us",
+ .data = &sysctl_ocrace_instantiate_us,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
{
.procname = "dentry-state",
.data = &dentry_stat,
@@ -405,6 +416,10 @@
unsigned flags;
dentry->d_inode = inode;
+ /* debug: an interrupt landing right here */
+ if (unlikely(READ_ONCE(sysctl_ocrace_instantiate_us)) &&
+ !strncmp(dentry->d_name.name, "ocrace", 6))
+ udelay(READ_ONCE(sysctl_ocrace_instantiate_us));
flags = READ_ONCE(dentry->d_flags);
flags &= ~DCACHE_ENTRY_TYPE;
flags |= type_flags;
Fixes:
|
||
|
|
99ae7dc439
|
ovl: reject lowerdir ending with unescaped backslash
In ovl_parse_param_split_lowerdirs() a backslash consumes the character that follows it. When the backslash is the last character of the lowerdir value, it consumes the NUL terminator: the loop exits normally and the backslash is kept in the output, so the lowerdir is passed downstream with a trailing backslash and the mount fails later with a confusing ENOENT from path lookup. A trailing unescaped backslash escapes nothing, so reject it with EINVAL. Signed-off-by: Guanglei Zhu <zhugl3@xiaopeng.com> Link: https://patch.msgid.link/20261012095538.938789-1-zhugl3@xiaopeng.com Reviewed-by: Amir Goldstein <amir73il@gmail.com> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
36ebddca56
|
fsnotify: prevent mark additions during connector teardown
fsnotify_destroy_marks() keeps a reference to its current mark while it
drops conn->lock to destroy the mark. This stabilizes the forward-walk
cursor, but does not prevent a new mark from being inserted before it.
For example, an inotify watch re-added after IN_IGNORED sorts before the
old, detached mark for the same group. The walk can miss the replacement
and then detach the entire connector from the object.
The replacement is still on its group's marks_list and, for inotify, in
the watch IDR. The group mutex no longer protects the connector's object
from disappearing. An unprivileged fdinfo read racing the final detach
can consequently call igrab(NULL):
BUG: KASAN: null-ptr-deref in igrab+0x24/0x100
Write of size 4 at addr 0000000000000148
Call Trace:
igrab+0x24/0x100
inotify_show_fdinfo+0x1dc/0x5e0
seq_show+0x397/0x690
Set a DESTROYING flag under conn->lock before starting the walk, and
reject new marks on that connector with -ENOENT. The existing add-mark
error paths undo the temporary group-list entry and the backend's watch
allocation. Do not wait for teardown while holding the group mutex,
which the destroyer needs. Once the old connector is detached, an add
can install a fresh connector as before.
This closes the insertion race for all object types using the common
connector teardown, without changing the mark reference or SRCU lifetime
rules.
Fixes:
|
||
|
|
a291a5cf00
|
fs: drop the smp_mb()s that mnt_get_count() made redundant
Both mntput_no_expire_slowpath() and do_umount() have an smp_mb() between taking mount_lock and summing up the reference count. They pair with the smp_mb() in __legitimize_mnt(). A walker in __legitimize_mnt() increments the count and then re-reads the seqcount of mount_lock. The unmounter writes the seqcount when it takes mount_lock and then sums up the count. They must not both miss each other's store. Otherwise the walker keeps a reference to a mount that the unmounter finishes off. That's the store buffering pattern and it needs a full barrier between the store and the load on both sides. In commit |
||
|
|
65676cd420
|
autofs: ignore notification errors from a replaced pipe
autofs_notify_daemon() takes a reference to the current notification
pipe under wq_mutex, then drops the mutex before writing the request. The
write can block while the daemon enters catatonic mode and installs a
replacement pipe.
If the old pipe's reader is then closed, the delayed write returns -EPIPE
and the generic error path enters catatonic mode again. That transition
acts on the current superblock state, closes the replacement pipe, and
releases the new daemon generation's wait queues.
Only enter catatonic mode when the pipe which failed is still the current
pipe. Factor the already-locked transition so the identity check and state
change are atomic with respect to another replacement.
This was reproduced on v7.3-rc5-337-gff47652a4b66c in a local QEMU guest.
An unprivileged UID 65534 lookup filled the old packet pipe before a
normal CATATONIC/SETPIPEFD restart. On the unmodified kernel, the new pipe
received HUP and both a pending victim lookup and a later lookup failed
with ENOENT in two out of two fresh-mount runs.
With this change, the new pipe remained active, received an intact
304-byte request, and both victim lookups completed in two out of two
runs. Normal lookup and restart-without-a-blocked-writer controls passed.
No KASAN, oops, refcount, or lock diagnostic was observed. A source
reproducer and complete local logs are available privately on request.
The demonstrated impact is limited to denial of a shared autofs service
during a legitimate daemon restart. No memory corruption, information leak,
privilege escalation, or code execution primitive was observed.
Fixes:
|
||
|
|
56c3824de5
|
ntfs3: free replaced mount options after a grace period
ntfs_fs_reconfigure() replaces the old mount options with new mount
options in sbi->options. The old mount options land in fs_context and
are freed as soon as remount returns. With a pathwalk held up in
ntfs_nls_to_utf16() between loading sbi->options and dereferencing it
while a remount replaces them:
BUG: KASAN: slab-use-after-free in ntfs_nls_to_utf16+0x7ae/0x7c0
Read of size 8 at addr ff110001044fd448 by task walker/98
Call Trace:
<TASK>
ntfs_nls_to_utf16+0x7ae/0x7c0
ntfs_d_hash+0x22b/0x500
link_path_walk+0x11ad/0x1d30
path_lookupat+0x63/0xb00
filename_lookup+0x1f1/0x5b0
vfs_statx+0xd7/0x3b0
[...]
Allocated by task 95:
__kmalloc_node_track_caller_noprof+0x2a2/0x730
kmemdup_noprof+0x25/0x50
ntfs_fill_super+0x1e5/0x4550
[...]
Freed by task 97:
kfree+0x1db/0x5b0
put_fs_context+0x13c/0x7d0
path_mount+0x54a/0x2130
__x64_sys_mount+0x20c/0x270
That info is needed in rcu lookup in the dentry hash and dentry compary
methods for ntfs3 and causes a UAF today. Delay freeing with an rcu
grace period.
ntfs_nls_to_utf16() reads ->nls and ->ads once under rcu_read_lock() so
the hooks pair with the release in ntfs_fs_reconfigure(). That also
covers callers outside of rcu pathwalk such as ntfs_lookup(). They don't
hold rcu_read_lock() themselves and wouldn't be protected by the grace
period otherwise. Remount refuses to change the iocharset so ->nls stays
valid after rcu_read_unlock().
Fixes:
|
||
|
|
c01b091b06
|
ntfs3: free the sbi after a grace period
If the "nocase" mount option ntfs3 uses custom hash and dentry compare
functions. Both access ntfs3 superblock private information in rcu
pathwalk. ntfs_put_super() and ntfs3_kill_sb() drop the info without any
rcu delay. A lookup that's still inside the filesystem when the last
reference to the mount is dropped causes a UAF. With a pathwalk held up
in ntfs_d_hash() while the last reference to the mount goes away:
BUG: KASAN: slab-use-after-free in ntfs_nls_to_utf16+0x752/0x940
Read of size 8 at addr ff1100010453c850 by task walker/99
Call Trace:
<TASK>
ntfs_nls_to_utf16+0x752/0x940
ntfs_d_hash+0x22b/0x500
link_path_walk+0x11ad/0x1d30
path_lookupat+0x63/0xb00
filename_lookup+0x1f1/0x5b0
vfs_statx+0xd7/0x3b0
[...]
Allocated by task 95:
__kmalloc_cache_noprof+0x249/0x560
ntfs_init_fs_context+0x2db/0x5a0
alloc_fs_context+0x52e/0xc70
[...]
Freed by task 98:
kfree+0x1db/0x5b0
deactivate_locked_super+0x9c/0xf0
cleanup_mnt+0x9f/0x130
task_work_run+0x131/0x250
The bug is currently masked by the grace period in namespace_unlock()
for umount(2). Keep the necessary information until the filesystem
private info is freed and free it with an rcu delay.
ntfs3_free_sbi() now runs from an rcu callback and drops the upcase
table via ntfs_put_shared(). So switch s_shared_lock over to
spin_lock_bh(). Otherwise the callback can deadlock against a task
holding the lock on the same cpu.
Fixes:
|
||
|
|
a96c5299bd
|
unicode: free the map after a grace period
generic_ci_d_hash() and generic_ci_d_compare() read sb->s_encoding and
dereference the unicode map while a pathwalk in rcu mode holds nothing
but rcu_read_lock(). The filesystems that use them, ext4, f2fs and
tmpfs, drop the map with utf8_unload() from ->put_super(), which runs
when the last reference to the mount goes away. A lookup that is still
inside such a filesystem at that point reads a freed map.
With a pathwalk held up in utf8_casefold_hash() while the last reference
to a casefolded tmpfs goes away:
BUG: KASAN: slab-use-after-free in utf8nlookup+0x74c/0x950
Read of size 8 at addr ff110001030fe658 by task walker/98
Call Trace:
<TASK>
utf8nlookup+0x74c/0x950
utf8byte+0x160/0x1560
utf8_casefold_hash+0x167/0x230
generic_ci_d_hash+0xdd/0x1c0
link_path_walk+0x11ad/0x1d30
path_lookupat+0x63/0xb00
filename_lookup+0x1f1/0x5b0
vfs_statx+0xd7/0x3b0
[...]
Allocated by task 94:
__kmalloc_cache_noprof+0x249/0x560
utf8_load+0x4e/0x570
shmem_parse_opt_casefold.isra.0+0x92/0x270
[...]
Freed by task 97:
kfree+0x1db/0x5b0
shmem_put_super+0x72/0x170
generic_shutdown_super+0x14c/0x530
kill_anon_super+0x3a/0x60
deactivate_locked_super+0x9c/0xf0
cleanup_mnt+0x9f/0x130
So far that has been hard to hit. umount(2) waits for a grace period
in namespace_unlock() before it drops the mount, so only a mount whose
last reference goes away elsewhere, like the close of a file or a chdir
out of a lazily unmounted directory, can race a walker. So it is
reachable.
Delay the free until a grace period has passed. The map and the data
table it holds a reference to then outlive any pathwalk that found the
superblock before the mount went away. The reference on the data table
module is dropped from the rcu callback as well. utf8-core is built in
whenever CONFIG_UNICODE is set, so there is no module exit that would
have to wait for the callbacks.
Overlayfs points the s_encoding of an overlay at the map of its first
layer without taking a reference of its own. That stays correct. The
layer is held by the overlay, so its map is dropped only after the
overlay is gone.
Fixes:
|
||
|
|
6e6928b396
|
eventfs: free the eventfs_inode after an rcu grace period too
tracefs_d_revalidate() reads ->is_freed of the eventfs_inode that dentry->d_fsdata points to, and ->d_revalidate() is called in rcu pathwalk mode with nothing but rcu_read_lock() held. The dentry itself stays around for such a walker, but tracefs_d_release() drops its reference to the eventfs_inode when the dentry is killed, and release_ei() then frees it after a grace period of eventfs_srcu. An SRCU grace period doesn't wait for rcu_read_lock() sections, so the walker can read an eventfs_inode that is already gone. With a pathwalk held up in tracefs_d_revalidate() while the dentry goes away: BUG: KASAN: slab-use-after-free in tracefs_d_revalidate+0x76/0x90 Read of size 1 at addr ff1100010152d8d0 by task walker/97 Call Trace: <TASK> tracefs_d_revalidate+0x76/0x90 lookup_fast+0x320/0x640 path_lookupat+0x160/0xb00 filename_lookup+0x1f1/0x5b0 vfs_statx+0xd7/0x3b0 vfs_fstatat+0x4d/0x90 __do_sys_newfstatat+0xa0/0x100 do_syscall_64+0x10e/0x5e0 entry_SYSCALL_64_after_hwframe+0x77/0x7f [...] Allocated by task 1: __kmalloc_cache_noprof+0x249/0x560 eventfs_create_dir+0x7a/0x590 event_create_dir+0x2fb/0xcf0 trace_add_event_call+0xbf/0x150 [...] Freed by task 11: kfree+0x1db/0x5b0 srcu_invoke_callbacks+0x3a3/0x620 process_one_work+0x937/0x1810 worker_thread+0x4f1/0xd60 Before commit |
||
|
|
bcec7fc2e2
|
9p: free the session after a grace period
v9fs_vfs_get_link_dotl() reads ->cache from the v9fs_session_info in
sb->s_fs_info to decide whether the symlink target sits in the page
cache. It does that before it looks at the dentry, so it also does it in
rcu pathwalk mode. Without loose or metadata caching the dentries have
no ->d_revalidate() that would make the walk leave rcu mode before it
gets there.
v9fs_kill_super() frees the session and clears sb->s_fs_info without
waiting for a grace period. Right now a pathwalk can't still be in
v9fs_vfs_get_link_dotl() at that point, but only by accident. umount(2)
waits for a grace period in namespace_unlock() before it drops the
mount. And a mount whose last reference goes away elsewhere, like the
chdir out of a lazily unmounted directory, still gets a grace period
from bdi_unregister(). v9fs_fill_super() sets up a private bdi, so
generic_shutdown_super() unregisters it from kill_anon_super() and
bdi_remove_from_list() waits in synchronize_rcu_expedited() before
v9fs_kill_super() frees the session. That grace period protects the rcu
readers of bdi_list in the writeback code. It has nothing to do with
pathwalk.
Don't rely on it. Keep sb->s_fs_info and free the session after a grace
period.
Fixes:
|
||
|
|
59e483739f
|
afs: free the super info after a grace period
afs_atcell_get_link() resolves the @cell and .@cell symlinks of a
dynamic root in rcu pathwalk mode. Before it looks at the dentry it gets
the afs_net through afs_i2net(), which reads ->net_ns from the
afs_super_info in sb->s_fs_info. The @cell dentries are kept around when
unused and the dynamic root has no ->d_revalidate(), so a lookup that
finds @cell in the dcache gets to ->get_link() in rcu mode.
afs_kill_super() frees the afs_super_info with a plain kfree() in
afs_destroy_sbi(). Right now a pathwalk can't still be in
afs_atcell_get_link() at that point, but only by accident. umount(2)
waits for a grace period in namespace_unlock() before it drops the
mount. And a mount whose last reference goes away elsewhere, like the
chdir out of a lazily unmounted directory, still gets a grace period
from bdi_unregister(). afs_fill_super() sets up a private bdi, so
generic_shutdown_super() unregisters it from kill_anon_super() and
bdi_remove_from_list() waits in synchronize_rcu_expedited() before
afs_destroy_sbi() runs. That grace period protects the rcu readers of
bdi_list in the writeback code. It has nothing to do with pathwalk.
Don't rely on it and free the afs_super_info after a grace period.
Fixes:
|
||
|
|
c17b44266a
|
ext4: free the sbi after a grace period
ext4_get_link() follows a symlink that keeps its target in a data block
in rcu pathwalk mode with ext4_getblk(EXT4_GET_BLOCKS_CACHED_NOWAIT).
That reads s_mount_state in the ASSERT() of ext4_getblk() and again in
ext4_es_lookup_extent(), and the lookup bumps the extent status hit or
miss counter, all through sb->s_fs_info.
ext4_put_super() destroys those counters, clears sb->s_fs_info and frees
the sbi without waiting for a grace period. A pathwalk that is still in
ext4_get_link() when the last reference to the mount goes away then
dereferences a NULL s_fs_info, the freed sbi or the destroyed percpu
counters.
With a pathwalk held up in ext4_get_link() while the last reference
goes away:
KASAN: null-ptr-deref in range [0x00000000000000a8-0x00000000000000af]
RIP: 0010:ext4_getblk+0xf0/0x640
Call Trace:
<TASK>
ext4_get_link+0x26c/0x410
pick_link+0xf96/0x1780
step_into_slowpath+0x9b8/0x1250
path_lookupat+0x484/0xb00
filename_lookup+0x1f1/0x5b0
vfs_statx+0xd7/0x3b0
vfs_fstatat+0x4d/0x90
__do_sys_newfstatat+0xa0/0x100
do_syscall_64+0x10e/0x5e0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
So far that has been hard to hit. umount(2) waits for a grace period in
namespace_unlock() before it drops the mount, so only a mount whose last
reference goes away elsewhere, like the close of a file or a chdir out of
a lazily unmounted directory, can race a walker.
Keep sb->s_fs_info and free the sbi together with the extent status
counters after a grace period. Split the counters out of
ext4_es_unregister_shrinker() for that. The shrinker still gets wasted
in ext4_put_super(), the counters go with the sbi.
Fixes:
|
||
|
|
40bb03d3ea
|
pipe: preserve existing fasync readers on writer setup failure
F_SETFL and FIOASYNC decide whether to invoke ->fasync() before
pipe_fasync() takes pipe->mutex. Two enables of one O_RDWR FIFO can
therefore both enter the callback.
fasync_helper() allocates before checking for an existing entry. If the
first enable installs both entries, a second enable can update the
reader entry and then fail the allocation for the writer entry. The
error path removes the pre-existing reader entry, clearing FASYNC while
the writer entry remains registered. __fput() then skips ->fasync(), so a
later pipe notification can dereference the freed file or its f_owner.
An unprivileged memory-pressure reproducer triggers a KASAN
slab-use-after-free in send_sigio() through this path.
Keep the reader helper's result and undo the registration only if this
call added it. The pipe mutex serializes changes to both lists, so a
failed new registration still removes its reader, while a failed
duplicate preserves the existing registrations and FASYNC. Successful
duplicate enables continue to update fa_fd as before.
Fixes:
|
||
|
|
4c43582893
|
ocfs2/dlm: Protect the recovery list during debugfs reads
Recovery participant records must remain alive while debug_state_print()
walks reco.node_data and formats their fields. The formatter holds
dlm->spinlock, but the recovery participant list is protected by
dlm_reco_state_lock. In particular, dlm_destroy_recovery_area() detaches
the list under dlm_reco_state_lock and frees the detached records after
releasing it. A concurrent debugfs open can therefore read freed memory.
Protect the participant traversal and field reads in debug_state_print()
with dlm_reco_state_lock, matching the existing list writers. This also
serializes the traversal against list_add_tail() during recovery-area
initialization. Make the existing lock available to the debug code
through dlmcommon.h; the lock remains internal to the OCFS2 DLM module.
Release dlm->spinlock after formatting the other domain state and before
acquiring dlm_reco_state_lock, so the debug reader does not nest the two
locks. Leave recovery list insertion, detachment and freeing unchanged.
KASAN report as below:
BUG: KASAN: slab-use-after-free in debug_state_open+0x1169/0x12d0
Read of size 4 at addr ffff88810662ef80 by task dlm-state-stres/896
CPU: 1 UID: 0 PID: 896 Comm: dlm-state-stres Not tainted 7.3.0-rc4-next-20260921-pmb-ocfs2-functional-v1+ #1 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? debug_state_open+0x1169/0x12d0
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20e/0x420
? debug_state_open+0x1169/0x12d0
kasan_report+0xe0/0x110
? debug_state_open+0x1169/0x12d0
debug_state_open+0x1169/0x12d0
? __pfx_debug_state_open+0x10/0x10
full_proxy_open_regular+0x193/0x310
do_dentry_open+0x595/0x12d0
? __pfx_full_proxy_open_regular+0x10/0x10
vfs_open_consume+0xd1/0x400
? srso_alias_return_thunk+0x5/0xfbef5
path_openat+0x18d0/0x2020
? __pfx_path_openat+0x10/0x10
do_file_open+0x21e/0x470
? __pfx_do_file_open+0x10/0x10
? _raw_spin_unlock+0x23/0x40
? srso_alias_return_thunk+0x5/0xfbef5
? alloc_fd+0x3a6/0x6b0
do_sys_openat2+0xf4/0x1b0
? __pfx_do_sys_openat2+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __fput+0x5b5/0xa60
__x64_sys_openat+0x136/0x1e0
? __pfx___x64_sys_openat+0x10/0x10
do_syscall_64+0x114/0x620
entry_SYSCALL_64_after_hwframe+0x77/0x7f
[Register dump omitted.]
</TASK>
Allocated by task 880:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x28d/0x610
dlm_remaster_locks+0x147/0x1d90
dlm_do_recovery+0xde1/0x1580
dlm_recovery_thread+0x109/0x300
kthread+0x351/0x460
ret_from_fork+0x659/0x940
ret_from_fork_asm+0x1a/0x30
Freed by task 880:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x5f/0x80
kfree+0x308/0x580
dlm_destroy_recovery_area+0x285/0x460
dlm_remaster_locks+0x17c4/0x1d90
dlm_do_recovery+0xde1/0x1580
dlm_recovery_thread+0x109/0x300
kthread+0x351/0x460
ret_from_fork+0x659/0x940
ret_from_fork_asm+0x1a/0x30
The buggy address belongs to the object at ffff88810662ef80
which belongs to the cache kmalloc-32 of size 32
The buggy address is located 0 bytes inside of
freed 32-byte region [ffff88810662ef80, ffff88810662efa0)
[Page and memory-state dumps omitted.]
Fixes:
|
||
|
|
7dfb218c82 |
xfs: fix exchange-range-to-eof file size exchange
Norbert Szetei posted a patch containing an incomplete description of a
bug in XFS_IOC_EXCHANGE_RANGE's TO_EOF flag. The bug tricks
exchange-range into modifying a file so that it has shared blocks
starting beyond EOF, which is never allowed because files never have
written data beyond EOF, and you can only share written data blocks.
This is key to all the incorrect behavior that follows.
Eventually I got from Norbert a description of what's going wrong:
> Fair. Here it is with physical block numbers, 4k blocks. peer is the file
> that gets rewritten and file1 is the one that ends up with the flag
> cleared.
>
> Start, nothing shared:
>
> peer size 4096 -> [100]
> file1 size 12288 -> [200] [201] [202]
> donor1 size 4096 -> [300]
> donor2 size 4096 -> [400]
>
> 1. FICLONERANGE peer's block into file1's middle slot:
>
> peer size 4096 -> [100]
> file1 size 12288 -> [200] [100] [202] reflink flag set
> ^^^^^ both files own block 100
>
> 2. XFS_IOC_EXCHANGE_RANGE file1 against donor1, file1_offset 0,
> file2_offset 8192, length 0, TO_EOF. One block is exchanged, and the sizes
> are exchanged with it:
>
> file1 size 4096 -> [200] [100] [300] reflink flag set
> ^^^^^^^^^^^ still owned, now past the size
> donor1 size 12288 -> [202]
>
> file1 says it is one block long while it still owns three. Nothing was
> unmapped. file2_offset is block 2, so this exchange cannot clear a flag.
>
> 3. XFS_IOC_EXCHANGE_RANGE file1 against donor2, both offsets 0, length 4096.
> By i_disk_size this is two one-block files exchanging everything, so
> xmi_can_exchange_reflink_flags() agrees and the flag is cleared:
>
> file1 size 4096 -> [400] [100] [300] reflink flag CLEARED
> donor2 size 4096 -> [200]
>
> Block 100 is still shared with peer.
>
> 4. pwrite(file1, 4096, 4096), which is file1's second slot, block 100.
> xfs_is_cow_inode(file1) is false, so no CoW:
>
> peer size 4096 -> [100] same block, new contents
>
> Block 100 is the only one that matters here. PoC below, it reads a file
> called peer in the current directory. The filesystem needs reflink and
> exchange-range, which mkfs.xfs 7.x turns on by default.
So yes, TO_EOF is screwing up the file size even if it's actually
exchanging the data block correctly. The following is the intended
behavior of XFS_EXCHANGE_RANGE_TO_EOF, as described by its manpage:
"Ignore the length parameter. All bytes in file1_fd from file1_offset
to EOF are moved to file2_fd, and file2's size is set to
(file2_offset+(file1_length-file1_offset)). Meanwhile, all bytes in
file2 from file2_offset to EOF are moved to file1 and file1's size is
set to (file1_offset+(file2_length-file2_offset))."
In other words, if we only exchange a portion of the file data, then we
should only exchange the portion of the file size that relates to the
exchanged data. Unfortunately, the code swaps the ondisk file size and
the incore file sizes, which is incorrect if the caller passes in a
nonzero offset.
Having confused the file sizes, the PoC uses a second exchange-range
call on what looks like a non-reflinked single-block file. Because the
file size is set incorrectly, exchange-range thinks it's exchanging the
full contents of two files and clears the reflink flag on the broken
file. That enables an extending write of the broken file to rewrite the
shared block that's just past EOF. This has become known colloquially
as refluxfs.
Once the file sizes are set correctly, the second exchange-range no
longer thinks that it's doing a full-contents swap, so it won't clear
the reflink flag on either of its file arguments.
Norbert followed up to an earlier version of this patch with a second
complaint about us not handling the i_disk_size state correctly:
> The offsets are bounded against the incore i_size but subtracted from
> the ondisk i_disk_size, so a file that has not been written back gives
> xmi_isize1 = 0 + (0 - 12288).
>
> For instance, you can do:
>
> $ xfs_io -f -c "pwrite -q 0 4096" -c fsync file1
> $ xfs_io -f -c "pwrite -q 0 12288" \
> -c "exchangerange -s 0 -d 12288 file1" dirty
> $ stat -c %s file1
> 0
> $ stat -c %i file1
> 131
> $ sudo xfs_io -r -c "bulkstat_single 131" . | grep bs_size
> bs_size = 18446744073709539328
That's wrong, and the new file size update logic depends on the ondisk
i_disk_size matching the incore i_size, so flush the pagecache as needed
to push it upwards.
Link: https://lore.kernel.org/linux-xfs/1E196589-DEBE-40AC-AFEA-D420DAAB067F@doyensec.com/
Reported-by: Norbert Szetei <norbert@doyensec.com>
Cc: stable@vger.kernel.org # v6.10
Fixes:
|
||
|
|
d84d6c1a7f |
xfs: stop exchanging reflink flags when exchanging mappings
When an exchange covers both files from offset 0 to i_disk_size, xmi_can_exchange_reflink_flags() assumes every mapping moved and hands the reflink flag over to the other file. That assumption is wrong. A file can still own shared blocks past i_disk_size, e.g. after EXCHANGE_RANGE_TO_EOF sets a smaller size without unmapping anything. The flag is cleared anyway, and the next write to such a block happens in place instead of CoW, corrupting the other file that shares it. Commit |
||
|
|
fc1c25014f |
fsverity fix for v7.3-rc7
Fix a regression from commit |
||
|
|
f99005fbed |
vfs-7.3-rc7.fixes
Please consider pulling these changes from the signed vfs-7.3-rc7.fixes tag.
Thanks!
Christian
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCasilxwAKCRCRxhvAZXjc
ooQTAQD6awSrQcUIG0ca5hbowpn7sLi8kWGnmzf2yydKSyp3bwEAnpSQEw3BXUKH
0O7JEyZFL0VcMTTceSPKkKCqGg5nugI=
=Wc48
-----END PGP SIGNATURE-----
Merge tag 'vfs-7.3-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner:
"This contains fixes for the current development cycle.
All of them came out of a review of the mount code that started with a
bug report. The review modeled the corner cases of mount propagation,
unmounting and mount reference counting and turned up a lot of bugs.
Most of them years old. Most fixes come with a selftest.
- Rework connected mounts.
A mount that is unmounted together with its parent can stay
attached to the parent to keep its mountpoint covered. That happens
when the mountpoint is removed with rmdir(), unlink() or rename(),
when a detached tree is dissolved, and for locked mounts in any
umount that isn't synchronous, including the teardown of their
mount namespace. The parent then owns the child and drops it on its
own final mntput(). So any reference from the child's superblock
back to one of its ancestors becomes a cycle that is never freed.
A loop device backed by an image on a tmpfs and mounted on that
same tmpfs is enough. Remove the directory the tmpfs is mounted on
from the host, let the container's mount namespace exit, and the
loop device, the tmpfs and the filesystem on the loop device are
leaked for good. The same works with autofs, zram, ecryptfs,
binfmt_misc, fuse passthrough, zloop, a mass storage gadget and md,
and the selftests have reproducers for them. This has been possible
since v4.1. It is also why "put_mnt_ns(): leave mounts connected"
was reverted in -rc5. Keeping every mount of a dying mount
namespace connected made these cycles trivial to create.
Every unmounted mount is now detached from its parent. Where the
mountpoint has to stay covered the mount leaves a cover on the
parent instead, allocated together with the mount. A lookup on the
unmounted parent that hits a cover finds an empty immutable
directory or file on the private nullfs instance. Nothing leads
from a cover to another mount, so no unmounted mount owns another
one and no cycle can form.
This is visible to userspace. A formerly connected mount can no
longer be reached through its unmounted parent and ".." inside it
leads nowhere, as for every other lazily unmounted mount.
With that the private nullfs instance becomes reachable from
userspace, so it now refuses mounts on top, is mounted read-only,
and refuses fsnotify marks and file locks. Its inodes are shared by
every holder and a watch or a lock would otherwise reach across
users. may_decode_fh() now also decides its subtree check under a
single mount_lock hold, as a racing umount could otherwise let it
decode into what a locked child covered.
- umount:
* Don't silently unmount busy mounts.
Since v4.13 propagate_umount() takes down propagated copies of
the victim with children as long as each child is an overmount or
another copy of the victim, but propagate_mount_busy() only ever
checked copies without children or with just an overmount. A
container that moved a tree beneath its copy of a host mount lost
that tree from under its open file descriptor to a plain umount()
on the host. propagate_mount_busy() now applies the same rules,
walking each chain of copies once.
* Don't let a migrating task hide its reference from umount().
mnt_get_count() sums the per-cpu counters under mount_lock but
the mntget() and mntput() fast paths don't take it. A task that
takes a reference on a cpu the sum has already passed and drops
it after migrating to one the sum hasn't reached yet hides the
reference it held to begin with, and umount() succeeds with the
file still open. Gets and puts now live in separate per-cpu
counters and all puts are summed before all gets with a full
barrier in between, the way srcu_readers_active_idx_check() does
it. mntget() is unchanged and mntput() gains an smp_wmb().
* Check each submount for references right before unmounting it.
shrink_submounts() and mark_mounts_for_expiry() checked all their
victims up front. Unmounting the first could move a busy
overmount to where the next victim's propagated copy is looked up
and it was then unmounted without a check.
* Never expire a locked mount.
A shrinkable mount moved beneath a locked mount with
MOVE_MOUNT_BENEATH takes over the lock, and umount() of an
unlocked ancestor expired it and revealed what it covered. That
umount() now fails with EBUSY as it does for any other locked
child. A lazy umount still takes the whole tree.
- Overmounts and locked mounts:
* Unhash a dentry before detaching the mounts on it.
unlink(), rmdir() and rename() detach the mounts on the victim
but only d_delete() it once its inode is unlocked, a window that
includes an expedited RCU grace period. In between, a lookup from
a mount namespace in which the dentry is a mountpoint found it
hashed, positive and uncovered. Drop the dentry first, as
d_invalidate() already does.
* Don't reveal overmounted entries in refwalk.
A refwalk that had grabbed the dentry before the unlink never
rechecked it the way rcuwalk does with d_seq and mount_lock.
Without any artificial widening three walkers read the covered
file 27 times in a minute. step_into() now fails an unhashed
dentry marked DCACHE_CANT_MOUNT with -ESTALE and the walk is
retried.
* Keep covered mounts covered in OPEN_TREE_NAMESPACE.
Creating such a mount namespace only takes a user namespace and
the copy followed bind mount rules: no children without
AT_RECURSIVE and no unbindable mounts with it. An unprivileged
user could see what mounts covered in the source, such as the
parts of /proc and /sys that container runtimes mask. If the
caller doesn't own the source mount namespace a non-recursive
copy of a mount with something mounted below the requested
directory is now refused and a recursive copy includes unbindable
mounts, the way unshare() copies.
* Keep the lock on a mount that a propagated copy is moved beneath.
MNT_LOCKED moved to any mount that ended up beneath a locked
mount, propagated copies included. A host mount and umount on a
directory covered by a locked mount in a less privileged mount
namespace left that cover unlocked for the namespace's owner to
remove. Only mounts the caller places beneath take over the lock
now.
* Handle mount locking for automounts correctly. Which copies to
lock was decided by the mount namespace of the task that
triggered the automount. A task in a user namespace that
triggered one on a host mount through a file descriptor got the
host's own automount locked while its own copy stayed unlocked
and could have nosuid, nodev and noexec cleared. Use the owner of
the mount namespace the mount lands in.
- Use-after-free and crashes:
* Refuse an automount below a mount that is in no namespace.
The private clones overlayfs uses for its layers have the
MNT_NS_INTERNAL error pointer as their namespace, which
finish_automount() let through and count_mounts() dereferenced. A
fanotify filesystem mark on an overlayfs lower layer hands out
file descriptors on such a clone. With debugfs as the lower layer
opening "tracing" oopses with namespace_sem held for writing and
every mount operation on the system blocks from then on.
* Reset the old parent's ->overmount in mnt_change_mountpoint().
When propagate_umount() moved an overmount off a mount that a
file descriptor kept alive, MOVE_MOUNT_BENEATH through that
descriptor later followed the stale pointer into the freed
overmount.
* statmount() with STATMOUNT_BY_FD and pivot_root() read the parent
of a mount that may be unmounted and only held by a file
descriptor, while the parent's final mntput() can free it.
statmount() now reads it under mount_lock and pivot_root() first
checks that both mounts are in the caller's mount namespace.
* Queue a mount only once for mount notifications. A mount
reparented by one umount_tree() and taken down by the next under
the same namespace_sem hold, as in shrink_submounts(), was queued
twice. That cut the mounts queued in between out of notify_list
while it still pointed at them, and once they were freed every
later mount operation walked freed memory.
* Don't let a pseudo dentry become the root of a mount. A bind
mount of a bpf token file did that with a DCACHE_NORCU dentry,
which is freed without an RCU grace period while lockless path
walks may still look at it. Refuse to clone such a mount.
* Don't inherit MNT_UMOUNT in clone_mnt(). A bind mount of a lazily
unmounted nsfs or pidfs mount through its file descriptor started
out flagged as unmounted. Among other things __detach_mounts()
then dropped the namespace's reference on it, the mount outlived
its namespace and mount_setattr() through the descriptor read the
freed namespace. A recursive bind mount of such a mount also
copied the unmounted stack still attached to it. That now fails
with EINVAL, copying the mount itself still works.
* Remove the fsnotify marks of a mount namespace in free_mnt_ns()
instead of the RCU callback that frees the namespace, where
taking the group mutexes meant sleeping in softirq context.
- Propagation and copies:
* Keep a copied mount unbindable. Since v6.17 clone_mnt() didn't
copy the unbindable flag, so every mount namespace created with
CLONE_NEWNS had bindable copies of all unbindable mounts. This
had been fixed once before.
* Refuse MOVE_MOUNT_SET_GROUP on an unbindable mount. It made the
mount an unbindable slave, a state nothing else can produce, or
silently dropped the unbindable flag. CRIU applies MS_UNBINDABLE
after restoring sharing and isn't affected.
* Check a recursive bind mount for mount namespace loops.
Recursively bind mounting a tree from another mount namespace
could put a mount of a namespace's file inside that same
namespace, which then pins itself and all its mounts. Repeating
it leaks without limit, the reproducer took Shmem from 380 kB to
65916 kB. The copy is now checked with check_for_nsfs_mounts()
before it is grafted, as move_mount() does.
* Look at the topmost mount for a mount namespace file.
attach_recursive_mnt() never looked at the topmost mount of the
source's chain of overmounts. If that was the chain's only mount
namespace file an existing mount at a propagated destination got
buried below the root of the nsfs file where no path walk reaches
it.
* Don't put a mountpoint on a dentry that's being removed.
attach_recursive_mnt() makes a mountpoint of the source's root
without its inode lock, so a racing rmdir() of that directory
could leave a mount on it that nothing ever detaches.
d_set_mounted() now checks cant_mount() as well.
- nullfs:
* Take no inode lock for readdir of an immutable directory.
The root of every empty mount namespace is the same nullfs
directory and iterate_dir() held its i_rwsem across
->iterate_shared(). A reader whose buffer faults on a FUSE mount
of its own holds it for as long as its server wants, and with an
exclusive locker queued behind it every lookup that misses the
dcache, every create and every mount in that directory waits. One
user of an empty mount namespace stalls all others. Directories
with the new FOP_IMMUTABLE flag skip the lock.
* Refuse to reconfigure internal superblocks through fspick(),
MS_REMOUNT or the read-only remount that a synchronous umount()
of the root does. For nullfs only root in the initial user
namespace could do it, but the superblock is shared by every
mount namespace and the flags showed up in statfs() for all of
them.
* Don't update the access time on nullfs and refuse F_SET_RW_HINT
on an immutable inode.
- unshare: Free an nsproxy that was never installed with
nsproxy_free() when set_cred_ucounts() fails. put_nsproxy() dropped
active references that were never taken, which triggered a warning
and hid the caller's own namespaces from listns().
- Smaller changes: mount_setattr() checks the target before it walks
the tree to allocate peer group ids, unshare() puts the old
fs_struct before the old namespaces, dissolve_on_fput() drops the
file's reference to the tree itself, disconnect_mount() is
simplified and the documentation of the propagated unmount rule is
brought up to date"
* tag 'vfs-7.3-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (58 commits)
namespace: simplify disconnect_mount()
selftests/filesystems: test covered mounts
namespace: rework connected mounts
nullfs: add an empty immutable regular file
selftests/filesystems: check that reading the root of an empty mount namespace stalls nobody
selftests/filesystems: add a helper that holds a readdir in a page fault
readdir: take no inode lock on an immutable directory
nullfs: refuse file locks
fsnotify: let a filesystem refuse marks on its objects
namespace: nothing is mounted on or written through knullfs
namespace: keep the private nullfs instance in knullfs
fhandle: decide the subtree check under mount_lock
selftests/filesystems: check that an automount below an overlay layer is refused
selftests/filesystems: check the atime of the empty mount namespace root
selftests/filesystems: check that a lock lands on the right mount and stays
namespace: keep the lock on a mount that a propagated copy is moved beneath
namespace: never expire a locked mount
nullfs: don't update the access time
namespace: handle mount locking for automounts correctly
namespace: refuse an automount below a mount that is in no namespace
...
|
||
|
|
6c377d19d4 |
Mark the ext4 data=journal feature as being deprecated and will be
removed in 2028. Also designate the primary branch that Sashiko and other tools use to find the primary development branch in the ext4 tree. -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEK2m5VNv+CHkogTfJ8vlZVpUNgaMFAmrHtq4ACgkQ8vlZVpUN gaMFZAf/eACeBhy6Tm9s6MDAewS84BDeNd/WuUrIn+Jpt1jwpL83sNPY3rgleR7s spPpg7fJQJfFbRiyOj4YUNbwShCOJ61cOw/BrLBYD5HlkCNuexBNWEOEVB7HWAVv f+J7FLF8k/6lgzuXI4CNzC4NIpKiWLUi3zbGbjxZgtdsr1j7zThVn83WwuHqnJic dLlW5Twcwox0j2UDKRmERL1x8QBeghJPSX9u1xNEtx8/gEkAKGJ601MrbVmZPDDy FuXZUdOuyctLuiFMjvB20jVXMVCO0SMj5DiJ8vwGYV/OHVYcLxx7WP7KxJ/IKmK8 lk748C174vVDTLTSzBDIAnqctfkUxQ== =zILx -----END PGP SIGNATURE----- Merge tag 'ext4_for_linus-7.3-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4 Pull ext4 fixes from Ted Ts'o: "Mark the ext4 data=journal feature as being deprecated and will be removed in 2028. Also designate the primary branch that Sashiko and other tools use to find the primary development branch in the ext4 tree" * tag 'ext4_for_linus-7.3-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: ext4: mark data=journal as deprecated and will be removed in January 2028. MAINTAINERS: name the ext4 dev branch |
||
|
|
3e78d7b063 |
ext4: mark data=journal as deprecated and will be removed in January 2028.
Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
||
|
|
7b63ef2d55 |
for-7.3-rc6-tag
-----BEGIN PGP SIGNATURE----- iQJPBAABCgA5FiEE8rQSAMVO+zA4DBdWxWXV+ddtWDsFAmrGSIUbFIAAAAAABAAO bWFudTIsMi41KzEuMTIsMiwyAAoJEMVl1fnXbVg7eQEP/0T5xO7uJw7S42kKi4w+ J/XP482GUb5xv0BmwOlITl5dGQHAHWKtq7zXflA+pNSoPPWUq+eTfKw1r/yYHGtu S1kHei3K3l9qWpeIOJYTdpcVayFRdn+ZZuMwvb5uojT7VbtV2UB01LQfMWM8mcaY PukCIMX+PiM8ue9l2iQZ5EXzFoaJzKpvyZP0T7ar+IIRsfE3n0qMmZeGiYKuCJOF A9N2IWNU/UIUtHpZGHZYwVzGuFdDH3Dc5b0ZzQUlL/dUvZ6345bX6NCXLceU+II1 SjlQwEKLpfY0IxfwnR3cLkAu+YE5ER6VfHCzcvVV1mZFVOQwF6+UuMTrZ3AdO6tX RFHnhVUVZSvULCRJlpsV5LIALDow/vSAif7n02oh3j2pou50UQVLI1q5olZhPOLS rpYSF7HIpj2R/1MStDko9FTp78atG+0g6DlZDsz1Dys3l0gaKx+WJ/tzh60cvPj8 aM2zck1Dw/9wsg8KMXLqlllFQRoCavmngmttONxqXYlRwz8h9CSf4uLPZzgSzORm /MR0Cb+o5W3c1LJgjRsdXa/j0NTdKGmGqbWRN+Hnwi3ZbR3GC5+HL7mGpH28TqHU /X93+/pDoGR11E8VMMgRkTDJwfuDPheKno98lwkhpC9x6znXwuhh43/6iT/gYA9k cbklJF1ok+18QTQLPTTSyIYf =Mk4y -----END PGP SIGNATURE----- Merge tag 'for-7.3-rc6-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux Pull btrfs fixes from David Sterba: - fix command queuing and cleanup in encoded read/write ioctls - fix root and transaction association to avoid unnecessary lock contention and transaction start - properly handle replacing multiple xattrs in the same item - in scrub, fix root reference leak after reporting an unresolved file path * tag 'for-7.3-rc6-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux: btrfs: fix lost error return value in btrfs_listxattr() btrfs: fix xattr replace when multiple xattrs are packed in the same item btrfs: don't stash io_uring encoded data across -EAGAIN btrfs: unlock inode and extent in caller when io_uring read extent fails btrfs: free iov when btrfs_uring_read_extent() fails btrfs: always return -EIOCBQUEUED after btrfs_uring_read_extent_endio btrfs: scrub: fix local_root reference leak in scrub_print_warning_inode() btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() |
||
|
|
3d39922442
|
namespace: simplify disconnect_mount()
Rename disconnect_mount() and simplify it. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
01cbebb420
|
namespace: rework connected mounts
UMOUNT_CONNECTED as implemented allows for the creation of reference
count cycles. Here's a simple example
mkdir /x; mkfifo /ready /go
unshare -m sh -c 'mount -t tmpfs tmpfs /x
truncate -s 8M /x/img; mkfs.ext4 -q /x/img
dev=$(losetup -f --show /x/img)
mkdir /x/mp; mount $dev /x/mp
echo $dev > /ready; read r < /go' &
read dev < /ready
rmdir /x
echo > /go
wait
losetup -d $dev
losetup -a
Take a directory /x on the host, create a new mount namespace, mount a
tmpfs on /x, use a file on that tmpfs as the backing file for a loop
device, mount that loop device on that tmpfs. Now rmdir /x on the host.
This will lazily unmount the mount on top of /x in the container with
UMOUNT_CONNECTED. Once the namespace exits nothing references the mount
anymore. Now the tmpfs is pinned by the backing file of the loop device
and the loop mount is owned by the tmpfs superblock.
Fun fact, such cycles can be formed by at least the following
subsystems and I have added reproducers for all of them:
(1) a loop mount P from an image on a tmpfs next to it, so that P's
death shows as the loop device giving up its backing file
(2) autofs with a FIFO on P as its pipe, zram with a device node on P as
its writeback device, both on a minix image since vfat has neither
(3) ecryptfs with its lower directory on P, under a passphrase token
added to the session keyring
(4) binfmt_misc in a new user namespace with an 'F' interpreter on P
(5) a fuse server that answers FUSE_INIT with passthrough on and
registers a file on P as a backing file
(6) zloop with its zone files in a directory on P
(7) a mass storage gadget on the dummy UDC with its LUN file on P,
mounted from the SCSI disk the gadget shows up as
(8) md with a RAID1 of one loop device and its bitmap file on P, which
skips while SET_BITMAP_FILE has no way to succeed
(9) rmdir of P's mountpoint from the parent, then the child exits, then
the device must be free and LOOP_CLR_FD must release the file
The underlying mechanism is UMOUNT_CONNECTED (MNT_LOCKED falls into the
same class). With UMOUNT_CONNECTED an unmounted mount stays attached to
its parent. This is used to protect revealing the underlying mount and
is a non-negotiable security mechanism. So now the parent owns that
mount and is put on the parent's final mntput(). That moves it to
mnt_stuck_children and ultimately it's cleaned up by cleanup_mnt().
The fact that ownership of the child mount gets transferred to the
parent turns every reference from a child's superblock back to one of
its ancestors into a cycle.
Don't keep the child attached at all. What the parent needs is that a
lookup at the child's mountpoint keeps finding some mount, not the child
itself and it's not a guarantee we have given really.
When an unmounted mount would have stayed attached to its unmounted
parent disconnect it like every other unmounted mount and leave a
marker behind.
A lookup on the parent that misses the mount hash and hits a marker
finds knullfs. Either a file or a directory. Nothing leads from a marker
to any other mount.
The marker is owned by the parent and dropped by the parent's final
mntput() or by __detach_mounts() when the mountpoint is deleted from
under it. It is allocated together with the mount.
With that every unmounted mount is a root and holds only its own
reference which namespace_unlock() drops. No unmounted mount owns
another one. A superblock that pins an ancestor can't form a cycle.
It has a visible change. Mounts left connected (rmdir etc.) used to stay
traversable through the parent for as long as something held the parent.
Now it is detached with the umount. It lives as long as something
references it but it isn't reachable through the parent anymore and ".."
inside it leads nowhere which is the same as for every other lazily
unmounted mount.
Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f
Link: https://patch.msgid.link/20261002-work-mount-cover-v1-2-232a8f52b43c@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
||
|
|
54867884fe
|
nullfs: add an empty immutable regular file
Add nullfs_new_file() to allocate an empty immutable regular file on a nullfs instance as a dentry of its own. It is never hashed under the root and so can't be found by lookup. Reads return nothing, changes are refused, file locks, leases and delegations are refused as. Link: https://patch.msgid.link/20261002-work-mount-cover-v1-1-232a8f52b43c@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
41c8899d59 |
btrfs: fix lost error return value in btrfs_listxattr()
If the input buffer does not have enough space to store the current xattr,
we set 'iter_ret' to -ERANGE and then do "break", but that only exits the
while loop over the xattrs in the current btrfs_dir_item, and then we
continue the btrfs_for_each_slot() iteration, which overwrites the value
of 'iter_ret' causing us to lose the error return value and proceed as if
the buffer has enough space.
Fix this by returning -ERANGE directly (the path is automatically freed)
instead of breaking from the while loop.
Fixes:
|
||
|
|
858bee1c77 |
btrfs: fix xattr replace when multiple xattrs are packed in the same item
If we have a btrfs_dir_item item that packs multiple xattrs and then we
replace the value of one of them (with the setxattr(2) family of syscalls)
with another value of a different size, we end up not having a fully
initialized btrfs_dir_item, resulting in a corruption that the tree
checker will detect at extent buffer writeback time.
This is because in btrfs_setxattr() when we find a btrfs_dir_item with
multiple xattrs (due to the crc32c hash of their name being the same)
we delete one of the xattr items (btrfs_dir_item) and then insert a new
one, but the deletion and insertion results in shifting existing data in
the leaf and therefore when the new value of a xattr has a different size,
the new btrfs_dir_item is placed in a leaf section that was not
initialized and we only copy the value's data and set the value's length
in the new btrfs_dir_item, without setting the name, the name's length,
the key (which must be all zeroes for xattrs), flags (BTRFS_FT_XATTR) and
transaction ID.
The following script reproduces the issue:
$ cat test.sh
#!/bin/bash
DEV=/dev/sdi
MNT=/mnt/sdi
mkfs.btrfs -f $DEV
mount $DEV $MNT
touch $MNT/testfile
# Add two xattrs that, on btrfs, have the same hash (crc32c) for their
# name and therefore are packed into the same btrfs_dir_item.
setfattr -n user.foobar -v 123 $MNT/testfile
setfattr -n user.WvG1c1Td -v qwerty $MNT/testfile
# Verify the xattrs are present.
echo "xattrs before:"
getfattr --absolute-names --dump $MNT/testfile
# Now replace the value of the foobar xattr with a significantly larger
# value.
setfattr -n user.foobar -v abcdefghijklmnopqrstuvwxyz $MNT/testfile
# Check the xattrs have the expected values.
echo "xattrs after:"
getfattr --absolute-names --dump $MNT/testfile
umount $MNT
Running it:
$ ./test.sh
(...)
xattrs before:
# file: /mnt/sdi/testfile
user.WvG1c1Td="qwerty"
user.foobar="123"
xattrs after:
# file: /mnt/sdi/testfile
user.WvG1c1Td="qwerty"
So the "user.foobar" xattr is missing and there was a transaction abort
when unmounting the fs with the following traces in dmesg:
$ dmesg
[869800.159271] BTRFS warning (device sdi): access to eb bytenr 30474240 len 16384 out of range start 16015 len 25964
[869800.159293] ------------[ cut here ]------------
[869800.159296] WARNING: fs/btrfs/extent_io.c:4408 at report_eb_range+0x44/0x60 [btrfs], CPU#8: getfattr/2605179
[869800.168961] Modules linked in: btrfs dm_thin_pool (...)
[869800.190527] CPU: 8 UID: 0 PID: 2605179 Comm: getfattr Tainted: G W 7.3.0-rc3-btrfs-next-244+ #1 PREEMPT(full)
[869800.193726] Tainted: [W]=WARN
[869800.194502] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014
[869800.197576] RIP: 0010:report_eb_range+0x44/0x60 [btrfs]
[869800.198985] Code: 48 8b 7b 18 (...)
[869800.203531] RSP: 0018:ffffce4541937d58 EFLAGS: 00010246
[869800.204609] RAX: 0000000000000000 RBX: ffff8dde054b8738 RCX: 0000000000000000
[869800.206137] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffffffffc04c92a0
[869800.207645] RBP: 0000000000003e8f R08: 0000000000000000 R09: 3fffffffffefffff
[869800.226175] R10: ffffce4541937a88 R11: 0000000000000003 R12: 000000000000656c
[869800.227594] R13: ffff8dde14be800f R14: 000000000000656c R15: 0000000000000069
[869800.229097] FS: 00007f59023bb780(0000) GS:ffff8de5788ed000(0000) knlGS:0000000000000000
[869800.231137] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[869800.232321] CR2: 0000558dc45e6a78 CR3: 0000000765b16004 CR4: 0000000000370ef0
[869800.233783] Call Trace:
[869800.234330] <TASK>
[869800.234791] read_extent_buffer+0x4d/0x100 [btrfs]
[869800.235898] btrfs_listxattr+0x199/0x240 [btrfs]
[869800.236912] vfs_listxattr+0x51/0xa0
[869800.237683] listxattr+0x7e/0x100
[869800.238398] path_listxattrat+0x9e/0x190
[869800.239117] do_syscall_64+0x89/0x470
[869800.239877] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[869800.240914] RIP: 0033:0x7f59024cdcb7
[869800.241686] Code: f0 ff ff 73 (...)
[869800.245353] RSP: 002b:00007ffd056dcdb8 EFLAGS: 00000246 ORIG_RAX: 00000000000000c2
[869800.246892] RAX: ffffffffffffffda RBX: 00007ffd056df2e2 RCX: 00007f59024cdcb7
[869800.248853] RDX: 0000000000006600 RSI: 0000558dc45e0470 RDI: 00007ffd056df2e2
[869800.250514] RBP: 00007ffd056df2e2 R08: 0000000000006600 R09: 0000000000006600
[869800.252295] R10: 0000000000000004 R11: 0000000000000246 R12: 00000000ffffff9c
[869800.254105] R13: 0000558dc45e0470 R14: 0000000000006600 R15: 0000000000000000
[869800.255931] </TASK>
[869800.256529] ---[ end trace 0000000000000000 ]---
[869800.260071] page: refcount:2 mapcount:0 mapping:000000007ccfc77f index:0x1d10 pfn:0x608c69
[869800.260075] memcg:ffff8dde00344d40
[869800.260076] aops:btree_aops [btrfs] ino:1
[869800.260151] flags: 0x17fffc00000402a(uptodate|lru|private|writeback|node=0|zone=2|lastcpupid=0x1ffff)
[869800.260154] raw: 017fffc00000402a fffff4adc773d4c8 fffff4adc48f3d88 ffff8de36504ba30
[869800.260155] raw: 0000000000001d10 ffff8dde054b8738 00000002ffffffff ffff8dde00344d40
[869800.260156] page dumped because: eb page dump
[869800.260157] BTRFS critical (device sdi): corrupt leaf: root=5 block=30474240 slot=5 ino=257, invalid location key type, have 46, expect 132 or 1
[869800.260161] BTRFS info (device sdi): leaf 30474240 gen 9 total ptrs 6 free space 15629 owner 5
[869800.260163] BTRFS info (device sdi): refs 3 lock_owner 0 current 2550294
[869800.260164] item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160
[869800.260165] inode generation 3 transid 0 size 0 nbytes 16384
[869800.260166] block group 0 mode 40755 links 1 uid 0 gid 0
[869800.260167] rdev 0 sequence 0 flags 0x0
[869800.260168] atime 1790340637.0
[869800.260169] ctime 1790340637.0
[869800.260169] mtime 1790340637.0
[869800.260170] otime 1790340637.0
[869800.260170] item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12
[869800.260172] index 0 name_len 2
[869800.260172] item 2 key (256 DIR_ITEM 982728850) itemoff 16073 itemsize 38
[869800.260173] location key (257 1 0) type 1
[869800.260174] transid 9 data_len 0 name_len 8
[869800.260175] item 3 key (257 INODE_ITEM 0) itemoff 15913 itemsize 160
[869800.260176] inode generation 9 transid 9 size 0 nbytes 0
[869800.260177] block group 0 mode 100664 links 1 uid 0 gid 0
[869800.260177] rdev 0 sequence 0 flags 0x0
[869800.260178] atime 1790340638.38778502
[869800.260179] ctime 1790340638.38778502
[869800.260179] mtime 1790340638.38778502
[869800.260180] otime 1790340638.38778502
[869800.260180] item 4 key (257 INODE_REF 256) itemoff 15895 itemsize 18
[869800.260181] index 2 name_len 8
[869800.260182] item 5 key (257 XATTR_ITEM 751495445) itemoff 15779 itemsize 116
[869800.260183] location key (0 0 0) type 8
[869800.266052] transid 9 data_len 6 name_len 13
[869800.266053] location key (8243121639454149888 46 7229457603934778967) type 0
[869800.266055] transid 113 data_len 26 name_len 0
[869800.266056] location key (8608196880778817904 120 162425) type 9
[869800.266057] transid 8391162079612502016 data_len 26982 name_len 25964
[869800.266058] BTRFS error (device sdi): block=30474240 write time tree block corruption detected
[869800.266091] ------------[ cut here ]------------
[869800.266092] WARNING: fs/btrfs/disk-io.c:336 at btree_csum_one_bio+0x20b/0x220 [btrfs], CPU#7: kworker/u50:7/2550294
[869800.268392] Modules linked in: btrfs dm_thin_pool (...)
[869800.365851] CPU: 7 UID: 0 PID: 2550294 Comm: kworker/u50:7 Tainted: G W 7.3.0-rc3-btrfs-next-244+ #1 PREEMPT(full)
[869800.368937] Tainted: [W]=WARN
[869800.369834] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014
[869800.372778] Workqueue: writeback wb_workfn (flush-btrfs-3821)
[869800.374314] RIP: 0010:btree_csum_one_bio+0x20b/0x220 [btrfs]
[869800.375915] Code: 89 44 24 04 (...)
[869800.380639] RSP: 0018:ffffce4548e3f7d0 EFLAGS: 00010246
[869800.382008] RAX: 0000000000000000 RBX: ffff8dde054b8738 RCX: 0000000000000000
[869800.383850] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8de091c2ddc0
[869800.385710] RBP: ffff8dde196a2000 R08: 0000000000000000 R09: 3fffffffffefffff
[869800.387388] R10: ffffce4548e3f500 R11: 0000000000000003 R12: ffffce4548e3f7d8
[869800.388973] R13: ffff8dde196a2000 R14: ffff8de36504b750 R15: ffff8dde4c497b00
[869800.390414] FS: 0000000000000000(0000) GS:ffff8de5788ad000(0000) knlGS:0000000000000000
[869800.392002] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[869800.393160] CR2: 000055cd6e92ad5c CR3: 00000007cb264001 CR4: 0000000000370ef0
[869800.394595] Call Trace:
[869800.395113] <TASK>
[869800.395570] btrfs_submit_bbio+0x872/0x890 [btrfs]
[869800.397284] write_meta_extent_buffer+0x70/0x80 [btrfs]
[869800.398940] btree_writepages+0x141/0x4f0 [btrfs]
[869800.400426] ? get_random_u32+0x8a/0xf0
[869800.401417] ? build_slab_freelist+0x47/0x130
[869800.402574] ? preempt_count_add+0x6b/0xa0
[869800.403633] ? _raw_spin_lock_irqsave+0x23/0x50
[869800.404807] ? _raw_spin_unlock_irqrestore+0x22/0x40
[869800.406085] ? alloc_from_new_slab+0x18f/0x330
[869800.407223] do_writepages+0xc6/0x160
[869800.408191] ? refill_objects+0xd8/0x300
[869800.409211] __writeback_single_inode+0x42/0x350
[869800.410408] writeback_sb_inodes+0x231/0x560
[869800.411511] wb_writeback+0x8a/0x300
[869800.412440] wb_workfn+0xbf/0x460
[869800.413291] ? _raw_spin_unlock+0x14/0x30
[869800.414328] ? finish_task_switch.isra.0+0xb9/0x380
[869800.415105] process_one_work+0x1d1/0x3d0
[869800.416633] worker_thread+0x1c4/0x330
[869800.417467] ? __pfx_worker_thread+0x10/0x10
[869800.418452] kthread+0xfc/0x130
[869800.419257] ? __pfx_kthread+0x10/0x10
[869800.420089] ret_from_fork+0x1f7/0x2c0
[869800.420863] ? __pfx_kthread+0x10/0x10
[869800.421654] ret_from_fork_asm+0x1a/0x30
[869800.422484] </TASK>
[869800.422953] ---[ end trace 0000000000000000 ]---
[869800.424005] BTRFS error (device sdi state A): Transaction 9 aborted (-EIO)
[869800.424010] BTRFS: error (device sdi state A) in __btrfs_run_delayed_items:1162: errno=-5 IO failure
[869800.424011] BTRFS info (device sdi state EA): forced readonly
[869800.424013] BTRFS warning (device sdi state EA): Skipping commit of aborted transaction.
[869800.424014] BTRFS: error (device sdi state EA) in cleanup_transaction:2076: errno=-5 IO failure
Fix this by always setting all fields in the new btrfs_dir_item when we
replace an existing xattr.
Fixes:
|
||
|
|
51562a8cb1 |
btrfs: don't stash io_uring encoded data across -EAGAIN
Returning -EAGAIN while leaving btrfs_uring_encoded_data in the cmd PDU leaks if the request is cancelled or the ring exits before reissue. io_uring does not free driver PDU allocations on cleanup. Write: io_queue_sqe() always issues with IO_URING_F_NONBLOCK first, so return -EAGAIN before allocating and free data on every exit. Read: free on nowait -EAGAIN too; only -EIOCBQUEUED keeps the allocation for btrfs_uring_read_finished(). Fixes: |
||
|
|
b6e8add5d7 |
btrfs: unlock inode and extent in caller when io_uring read extent fails
btrfs_uring_read_extent() runs only after btrfs_encoded_read() has taken
the inode shared lock and the extent lock. On failure it used to unlock
in out_fail, and a pages-array allocation failure returned -ENOMEM
without unlocking at all.
Unlock in the caller instead on all failure returns, matching the
copy_to_user() error path. The deferred -EIOCBQUEUED path still unlocks
in btrfs_uring_read_finished().
Fixes:
|
||
|
|
a8ea92830b |
btrfs: free iov when btrfs_uring_read_extent() fails
After btrfs_uring_read_extent(), the caller always jumped to out_acct.
That skips kfree(data->iov), which is only correct for -EIOCBQUEUED
where the deferred path owns the iov. On failure, fall through to
out_free instead.
Fixes:
|
||
|
|
80a8dbb30d |
btrfs: always return -EIOCBQUEUED after btrfs_uring_read_extent_endio
If all bios finish before btrfs_encoded_read_regular_fill_pages()
returns, it calls btrfs_uring_read_extent_endio() and previously
returned the I/O status. A negative errno then made
btrfs_uring_read_extent() unlock and free while
btrfs_uring_read_finished() did the same again.
Return -EIOCBQUEUED so only the deferred path cleans up.
Reported-by: Yue Sun <samsun1006219@gmail.com>
Closes: https://lore.kernel.org/linux-btrfs/20260630091609.3414-1-samsun1006219@gmail.com/
Suggested-by: Jens Axboe <axboe@kernel.dk>
Fixes:
|
||
|
|
1fd742c747 |
btrfs: scrub: fix local_root reference leak in scrub_print_warning_inode()
When paths_from_inode() fails, scrub_print_warning_inode() jumps to err
without dropping the reference taken by btrfs_get_fs_root(), leaking a
reference to the root every time path resolution fails while printing
scrub warnings. Every other error and success path of the function
drops the reference.
Drop the reference on the paths_from_inode() failure path too.
Fixes:
|
||
|
|
3e6ff34afc |
btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans()
If we exit early because the transaction that last used the root already matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP bit set in the root (which we just set right before the exit). While this does not cause any functional issue, it makes callers of btrfs_record_root_in_trans() lock fs_info->reloc_mutex and call record_root_in_trans() for nothing, causing unnecessary lock contention, until one of them clears the bit in record_root_in_trans(). One caller of btrfs_record_root_in_trans() is start_transaction(), used to start new transaction or joining an existing one, which is a hot path. So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit. Assisted-by: LLM Reviewed-by: Boris Burkov <boris@bur.io> Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> |
||
|
|
e58c3805ed |
fsverity: RCU-delay the freeing of struct fsverity_info
__fsverity_get_info() calls rhashtable_lookup_fast(), which just uses
rcu_read_lock() and doesn't directly synchronize with
fsverity_remove_info().
For the same inode this isn't a problem: its fsverity_info is removed
only at inode eviction time or upon failure to enable verity, when the
inode no longer needs its fsverity_info and it will no longer be
accessed via that inode.
However, this is broken and can cause a use-after-free for concurrent
__fsverity_get_info() for *different* inodes. Those rely on following
fsverity_info::rhash_head in the rhashtable under rcu_read_lock() only.
They also use fsverity_info::inode to do the key comparison.
Fix this by RCU-delaying the freeing of 'struct fsverity_info' after
it's been removed from the rhashtable.
Fixes:
|
||
|
|
e08f57a7dc |
readdir: take no inode lock on an immutable directory
iterate_dir() takes the directory's i_rwsem shared and holds it across
->iterate_shared(). For a directory that never has an entry and is
never removed the lock keeps nothing still, it only orders every reader
and every writer of that inode behind each other.
For the directory of a nullfs instance that matters. The instance of
the initial mount namespace is the root of every empty mount namespace
and the private instance is the root of every kernel thread, so one
inode is shared across users who have nothing else in common. And a
reader can hold the lock for as long as it likes: back the getdents()
buffer with a mapping of a file on a FUSE mount of your own, let the
copy of "." and ".." fault and let the server wait. Queue an exclusive
taker behind it, a mkdir() in that directory goes through start_dirop()
before the read-only mount is reported, and from then on every lookup
that misses the dcache in that directory, every create and every mount
on it waits until the server answers. One user of an empty mount
namespace stalls all the others.
Add FOP_IMMUTABLE for the file operations of a directory that never
changes and is never removed and let iterate_dir() skip the lock for
it. The flag never changes for a file, ->f_pos is protected by
f_pos_lock since directories are FMODE_ATOMIC_POS, IS_DEADDIR can't be
set on such a directory and neither touch_atime() nor fsnotify take
i_rwsem. Set it on the nullfs directory. The placeholder directories of
libfs never have an entry either but their owners remove them, so they
keep the lock.
Fixes:
|
||
|
|
31a1fb9062
|
nullfs: refuse file locks
Refuse flock() and POSIX locks on nullfs. Its one inode is the root of every kernel thread and the following patches make it the directory that stands in for an unmounted mount, so a lock taken through one such directory would block the locks of every other holder and F_GETLK would tell them the pid of the holder. Give the directory file operations of its own: what libfs gives an empty directory plus ->lock and ->flock that fail with ENOLCK, the way a filesystem without lock support does. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-17-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
c9ec8607b4
|
fsnotify: let a filesystem refuse marks on its objects
Don't let nullfs be watched. fanotify refuses mount and filesystem marks on SB_NOUSER superblocks but inode marks of inotify, fanotify and dnotify go through. The one inode of knullfs is the root of every kernel thread and the following patches make it reachable from userspace as the directory that stands in for an unmounted mount. A watch placed through one such directory would report the opens through all the others, across users. Add FS_DISALLOW_NOTIFY next to FS_DISALLOW_NOTIFY_PERM, refuse a mark on any object of such a filesystem in fsnotify_add_mark_list() where every backend ends up and set it for nullfs. There's nothing to watch on a permanently empty and immutable filesystem. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-16-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
e22e69c2ad
|
namespace: nothing is mounted on or written through knullfs
Mark the root of knullfs with dont_mount(). Nothing is ever mounted on the root of a kernel thread and the following patches make that root reachable from userspace, so say it on the dentry where it doesn't depend on the mount being in no namespace. Let do_lock_mount() refuse such a target before it takes the inode lock and namespace_sem. The flag is sticky so the check needs no lock. The one under the locks stays for a mountpoint that is being removed. Make the mount read-only as well. Its one inode is immutable so nothing could be changed through it anyway, but MNT_READONLY makes that visible the usual way: EROFS instead of EPERM and ST_RDONLY in statvfs(). Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-15-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
4209cd7f54
|
namespace: keep the private nullfs instance in knullfs
The private nullfs instance that kernel threads are confined to is only ever reachable through init_task's root and pwd. The following patches point mounts at it as well, so keep it in a global. No functional changes. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-14-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
67b69b8a6e
|
fhandle: decide the subtree check under mount_lock
may_decode_fh() lets a caller who is privileged over the mount namespace of @root decode handles below @root->dentry as long as the mount is mounted and no locked child covers something below it. The three parts are read one after the other without a lock: is_mounted() and capable_wrt_mount() read ->mnt_ns and has_locked_children() walks ->mnt_mounts under a mount_lock of its own. Today the gaps are harmless. A lazy umount in between clears ->mnt_ns, but the locked children stay attached to their unmounted parent, so the walk still finds them and the decode is refused either way. The following patches detach every unmounted mount from its parent. Then an umount between is_mounted() and the walk makes the walk come back empty and the caller decodes into what a locked child covered. So take mount_lock once and answer all three questions under it. is_mounted() is stable there, umount_tree() clears ->mnt_ns on the write side, and a mounted mount still has its locked children on its list. ns_capable() under the spinlock is fine, generic_permission() calls it in RCU walk already. has_locked_children() loses its locking wrapper, its other callers hold namespace_sem or mount_lock anyway. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-13-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> |
||
|
|
d74d66aa70
|
namespace: keep the lock on a mount that a propagated copy is moved beneath
attach_recursive_mnt() transfers MNT_LOCKED from the top mount to the
mount that is moved beneath it with MOVE_MOUNT_BENEATH. This allows the
owner of a user namespace to replace its locked /proc or its root. The
mount beneath takes on the job of covering the underlying mountpoint
allowing the top mount to be unmounted.
Consider two mount namespaces:
(H) The host H has a shared mount P with a secret in P/d
(Z) Z is a user namespace made from M. Its copy of P receives
propagation from the host's P and its copy of M's cover on P/d is
locked Z's owner must not get to see P/d.
Now (H) mounts X on P/d. This propagates into (Z). The copy of X lands
beneath (Z)'s locked cover. The locked property is transfered from (Z)'s
cover to the copy of X propagated beneath it. The cover is now unlocked.
Now (H) unmounts X again. The copy of X in (Z) gets unmounted and the
covering mount is left unlocked on top of P/d. (Z) can now unmount it:
Z: umount2(P/d) = EINVAL /* the cover is locked */
H: mount X on P/d, umount X /* both propagate into Z /*
Z: umount2(P/d) = 0 /* the cover is now unlocked */
Z: read P/d/secret = "covered-by-root" /* secret revealed */
So only transfer the locked property to the mount beneath for mounts the
caller has placed. A propagated copy that lands beneath a locked mount
is locked as well so that the mount at the bottom of the stack carries a
lock the way every check expects. The mount on top of it remains locked
to ensure that it keeps covering even if the propagated mount is
unmounted again.
Fixes:
|
||
|
|
f78ddcc872
|
namespace: never expire a locked mount
Locked mounts are special. They protect the underlying files and directories from being revealed. do_umount() refuses to unmount locked mounts but shrink_submounts() doesn't. A shrinkable mount can become locked once the owner of a user namespace puts it beneath a locked mount with MOVE_MOUNT_BENEATH. The locked property now moves to the mount at the bottom making it possible to unmount the top mount. So umount() of an unlocked ancestor now expires the bottom mount first and the covered directory is revealed: move_mount(c -> x/hidden, MOVE_MOUNT_BENEATH) = 0 the cover after the lock moved: umount2(x/hidden) = 0 the holder of the lock: umount2(x/hidden) = EINVAL the root of the copy, busy: umount2(x) = EBUSY reads x/hidden/secret: "covered-by-root" So leave a locked mount alone as it dies together with its parent. A plain umount() of an unlocked mount with a locked one below it is EBUSY from now on. It's the same for any other locked child. A lazy umount still takes the whole tree. mark_mounts_for_expiry() never sees a locked mount. lock_mnt_tree() leaves a mount on an expiry list alone and nothing else puts a locked one on such a list. Add an assert for this. Fixes: |
||
|
|
caf16f9b4a
|
nullfs: don't update the access time
There's no point in updating access times of the nullfs instance. Raise
S_IMMUTABLE.
Fixes:
|
||
|
|
33aadc7c72
|
namespace: handle mount locking for automounts correctly
If mounts are propagated across user namespaces, attach_recursive_mnt()
locks every copy of the source mount to protect overmounts from
vanishing and revealing the underlying files or directories.
The user namespace is taken from the caller's mount namespaces since
this is where the mounts end up. Except, that's not always true.
Automounts may legitimately get popped in by tasks located in a
different mount and user namespace during path lookup.
Then check doesn't make sense at that point. The copy in the namespace
of the parent - which may be the host's - is now locked and the host
cannot change the flags of its own mounts anymore.
Here's the reproducer:
- host has debugfs mounted nosuid,nodev,noexec and shared
- tracefs automount below it is not yet active
- hand a child process a directory descriptor on that mount
- child process enters auser and mount namespace
- child process' namespace now holds a locked copy of the host's debugfs
mount that receives propagation from it
- child process stats "tracing/." through the descriptor
- lookup runs on the host's mount so the automount lands below the host's mount
- propagation puts a copy below the child's copy
- both try to clear the flags on the mount they got, with a bind remount:
host, on its own automount: MS_REMOUNT|MS_BIND = EPERM
child, on the copy in its namespace: MS_REMOUNT|MS_BIND = 0
So the lock landed on the host's mount instead of the child's copy.
Congrats. So we need to compare with the owner of the namespace the
mount actually gets mounted on. For all regular cases that is the
caller's mount namespace and so nothing changes.
Detached trees in anonymous mount namespaces by be handed over via
SCM_RIGHTS or inherited in other ways on purpose so the attaching task's
mount namespace is authoritative, not the creator of the detached tree.
Fixes:
|
||
|
|
ba985ce084
|
namespace: refuse an automount below a mount that is in no namespace
It's possible to add autmounts even when the parent mount isn't in the
mount namespace of the caller. The only requirement we have is that the
parent mount namespace must not be NULL, i.e., unmounted.
Problem is that clone_private_mount() has MNT_NS_INTERNAL which makes
that trivially true. So that passes the test and attach_recursive_mnt()
accepts that as a mount point and funny enough, count_mounts()
dereferences MNT_NS_INTERNAL. The problem is it is an error pointer...
So we can reach this in userspace via fanotify. A filesystem mark on the
lower filesystem of an overlay reports paths on the layer clone and
reading the event hands out a descriptor on it. For example with debugfs
as the lower layer it goes kaboom:
openat(evfd, "tracing", O_DIRECTORY)
Oops: general protection fault
KASAN: null-ptr-deref in range [0x1d0-0x1d7]
RIP: 0010:count_mounts+0x35/0x200
attach_recursive_mnt
finish_automount
And since that sleeping beauty happens under namespace_sem held for
writing every mount operation on the system blocks from then on.
Congrats.
Use is_mounted() instead which rejects unmounted and internal mounts
alike. The open fails with EINVAL just as it did before
clone_private_mount() used MNT_NS_INTERNAL.
Fixes:
|
||
|
|
b0d1022b9b
|
fcntl: refuse F_SET_RW_HINT on an immutable inode
An immutable inode is never written to so write hints are pointless.
Refuse the hint on an immutable inode the way setattr(), fallocate() and
etxattr() refuse their changes.
Fixes:
|
||
|
|
8189ab688f
|
namei: don't reveal overmounted entries in refwalk
In rcuwalk the dentry is validated before it is accepted. step_into()
step_into() rechecks d_seq and __follow_mount_rcu() rechecks mount_lock.
An entry that gets unlinked in between causes the lookup to retry and
miss.
A refwalk doesn't do this. lookup_fast() takes a reference on the hashed
dentry and simply accepts it. So an unlink that happens after the
reference was taken isn't seen by refwalk. That's fine for a simple
file. We just happened to open it before it was unlinked, no problem.
For a mountpoint and specifically a locked mountpoint it very much
isn't. The unlink detaches all mounts and then removes the name. Any
refwalk that hasn't traversed the mounts yet simply reveals the
underlying entry. It's a very narrow window but it can be hit:
reads of a covered file in 60 s, 3 walkers, ~15000 unlinks
no widening 27
that step + 200 us 3741
See the appended patch for a more reliable reproducer.
So check the entry after step_into(). unlink(), rmdir() and rename()
mark the dentry with dont_mount() before they detach the mounts and
remove the dentry.
So a dentry that is marked with DCACHE_CANT_MOUNT and is unhashed by the
time its mounts were looked at is a name that was unlinked under the
refwalk. DCACHE_CANT_MOUNT is read after the mounts were looked up and
it is set before they are detached by detach_mounts(). A refwalk that
missed the mounts will see DCACHE_CANT_MOUNT.
Plain d_unlinked() is fine. A rename takes the dentry off its hash chain
but ___d_drop() leaves d_hash.pprev set. So only __d_drop() and a rename
over the dentry unhash it. The only move that flips IS_ROOT splices in a
disconnected alias. That doesn't have DCACHE_CANT_MOUNT set.
Add the to step_into_slowpath(). ".." and LOOKUP_DOWN may legitimately
land on an unhashed directory. A refwalk that crossed onto a mount has
path.mnt different from nd->path.mnt. A dentry that a filesystem dropped
on its own (d_invalidate(), d_drop()) doesn't have the flag set and is
treated as before.
// SPDX-License-Identifier: GPL-2.0
/*
* unlink_covered: unlink a file that is a mountpoint in a detached copy of
* its mount, against walkers that open it through that copy.
*
* A is a tmpfs with the file f1 (content SECRET_F) and the plain file
* MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound
* on A'/f1, held through an O_PATH fd on its root once the tree fd is
* closed. Walkers open f1 through that fd while the driver unlinks A/f1,
* where nothing is mounted on it. A walker may read MARK_A or get ENOENT.
* A read of SECRET_F is a hit: the name was found after its mount was
* gone. The lockless walks use openat2(RESOLVE_CACHED).
*
* usage: unlink_covered [-t seconds] [-w walkers]
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <linux/openat2.h>
#ifndef __NR_open_tree
#define __NR_open_tree 428
#endif
#ifndef __NR_move_mount
#define __NR_move_mount 429
#endif
#ifndef __NR_openat2
#define __NR_openat2 437
#endif
#ifndef OPEN_TREE_CLONE
#define OPEN_TREE_CLONE 1
#endif
#ifndef OPEN_TREE_CLOEXEC
#define OPEN_TREE_CLOEXEC O_CLOEXEC
#endif
#ifndef MOVE_MOUNT_F_EMPTY_PATH
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
#endif
#define WORK "/tmp/uc"
#define ADIR WORK "/A"
static int duration = 60, nwalkers = 3;
static atomic_int stop, writer_waiting;
static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER;
static int cur_fd = -1; /* the root of A', -1 while there is none */
static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_other, n_secret,
n_secret_cached;
static void die(const char *what)
{
fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno));
exit(2);
}
static void put_file(int dfd, const char *name, const char *content)
{
int fd = openat(dfd, name, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
0644);
if (fd < 0 || write(fd, content, strlen(content)) < 0)
die(name);
close(fd);
}
/* "plain/../" @n times, then f1: a longer walk that checks nothing on its way */
static char *longpath(int n)
{
char *p = malloc(n * 9 + 3), *q = p;
for (int i = 0; i < n; i++, q += 9)
memcpy(q, "plain/../", 9);
strcpy(q, "f1");
return p;
}
static void try_read(int dfd, const char *path, int cached)
{
struct open_how how = { .flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_CACHED };
char buf[32] = "";
long n;
int fd;
if (cached)
fd = syscall(__NR_openat2, dfd, path, &how, sizeof(how));
else
fd = openat(dfd, path, O_RDONLY | O_CLOEXEC);
atomic_fetch_add(&n_walk, 1);
if (fd < 0) {
if (errno == ENOENT)
atomic_fetch_add(&n_enoent, 1);
else if (!cached || errno != EAGAIN)
atomic_fetch_add(&n_other, 1);
return;
}
n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n >= 6 && !strncmp(buf, "SECRET", 6)) {
if (cached)
atomic_fetch_add(&n_secret_cached, 1);
if (!atomic_fetch_add(&n_secret, 1))
printf("HIT: read \"%s\" through %s (%s walk)\n", buf,
path, cached ? "lockless" : "any");
} else {
atomic_fetch_add(&n_mark, 1);
}
}
static void *walker(void *arg)
{
unsigned int r = (long)arg * 2654435761u;
while (!atomic_load(&stop)) {
char *p_long, *p_short;
int dfd;
while (atomic_load(&writer_waiting) && !atomic_load(&stop))
usleep(20);
pthread_rwlock_rdlock(&cur_lock);
dfd = cur_fd;
if (dfd < 0) {
pthread_rwlock_unlock(&cur_lock);
usleep(100);
continue;
}
r = r * 1103515245u + 12345u;
p_long = longpath(1 + (r >> 8) % 400);
p_short = longpath(0);
try_read(dfd, p_long, 0);
try_read(dfd, p_short, 0);
try_read(dfd, p_long, 1);
pthread_rwlock_unlock(&cur_lock);
free(p_long);
free(p_short);
}
return NULL;
}
/* hand the walkers a new A' (or none), close the old one */
static void publish(int fd)
{
int old;
atomic_fetch_add(&writer_waiting, 1);
pthread_rwlock_wrlock(&cur_lock);
old = cur_fd;
cur_fd = fd;
pthread_rwlock_unlock(&cur_lock);
atomic_fetch_sub(&writer_waiting, 1);
if (old >= 0)
close(old);
}
static void *driver(void *arg __attribute__((unused)))
{
int a;
if (mkdir(ADIR, 0755) && errno != EEXIST)
die("mkdir A");
if (mount("A", ADIR, "tmpfs", 0, "size=4M"))
die("mount A");
a = open(ADIR, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (a < 0 || mkdirat(a, "plain", 0755))
die("A/plain");
put_file(a, "MARK_A", "MARK_A");
while (!atomic_load(&stop)) {
int t, m, fd_a;
put_file(a, "f1", "SECRET_F");
t = syscall(__NR_open_tree, a, "",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH);
if (t < 0)
die("open_tree A");
m = syscall(__NR_open_tree, a, "MARK_A",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
if (m < 0)
die("open_tree MARK_A");
if (syscall(__NR_move_mount, m, "", t, "f1",
MOVE_MOUNT_F_EMPTY_PATH))
die("move_mount");
close(m);
fd_a = openat(t, ".", O_PATH | O_DIRECTORY | O_CLOEXEC);
if (fd_a < 0)
die("open A'");
publish(fd_a);
usleep(200 + rand() % 1000);
close(t); /* A' is unmounted, fd_a holds it */
usleep(200 + rand() % 1000);
if (unlinkat(a, "f1", 0)) /* through A, a plain file there */
die("unlink f1");
atomic_fetch_add(&n_unlink, 1);
usleep(rand() % 300);
publish(-1);
}
close(a);
umount2(ADIR, MNT_DETACH);
return NULL;
}
int main(int argc, char **argv)
{
pthread_t d, *w;
int c, i;
setvbuf(stdout, NULL, _IOLBF, 0);
while ((c = getopt(argc, argv, "t:w:")) != -1) {
switch (c) {
case 't':
duration = atoi(optarg);
break;
case 'w':
nwalkers = atoi(optarg);
break;
default:
fprintf(stderr, "usage: unlink_covered [-t seconds] [-w walkers]\n");
return 2;
}
}
if (unshare(CLONE_NEWNS) ||
mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
die("unshare");
if (mkdir(WORK, 0755) && errno != EEXIST)
die("mkdir");
w = calloc(nwalkers, sizeof(*w));
for (i = 0; i < nwalkers; i++)
if (pthread_create(&w[i], NULL, walker, (void *)(long)i))
die("pthread_create");
if (pthread_create(&d, NULL, driver, NULL))
die("pthread_create");
sleep(duration);
atomic_store(&stop, 1);
pthread_join(d, NULL);
for (i = 0; i < nwalkers; i++)
pthread_join(w[i], NULL);
printf("unlink_covered: %d s, %d walkers: unlinks %ld walks %ld mark %ld enoent %ld other %ld SECRET %ld (lockless %ld)\n",
duration, nwalkers, atomic_load(&n_unlink), atomic_load(&n_walk),
atomic_load(&n_mark), atomic_load(&n_enoent),
atomic_load(&n_other), atomic_load(&n_secret),
atomic_load(&n_secret_cached));
return atomic_load(&n_secret) ? 1 : 0;
}
diff --git a/fs/namei.c b/fs/namei.c
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -35,6 +35,7 @@
#include <linux/fcntl.h>
#include <linux/device_cgroup.h>
#include <linux/fs_struct.h>
+#include <linux/delay.h>
#include <linux/posix_acl.h>
#include <linux/hash.h>
#include <linux/bitops.h>
@@ -1205,9 +1206,26 @@ static int sysctl_protected_symlinks __read_mostly;
static int sysctl_protected_hardlinks __read_mostly;
static int sysctl_protected_fifos __read_mostly;
static int sysctl_protected_regular __read_mostly;
+/* debug: widen the two windows of the detach_mounts() race */
+static int sysctl_detach_race_walk_us __read_mostly;
+static int sysctl_detach_race_unlink_us __read_mostly;
#ifdef CONFIG_SYSCTL
static const struct ctl_table namei_sysctls[] = {
+ {
+ .procname = "detach_race_walk_us",
+ .data = &sysctl_detach_race_walk_us,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "detach_race_unlink_us",
+ .data = &sysctl_detach_race_unlink_us,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
{
.procname = "protected_symlinks",
.data = &sysctl_protected_symlinks,
@@ -1878,6 +1896,10 @@ static struct dentry *lookup_fast(struct nameidata *nd)
dentry = __d_lookup(parent, &nd->last);
if (unlikely(!dentry))
return NULL;
+ /* debug: between finding a mountpoint and crossing its mounts */
+ if (unlikely(sysctl_detach_race_walk_us) && d_mountpoint(dentry))
+ usleep_range(sysctl_detach_race_walk_us,
+ sysctl_detach_race_walk_us + 10);
status = d_revalidate(nd->inode, &nd->last, dentry, nd->flags);
}
if (unlikely(status <= 0)) {
@@ -5693,6 +5715,10 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir,
if (!error) {
dont_mount(dentry);
detach_mounts(dentry);
+ /* debug: the mounts are gone, d_delete() is still to come */
+ if (unlikely(sysctl_detach_race_unlink_us))
+ usleep_range(sysctl_detach_race_unlink_us,
+ sysctl_detach_race_unlink_us + 10);
}
}
}
Fixes:
|
||
|
|
528b383dc3
|
namespace: unhash a dentry before detaching the mounts on it
unlink(), rmdir() and rename() remove the entry from the filesystem,
call detach_mounts() on the entry with the inode locked and then call
d_delete() once the inode is unlocked.
The thing is that between detach_mounts() and d_delete() the dentry is
still hashed and positive. But after detach_mounts() nothing covers the
dentry anymore. A lookup that finds this dentry in the dcache can
uncover what the mounts hid.
That's a problem when unlinking files or directories that are
mountpoints in other mount namespaces. Everybody who had the underlying
entry covered can race the detach_mount() call until d_delete() has run.
The race window isn't all that small. It encompasses namespace_unlock()
with a full synchronize_rcu_expedited() grace period and the
inode_unlock() of the entry.
In my experiments three walkers that kept trying read an overmounted
file in 11938 of the 16140 unlinks that removed its mountpoint from a
bind mount of the filesystem within a minute.
Fun fact, d_invalidate() has the same ordering problem but gets it
right. It unhashes the dentry first and detaches the mounts afterwards.
Let's do the same in __detach_mounts():
- A lookup that hasn't found the dentry yet misses it in the dcache and
waits for the directory lock that the caller holds until the name is
gone for good.
- A lockless lookup that found it already fails the mount_lock check
while the dentry still counts as a mountpoint and the d_seq check once
it doesn't, and retries.
This fixes the lockless path. We still need to fix the reference count
lookup in a follow-up patch.
So d_drop() the dentry. The dentry stays positive and held but can't be
found anymore. Then proceed with the detach and unlink.
Reproducer:
The reads were counted with the program below, three walkers for 60 s
in a VM with 4 CPUs. The race window is stretched with the debug patch
pasted here. fs.detach_race_walk_us sleeps in lookup_fast() once it
found a mountpoint dentry and before its mounts are crossed.
fs.detach_race_unlink_us sleeps in vfs_unlink() between detach_mounts()
and d_delete().
// SPDX-License-Identifier: GPL-2.0
/*
* unlink_covered: unlink a file that is a mountpoint in a detached copy of
* its mount, against lookups that open it through that copy.
*
* A is a tmpfs with the file f1 (content SECRET_F) and the plain file
* MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound
* on A'/f1, held through an O_PATH fd on its root once the tree fd is
* closed. Walkers open f1 through that fd while the driver unlinks A/f1,
* where nothing is mounted on it. A walker may read MARK_A or get ENOENT.
* A read of SECRET_F is a hit: the name was found after its mount was
* gone. The lockless walks use openat2(RESOLVE_CACHED).
*
* usage: unlink_covered [-t seconds] [-w walkers]
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <linux/openat2.h>
#ifndef __NR_open_tree
#define __NR_open_tree 428
#endif
#ifndef __NR_move_mount
#define __NR_move_mount 429
#endif
#ifndef __NR_openat2
#define __NR_openat2 437
#endif
#ifndef OPEN_TREE_CLONE
#define OPEN_TREE_CLONE 1
#endif
#ifndef OPEN_TREE_CLOEXEC
#define OPEN_TREE_CLOEXEC O_CLOEXEC
#endif
#ifndef MOVE_MOUNT_F_EMPTY_PATH
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
#endif
#define WORK "/tmp/uc"
#define ADIR WORK "/A"
static int duration = 60, nwalkers = 3;
static atomic_int stop, writer_waiting;
static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER;
static int cur_fd = -1; /* the root of A', -1 while there is none */
static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_other, n_secret,
n_secret_cached;
static void die(const char *what)
{
fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno));
exit(2);
}
static void put_file(int dfd, const char *name, const char *content)
{
int fd = openat(dfd, name, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
0644);
if (fd < 0 || write(fd, content, strlen(content)) < 0)
die(name);
close(fd);
}
/* "plain/../" @n times, then f1: a longer walk that checks nothing on its way */
static char *longpath(int n)
{
char *p = malloc(n * 9 + 3), *q = p;
for (int i = 0; i < n; i++, q += 9)
memcpy(q, "plain/../", 9);
strcpy(q, "f1");
return p;
}
static void try_read(int dfd, const char *path, int cached)
{
struct open_how how = { .flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_CACHED };
char buf[32] = "";
long n;
int fd;
if (cached)
fd = syscall(__NR_openat2, dfd, path, &how, sizeof(how));
else
fd = openat(dfd, path, O_RDONLY | O_CLOEXEC);
atomic_fetch_add(&n_walk, 1);
if (fd < 0) {
if (errno == ENOENT)
atomic_fetch_add(&n_enoent, 1);
else if (!cached || errno != EAGAIN)
atomic_fetch_add(&n_other, 1);
return;
}
n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n >= 6 && !strncmp(buf, "SECRET", 6)) {
if (cached)
atomic_fetch_add(&n_secret_cached, 1);
if (!atomic_fetch_add(&n_secret, 1))
printf("HIT: read \"%s\" through %s (%s walk)\n", buf,
path, cached ? "lockless" : "any");
} else {
atomic_fetch_add(&n_mark, 1);
}
}
static void *walker(void *arg)
{
unsigned int r = (long)arg * 2654435761u;
while (!atomic_load(&stop)) {
char *p_long, *p_short;
int dfd;
while (atomic_load(&writer_waiting) && !atomic_load(&stop))
usleep(20);
pthread_rwlock_rdlock(&cur_lock);
dfd = cur_fd;
if (dfd < 0) {
pthread_rwlock_unlock(&cur_lock);
usleep(100);
continue;
}
r = r * 1103515245u + 12345u;
p_long = longpath(1 + (r >> 8) % 400);
p_short = longpath(0);
try_read(dfd, p_long, 0);
try_read(dfd, p_short, 0);
try_read(dfd, p_long, 1);
pthread_rwlock_unlock(&cur_lock);
free(p_long);
free(p_short);
}
return NULL;
}
/* hand the walkers a new A' (or none), close the old one */
static void publish(int fd)
{
int old;
atomic_fetch_add(&writer_waiting, 1);
pthread_rwlock_wrlock(&cur_lock);
old = cur_fd;
cur_fd = fd;
pthread_rwlock_unlock(&cur_lock);
atomic_fetch_sub(&writer_waiting, 1);
if (old >= 0)
close(old);
}
static void *driver(void *arg __attribute__((unused)))
{
int a;
if (mkdir(ADIR, 0755) && errno != EEXIST)
die("mkdir A");
if (mount("A", ADIR, "tmpfs", 0, "size=4M"))
die("mount A");
a = open(ADIR, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (a < 0 || mkdirat(a, "plain", 0755))
die("A/plain");
put_file(a, "MARK_A", "MARK_A");
while (!atomic_load(&stop)) {
int t, m, fd_a;
put_file(a, "f1", "SECRET_F");
t = syscall(__NR_open_tree, a, "",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH);
if (t < 0)
die("open_tree A");
m = syscall(__NR_open_tree, a, "MARK_A",
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
if (m < 0)
die("open_tree MARK_A");
if (syscall(__NR_move_mount, m, "", t, "f1",
MOVE_MOUNT_F_EMPTY_PATH))
die("move_mount");
close(m);
fd_a = openat(t, ".", O_PATH | O_DIRECTORY | O_CLOEXEC);
if (fd_a < 0)
die("open A'");
publish(fd_a);
usleep(200 + rand() % 1000);
close(t); /* A' is unmounted, fd_a holds it */
usleep(200 + rand() % 1000);
if (unlinkat(a, "f1", 0)) /* through A, a plain file there */
die("unlink f1");
atomic_fetch_add(&n_unlink, 1);
usleep(rand() % 300);
publish(-1);
}
close(a);
umount2(ADIR, MNT_DETACH);
return NULL;
}
int main(int argc, char **argv)
{
pthread_t d, *w;
int c, i;
setvbuf(stdout, NULL, _IOLBF, 0);
while ((c = getopt(argc, argv, "t:w:")) != -1) {
switch (c) {
case 't':
duration = atoi(optarg);
break;
case 'w':
nwalkers = atoi(optarg);
break;
default:
fprintf(stderr, "usage: unlink_covered [-t seconds] [-w walkers]\n");
return 2;
}
}
if (unshare(CLONE_NEWNS) ||
mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
die("unshare");
if (mkdir(WORK, 0755) && errno != EEXIST)
die("mkdir");
w = calloc(nwalkers, sizeof(*w));
for (i = 0; i < nwalkers; i++)
if (pthread_create(&w[i], NULL, walker, (void *)(long)i))
die("pthread_create");
if (pthread_create(&d, NULL, driver, NULL))
die("pthread_create");
sleep(duration);
atomic_store(&stop, 1);
pthread_join(d, NULL);
for (i = 0; i < nwalkers; i++)
pthread_join(w[i], NULL);
printf("unlink_covered: %d s, %d walkers: unlinks %ld walks %ld mark %ld enoent %ld other %ld SECRET %ld (lockless %ld)\n",
duration, nwalkers, atomic_load(&n_unlink), atomic_load(&n_walk),
atomic_load(&n_mark), atomic_load(&n_enoent),
atomic_load(&n_other), atomic_load(&n_secret),
atomic_load(&n_secret_cached));
return atomic_load(&n_secret) ? 1 : 0;
}
diff --git a/fs/namei.c b/fs/namei.c
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -35,6 +35,7 @@
#include <linux/fcntl.h>
#include <linux/device_cgroup.h>
#include <linux/fs_struct.h>
+#include <linux/delay.h>
#include <linux/posix_acl.h>
#include <linux/hash.h>
#include <linux/bitops.h>
@@ -1205,9 +1206,26 @@ static int sysctl_protected_symlinks __read_mostly;
static int sysctl_protected_hardlinks __read_mostly;
static int sysctl_protected_fifos __read_mostly;
static int sysctl_protected_regular __read_mostly;
+/* debug: widen the two windows of the detach_mounts() race */
+static int sysctl_detach_race_walk_us __read_mostly;
+static int sysctl_detach_race_unlink_us __read_mostly;
#ifdef CONFIG_SYSCTL
static const struct ctl_table namei_sysctls[] = {
+ {
+ .procname = "detach_race_walk_us",
+ .data = &sysctl_detach_race_walk_us,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "detach_race_unlink_us",
+ .data = &sysctl_detach_race_unlink_us,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
{
.procname = "protected_symlinks",
.data = &sysctl_protected_symlinks,
@@ -1878,6 +1896,10 @@ static struct dentry *lookup_fast(struct nameidata *nd)
dentry = __d_lookup(parent, &nd->last);
if (unlikely(!dentry))
return NULL;
+ /* debug: between finding a mountpoint and crossing its mounts */
+ if (unlikely(sysctl_detach_race_walk_us) && d_mountpoint(dentry))
+ usleep_range(sysctl_detach_race_walk_us,
+ sysctl_detach_race_walk_us + 10);
status = d_revalidate(nd->inode, &nd->last, dentry, nd->flags);
}
if (unlikely(status <= 0)) {
@@ -5693,6 +5715,10 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir,
if (!error) {
dont_mount(dentry);
detach_mounts(dentry);
+ /* debug: the mounts are gone, d_delete() is still to come */
+ if (unlikely(sysctl_detach_race_unlink_us))
+ usleep_range(sysctl_detach_race_unlink_us,
+ sysctl_detach_race_unlink_us + 10);
}
}
}
Fixes:
|
||
|
|
ff47652a4b |
smb client fixes for v7.3-rc6
- Stop data dirtied past EOF through an mmap from reappearing as file
content once the file is extended by a write, truncate, zero range,
copy range or clone range
- Flush dirty data and drain in-flight I/O before operations that
assume the pagecache and the server agree on the file: querying
allocated ranges, the O_TRUNC open, interior zero range, and
server-side copy/clone
- Stop a genuine size-extending zero range or preallocate from being
refused with -EOPNOTSUPP when the inode is not read caching, by
querying the server's authoritative EOF instead of trusting a stale
cached i_size
- Zero the untransferred tail of a short read, both in the netfs
read-gaps path (where stale folio content could otherwise be written
back to the server) and in the DIO/unbuffered read collector, and
tell a real EOF apart from a stale cached remote_i_size after a lease
downgrade
- Require stable pages on signed connections so a buffered write can't
modify a folio whose signature has already been computed and is in
flight, which the server rejected with STATUS_ACCESS_DENIED and the
client surfaced as -EIO
- Split several cifsFileInfo flags out of a shared bitfield byte so
concurrent updates taken under different locks no longer clobber each
other through a byte-level RMW
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQTcqRusfSdYROJQwGkpVtNKoQNdYwUCasAVCQAKCRApVtNKoQNd
YxLEAQDBsM7NZk2+hUlPZMDq/NK+8C12Vm8WnN8nqb3Dpt8jCwEA2JJGMeO+p9/L
/hwKubpM6CuWiG2yMbqyUHWVZSkOFwQ=
=8v7I
-----END PGP SIGNATURE-----
Merge tag 'cifs-fixes-7.3-rc6' of https://git.manguebit.org/linux
Pull smb client fixes from Paulo Alcantara:
"Fix a series of data corruption and I/O error bugs found by running
generic/363 (fsx) in a loop against Windows Server 2022 and Samba.
- Stop data dirtied past EOF through an mmap from reappearing as file
content once the file is extended by a write, truncate, zero range,
copy range or clone range
- Flush dirty data and drain in-flight I/O before operations that
assume the pagecache and the server agree on the file: querying
allocated ranges, the O_TRUNC open, interior zero range, and
server-side copy/clone
- Stop a genuine size-extending zero range or preallocate from being
refused with -EOPNOTSUPP when the inode is not read caching, by
querying the server's authoritative EOF instead of trusting a stale
cached i_size
- Zero the untransferred tail of a short read, both in the netfs
read-gaps path (where stale folio content could otherwise be
written back to the server) and in the DIO/unbuffered read
collector, and tell a real EOF apart from a stale cached
remote_i_size after a lease downgrade
- Require stable pages on signed connections so a buffered write
can't modify a folio whose signature has already been computed and
is in flight, which the server rejected with STATUS_ACCESS_DENIED
and the client surfaced as -EIO
- Split several cifsFileInfo flags out of a shared bitfield byte so
concurrent updates taken under different locks no longer clobber
each other through a byte-level RMW"
* tag 'cifs-fixes-7.3-rc6' of https://git.manguebit.org/linux:
smb: client: split cifsFileInfo bitfields to avoid shared-byte RMW races
smb: client: require stable pages for signed connections
smb: client: distinguish real EOF from a stale remote_i_size on read
netfs: zero the tail of a short DIO/unbuffered read
smb: client: only require read lease for size-extending preallocate
netfs: zero gaps in read-gaps folio to avoid writing back stale data
smb: client: only require read lease for size-extending zero range
smb: client: drain and invalidate before server-side copy/clone
smb: client: flush dirty data before zeroing a range
smb: client: drain outstanding I/O before truncating on O_TRUNC open
smb: client: flush and commit data before querying allocated ranges
smb: client: discard post-EOF pagecache when extending a file via clone range
smb: client: discard post-EOF pagecache when extending a file via copy range
smb: client: discard post-EOF pagecache when extending a file via zero range
smb: client: clear post-EOF pagecache when extending a file via truncate
netfs: clear post-EOF pagecache when extending a file via write
|