From 432834f79e28f35da3c5f5ba1a874794708c8f32 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 3 Nov 2026 12:50:13 +0100 Subject: [PATCH] nsresourced: refuse RENAME_WHITEOUT where mknod is refused renameat2() with RENAME_WHITEOUT leaves a whiteout in place of the source. That's a character device owned by the caller's fsuid and fsgid which the file system creates from its ->rename() method, so it never passes the path_mknod hook. Since Linux 5.8 this doesn't require any privileges either. So a managed user namespace that can rename an entry on a file system that outlives it, e.g., in a world-writable directory the user prepared from the outside, can leave a whiteout owned by its transient range behind. The only hook that sees this is path_rename, which gets the flags. Refuse RENAME_WHITEOUT there whenever validate_mount() would refuse a mknod in the source directory. (cherry picked from commit 075c36427330e533b12616ce6b4a0418cceba73c) (cherry picked from commit b4f5697d2241327276d175dcba3f1671a835c31c) --- src/bpf/userns-restrict.bpf.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/bpf/userns-restrict.bpf.c b/src/bpf/userns-restrict.bpf.c index 2dae3386eef..81b57ba58bf 100644 --- a/src/bpf/userns-restrict.bpf.c +++ b/src/bpf/userns-restrict.bpf.c @@ -69,6 +69,9 @@ void* bpf_rdonly_cast(const void *, __u32) __ksym; #define OVERLAYFS_SUPER_MAGIC 0x794c7630 #define CGROUP2_SUPER_MAGIC 0x63677270 +/* From include/uapi/linux/fs.h, for the same reason. */ +#define RENAME_WHITEOUT (1U << 2) + /* From include/uapi/asm-generic/fcntl.h. Only archs without BPF trampolines (no LSM programs) deviate. */ #define __O_TMPFILE (1U << 22) /* O_TMPFILE without O_DIRECTORY */ @@ -416,6 +419,16 @@ int BPF_PROG(userns_restrict_file_open, struct file *file, int ret) { return validate_mount(file->f_path.mnt, ret); } +/* RENAME_WHITEOUT puts a whiteout owned by the calling task in place of the source, past the mknod hook */ +SEC("lsm/path_rename") +int BPF_PROG(userns_restrict_path_rename, const struct path *old_dir, struct dentry *old_dentry, + const struct path *new_dir, struct dentry *new_dentry, unsigned flags, int ret) { + if (!(flags & RENAME_WHITEOUT)) + return ret; + + return validate_mount(old_dir->mnt, ret); +} + /* overlayfs' struct ovl_fs and struct ovl_layer are private to the module and hence absent from vmlinux.h * whenever overlayfs is built as a module. We declare the two fields we need as CO-RE "type flavors": libbpf * strips the ___local suffix and relocates these against the kernel's real ovl_fs/ovl_layer. This both