__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 <mason@kernel.org>
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: <redacted>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
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