__xlate_proc_name() walks the components of a name like "a/b/c" and
takes whatever it finds for "a" and "b" as the directory to go on in.
It never checks that they actually are directories. So
proc_create("file/x") hangs x into the subdir tree of the regular file
"file". Nobody can look x up there, but remove_proc_subtree() on the
file removes and frees x along with it, behind the back of whoever
registered x.pktgen passes the name the writer of "add_device" picks to proc_create_data(), and only the part in front of the '@' has to name a network device: echo "add_device lo@" > /proc/net/pktgen/kpktgend_0 echo "add_device lo@/x" > /proc/net/pktgen/kpktgend_1 echo "rem_device_all" > /proc/net/pktgen/kpktgend_0 echo "rem_device_all" > /proc/net/pktgen/kpktgend_1 The third write frees both entries and the fourth one makes kpktgend_1 call proc_remove() on the freed second one: BUG: KASAN: slab-use-after-free in proc_remove+0x8f/0xa0 Read of size 8 at addr ff110001134c8e70 by task kpktgend_1/243 Call Trace: proc_remove+0x8f/0xa0 pktgen_remove_device.isra.0+0x118/0x500 pktgen_rem_all_ifs+0xeb/0x1a0 pktgen_thread_worker+0x1b42/0x7280 The files of a network namespace belong to the root of its user namespace and these writes don't require a capability. So once pktgen is loaded an unprivileged user can do this in namespaces of its own. Treat a component that isn't a directory like one that doesn't exist. All names with a '/' that the tree passes in as literals only walk through directories. pktgen is fixed separately to not pass such names in the first place. Reported-by: Chris Mason <[email protected]> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: [email protected] Signed-off-by: Christian Brauner (Amutable) <[email protected]> --- fs/proc/generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 2b1971da4a85..c560c96a64eb 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -170,7 +170,7 @@ static int __xlate_proc_name(const char *name, struct proc_dir_entry **ret, de = *ret ?: &proc_root; while ((next = strchr(cp, '/')) != NULL) { de = pde_subdir_find(de, cp, next - cp); - if (!de) { + if (!de || !S_ISDIR(de->mode)) { WARN(1, "name '%s'\n", name); return -ENOENT; } -- 2.53.0
