|
| 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 | + |
| 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