Skip to content

Commit 99a5c13

Browse files
committed
proc: fix UAF in proc_get_inode()
jira LE-3262 cve CVE-2025-21999 Rebuild_History Non-Buildable kernel-5.14.0-570.22.1.el9_6 commit-author Ye Bin <[email protected]> commit 654b33a Empty-Commit: Cherry-Pick Conflicts during history rebuild. Will be included in final tarball splat. Ref for failed cherry-pick at: ciq/ciq_backports/kernel-5.14.0-570.22.1.el9_6/654b33ad.failed Fix race between rmmod and /proc/XXX's inode instantiation. The bug is that pde->proc_ops don't belong to /proc, it belongs to a module, therefore dereferencing it after /proc entry has been registered is a bug unless use_pde/unuse_pde() pair has been used. use_pde/unuse_pde can be avoided (2 atomic ops!) because pde->proc_ops never changes so information necessary for inode instantiation can be saved _before_ proc_register() in PDE itself and used later, avoiding pde->proc_ops->... dereference. rmmod lookup sys_delete_module proc_lookup_de pde_get(de); proc_get_inode(dir->i_sb, de); mod->exit() proc_remove remove_proc_subtree proc_entry_rundown(de); free_module(mod); if (S_ISREG(inode->i_mode)) if (de->proc_ops->proc_read_iter) --> As module is already freed, will trigger UAF BUG: unable to handle page fault for address: fffffbfff80a702b PGD 817fc4067 P4D 817fc4067 PUD 817fc0067 PMD 102ef4067 PTE 0 Oops: Oops: 0000 [#1] PREEMPT SMP KASAN PTI CPU: 26 UID: 0 PID: 2667 Comm: ls Tainted: G Hardware name: QEMU Standard PC (i440FX + PIIX, 1996) RIP: 0010:proc_get_inode+0x302/0x6e0 RSP: 0018:ffff88811c837998 EFLAGS: 00010a06 RAX: dffffc0000000000 RBX: ffffffffc0538140 RCX: 0000000000000007 RDX: 1ffffffff80a702b RSI: 0000000000000001 RDI: ffffffffc0538158 RBP: ffff8881299a6000 R08: 0000000067bbe1e5 R09: 1ffff11023906f20 R10: ffffffffb560ca07 R11: ffffffffb2b43a58 R12: ffff888105bb78f0 R13: ffff888100518048 R14: ffff8881299a6004 R15: 0000000000000001 FS: 00007f95b9686840(0000) GS:ffff8883af100000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: fffffbfff80a702b CR3: 0000000117dd2000 CR4: 00000000000006f0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: <TASK> proc_lookup_de+0x11f/0x2e0 __lookup_slow+0x188/0x350 walk_component+0x2ab/0x4f0 path_lookupat+0x120/0x660 filename_lookup+0x1ce/0x560 vfs_statx+0xac/0x150 __do_sys_newstat+0x96/0x110 do_syscall_64+0x5f/0x170 entry_SYSCALL_64_after_hwframe+0x76/0x7e [[email protected]: don't do 2 atomic ops on the common path] Link: https://lkml.kernel.org/r/3d25ded0-1739-447e-812b-e34da7990dcf@p183 Fixes: 778f3dd ("Fix procfs compat_ioctl regression") Signed-off-by: Ye Bin <[email protected]> Signed-off-by: Alexey Dobriyan <[email protected]> Cc: Al Viro <[email protected]> Cc: David S. Miller <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]> (cherry picked from commit 654b33a) Signed-off-by: Jonathan Maple <[email protected]> # Conflicts: # fs/proc/internal.h
1 parent 8a368f6 commit 99a5c13

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
proc: fix UAF in proc_get_inode()
2+
3+
jira LE-3262
4+
cve CVE-2025-21999
5+
Rebuild_History Non-Buildable kernel-5.14.0-570.22.1.el9_6
6+
commit-author Ye Bin <[email protected]>
7+
commit 654b33ada4ab5e926cd9c570196fefa7bec7c1df
8+
Empty-Commit: Cherry-Pick Conflicts during history rebuild.
9+
Will be included in final tarball splat. Ref for failed cherry-pick at:
10+
ciq/ciq_backports/kernel-5.14.0-570.22.1.el9_6/654b33ad.failed
11+
12+
Fix race between rmmod and /proc/XXX's inode instantiation.
13+
14+
The bug is that pde->proc_ops don't belong to /proc, it belongs to a
15+
module, therefore dereferencing it after /proc entry has been registered
16+
is a bug unless use_pde/unuse_pde() pair has been used.
17+
18+
use_pde/unuse_pde can be avoided (2 atomic ops!) because pde->proc_ops
19+
never changes so information necessary for inode instantiation can be
20+
saved _before_ proc_register() in PDE itself and used later, avoiding
21+
pde->proc_ops->... dereference.
22+
23+
rmmod lookup
24+
sys_delete_module
25+
proc_lookup_de
26+
pde_get(de);
27+
proc_get_inode(dir->i_sb, de);
28+
mod->exit()
29+
proc_remove
30+
remove_proc_subtree
31+
proc_entry_rundown(de);
32+
free_module(mod);
33+
34+
if (S_ISREG(inode->i_mode))
35+
if (de->proc_ops->proc_read_iter)
36+
--> As module is already freed, will trigger UAF
37+
38+
BUG: unable to handle page fault for address: fffffbfff80a702b
39+
PGD 817fc4067 P4D 817fc4067 PUD 817fc0067 PMD 102ef4067 PTE 0
40+
Oops: Oops: 0000 [#1] PREEMPT SMP KASAN PTI
41+
CPU: 26 UID: 0 PID: 2667 Comm: ls Tainted: G
42+
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
43+
RIP: 0010:proc_get_inode+0x302/0x6e0
44+
RSP: 0018:ffff88811c837998 EFLAGS: 00010a06
45+
RAX: dffffc0000000000 RBX: ffffffffc0538140 RCX: 0000000000000007
46+
RDX: 1ffffffff80a702b RSI: 0000000000000001 RDI: ffffffffc0538158
47+
RBP: ffff8881299a6000 R08: 0000000067bbe1e5 R09: 1ffff11023906f20
48+
R10: ffffffffb560ca07 R11: ffffffffb2b43a58 R12: ffff888105bb78f0
49+
R13: ffff888100518048 R14: ffff8881299a6004 R15: 0000000000000001
50+
FS: 00007f95b9686840(0000) GS:ffff8883af100000(0000) knlGS:0000000000000000
51+
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
52+
CR2: fffffbfff80a702b CR3: 0000000117dd2000 CR4: 00000000000006f0
53+
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
54+
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
55+
Call Trace:
56+
<TASK>
57+
proc_lookup_de+0x11f/0x2e0
58+
__lookup_slow+0x188/0x350
59+
walk_component+0x2ab/0x4f0
60+
path_lookupat+0x120/0x660
61+
filename_lookup+0x1ce/0x560
62+
vfs_statx+0xac/0x150
63+
__do_sys_newstat+0x96/0x110
64+
do_syscall_64+0x5f/0x170
65+
entry_SYSCALL_64_after_hwframe+0x76/0x7e
66+
67+
[[email protected]: don't do 2 atomic ops on the common path]
68+
Link: https://lkml.kernel.org/r/3d25ded0-1739-447e-812b-e34da7990dcf@p183
69+
Fixes: 778f3dd5a13c ("Fix procfs compat_ioctl regression")
70+
Signed-off-by: Ye Bin <[email protected]>
71+
Signed-off-by: Alexey Dobriyan <[email protected]>
72+
Cc: Al Viro <[email protected]>
73+
Cc: David S. Miller <[email protected]>
74+
75+
Signed-off-by: Andrew Morton <[email protected]>
76+
(cherry picked from commit 654b33ada4ab5e926cd9c570196fefa7bec7c1df)
77+
Signed-off-by: Jonathan Maple <[email protected]>
78+
79+
# Conflicts:
80+
# fs/proc/internal.h
81+
diff --cc fs/proc/internal.h
82+
index 92bb5d4ce5a3,77a517f91821..000000000000
83+
--- a/fs/proc/internal.h
84+
+++ b/fs/proc/internal.h
85+
@@@ -79,6 -80,25 +79,28 @@@ static inline bool pde_is_permanent(con
86+
return pde->flags & PROC_ENTRY_PERMANENT;
87+
}
88+
89+
++<<<<<<< HEAD
90+
++=======
91+
+ static inline void pde_make_permanent(struct proc_dir_entry *pde)
92+
+ {
93+
+ pde->flags |= PROC_ENTRY_PERMANENT;
94+
+ }
95+
+
96+
+ static inline bool pde_has_proc_read_iter(const struct proc_dir_entry *pde)
97+
+ {
98+
+ return pde->flags & PROC_ENTRY_proc_read_iter;
99+
+ }
100+
+
101+
+ static inline bool pde_has_proc_compat_ioctl(const struct proc_dir_entry *pde)
102+
+ {
103+
+ #ifdef CONFIG_COMPAT
104+
+ return pde->flags & PROC_ENTRY_proc_compat_ioctl;
105+
+ #else
106+
+ return false;
107+
+ #endif
108+
+ }
109+
+
110+
++>>>>>>> 654b33ada4ab (proc: fix UAF in proc_get_inode())
111+
extern struct kmem_cache *proc_dir_entry_cache;
112+
void pde_free(struct proc_dir_entry *pde);
113+
114+
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
115+
index 8379593fa4bb..1edf53be9bdb 100644
116+
--- a/fs/proc/generic.c
117+
+++ b/fs/proc/generic.c
118+
@@ -558,10 +558,16 @@ struct proc_dir_entry *proc_create_reg(const char *name, umode_t mode,
119+
return p;
120+
}
121+
122+
-static inline void pde_set_flags(struct proc_dir_entry *pde)
123+
+static void pde_set_flags(struct proc_dir_entry *pde)
124+
{
125+
if (pde->proc_ops->proc_flags & PROC_ENTRY_PERMANENT)
126+
pde->flags |= PROC_ENTRY_PERMANENT;
127+
+ if (pde->proc_ops->proc_read_iter)
128+
+ pde->flags |= PROC_ENTRY_proc_read_iter;
129+
+#ifdef CONFIG_COMPAT
130+
+ if (pde->proc_ops->proc_compat_ioctl)
131+
+ pde->flags |= PROC_ENTRY_proc_compat_ioctl;
132+
+#endif
133+
}
134+
135+
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
136+
@@ -625,6 +631,7 @@ struct proc_dir_entry *proc_create_seq_private(const char *name, umode_t mode,
137+
p->proc_ops = &proc_seq_ops;
138+
p->seq_ops = ops;
139+
p->state_size = state_size;
140+
+ pde_set_flags(p);
141+
return proc_register(parent, p);
142+
}
143+
EXPORT_SYMBOL(proc_create_seq_private);
144+
@@ -655,6 +662,7 @@ struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode,
145+
return NULL;
146+
p->proc_ops = &proc_single_ops;
147+
p->single_show = show;
148+
+ pde_set_flags(p);
149+
return proc_register(parent, p);
150+
}
151+
EXPORT_SYMBOL(proc_create_single_data);
152+
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
153+
index e2174a8bf617..68d99cea93b2 100644
154+
--- a/fs/proc/inode.c
155+
+++ b/fs/proc/inode.c
156+
@@ -674,13 +674,13 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
157+
158+
if (S_ISREG(inode->i_mode)) {
159+
inode->i_op = de->proc_iops;
160+
- if (de->proc_ops->proc_read_iter)
161+
+ if (pde_has_proc_read_iter(de))
162+
inode->i_fop = &proc_iter_file_ops;
163+
else
164+
inode->i_fop = &proc_reg_file_ops;
165+
#ifdef CONFIG_COMPAT
166+
- if (de->proc_ops->proc_compat_ioctl) {
167+
- if (de->proc_ops->proc_read_iter)
168+
+ if (pde_has_proc_compat_ioctl(de)) {
169+
+ if (pde_has_proc_read_iter(de))
170+
inode->i_fop = &proc_iter_file_ops_compat;
171+
else
172+
inode->i_fop = &proc_reg_file_ops_compat;
173+
* Unmerged path fs/proc/internal.h
174+
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
175+
index 12bf96916c8b..6f7b89b520d6 100644
176+
--- a/include/linux/proc_fs.h
177+
+++ b/include/linux/proc_fs.h
178+
@@ -20,10 +20,13 @@ enum {
179+
* If in doubt, ignore this flag.
180+
*/
181+
#ifdef MODULE
182+
- PROC_ENTRY_PERMANENT = 0U,
183+
+ PROC_ENTRY_PERMANENT = 0U,
184+
#else
185+
- PROC_ENTRY_PERMANENT = 1U << 0,
186+
+ PROC_ENTRY_PERMANENT = 1U << 0,
187+
#endif
188+
+
189+
+ PROC_ENTRY_proc_read_iter = 1U << 1,
190+
+ PROC_ENTRY_proc_compat_ioctl = 1U << 2,
191+
};
192+
193+
struct proc_ops {

0 commit comments

Comments
 (0)