Skip to content

Commit 895b4c0

Browse files
Wei Yangakpm00
authored andcommitted
fs/proc: fix uaf in proc_readdir_de()
Pde is erased from subdir rbtree through rb_erase(), but not set the node to EMPTY, which may result in uaf access. We should use RB_CLEAR_NODE() set the erased node to EMPTY, then pde_subdir_next() will return NULL to avoid uaf access. We found an uaf issue while using stress-ng testing, need to run testcase getdent and tun in the same time. The steps of the issue is as follows: 1) use getdent to traverse dir /proc/pid/net/dev_snmp6/, and current pde is tun3; 2) in the [time windows] unregister netdevice tun3 and tun2, and erase them from rbtree. erase tun3 first, and then erase tun2. the pde(tun2) will be released to slab; 3) continue to getdent process, then pde_subdir_next() will return pde(tun2) which is released, it will case uaf access. CPU 0 | CPU 1 ------------------------------------------------------------------------- traverse dir /proc/pid/net/dev_snmp6/ | unregister_netdevice(tun->dev) //tun3 tun2 sys_getdents64() | iterate_dir() | proc_readdir() | proc_readdir_de() | snmp6_unregister_dev() pde_get(de); | proc_remove() read_unlock(&proc_subdir_lock); | remove_proc_subtree() | write_lock(&proc_subdir_lock); [time window] | rb_erase(&root->subdir_node, &parent->subdir); | write_unlock(&proc_subdir_lock); read_lock(&proc_subdir_lock); | next = pde_subdir_next(de); | pde_put(de); | de = next; //UAF | rbtree of dev_snmp6 | pde(tun3) / \ NULL pde(tun2) Link: https://lkml.kernel.org/r/20251025024233.158363-1-albin_yang@163.com Signed-off-by: Wei Yang <albinwyang@tencent.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Christian Brauner <brauner@kernel.org> Cc: wangzijie <wangzijie1@honor.com> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent fa5a061 commit 895b4c0

File tree

1 file changed

+9
-3
lines changed

1 file changed

+9
-3
lines changed

fs/proc/generic.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,12 @@ void pde_put(struct proc_dir_entry *pde)
698698
}
699699
}
700700

701+
static void pde_erase(struct proc_dir_entry *pde, struct proc_dir_entry *parent)
702+
{
703+
rb_erase(&pde->subdir_node, &parent->subdir);
704+
RB_CLEAR_NODE(&pde->subdir_node);
705+
}
706+
701707
/*
702708
* Remove a /proc entry and free it if it's not currently in use.
703709
*/
@@ -720,7 +726,7 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
720726
WARN(1, "removing permanent /proc entry '%s'", de->name);
721727
de = NULL;
722728
} else {
723-
rb_erase(&de->subdir_node, &parent->subdir);
729+
pde_erase(de, parent);
724730
if (S_ISDIR(de->mode))
725731
parent->nlink--;
726732
}
@@ -764,7 +770,7 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)
764770
root->parent->name, root->name);
765771
return -EINVAL;
766772
}
767-
rb_erase(&root->subdir_node, &parent->subdir);
773+
pde_erase(root, parent);
768774

769775
de = root;
770776
while (1) {
@@ -776,7 +782,7 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)
776782
next->parent->name, next->name);
777783
return -EINVAL;
778784
}
779-
rb_erase(&next->subdir_node, &de->subdir);
785+
pde_erase(next, de);
780786
de = next;
781787
continue;
782788
}

0 commit comments

Comments
 (0)