Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ksmbd: fix use-after-free in smb_break_all_levII_oplock()

There is a room in smb_break_all_levII_oplock that can cause racy issues
when unlocking in the middle of the loop. This patch use read lock
to protect whole loop.

Cc: stable@vger.kernel.org
Reported-by: Norbert Szetei <norbert@doyensec.com>
Tested-by: Norbert Szetei <norbert@doyensec.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Namjae Jeon and committed by
Steve French
18b4fac5 21a4e475

+9 -21
+9 -20
fs/smb/server/oplock.c
··· 129 129 kfree(opinfo); 130 130 } 131 131 132 - static inline void opinfo_free_rcu(struct rcu_head *rcu_head) 133 - { 134 - struct oplock_info *opinfo; 135 - 136 - opinfo = container_of(rcu_head, struct oplock_info, rcu_head); 137 - free_opinfo(opinfo); 138 - } 139 - 140 132 struct oplock_info *opinfo_get(struct ksmbd_file *fp) 141 133 { 142 134 struct oplock_info *opinfo; ··· 149 157 if (list_empty(&ci->m_op_list)) 150 158 return NULL; 151 159 152 - rcu_read_lock(); 153 - opinfo = list_first_or_null_rcu(&ci->m_op_list, struct oplock_info, 160 + down_read(&ci->m_lock); 161 + opinfo = list_first_entry(&ci->m_op_list, struct oplock_info, 154 162 op_entry); 155 163 if (opinfo) { 156 164 if (opinfo->conn == NULL || ··· 163 171 } 164 172 } 165 173 } 166 - 167 - rcu_read_unlock(); 174 + up_read(&ci->m_lock); 168 175 169 176 return opinfo; 170 177 } ··· 176 185 if (!atomic_dec_and_test(&opinfo->refcount)) 177 186 return; 178 187 179 - call_rcu(&opinfo->rcu_head, opinfo_free_rcu); 188 + free_opinfo(opinfo); 180 189 } 181 190 182 191 static void opinfo_add(struct oplock_info *opinfo) ··· 184 193 struct ksmbd_inode *ci = opinfo->o_fp->f_ci; 185 194 186 195 down_write(&ci->m_lock); 187 - list_add_rcu(&opinfo->op_entry, &ci->m_op_list); 196 + list_add(&opinfo->op_entry, &ci->m_op_list); 188 197 up_write(&ci->m_lock); 189 198 } 190 199 ··· 198 207 write_unlock(&lease_list_lock); 199 208 } 200 209 down_write(&ci->m_lock); 201 - list_del_rcu(&opinfo->op_entry); 210 + list_del(&opinfo->op_entry); 202 211 up_write(&ci->m_lock); 203 212 } 204 213 ··· 1338 1347 ci = fp->f_ci; 1339 1348 op = opinfo_get(fp); 1340 1349 1341 - rcu_read_lock(); 1342 - list_for_each_entry_rcu(brk_op, &ci->m_op_list, op_entry) { 1350 + down_read(&ci->m_lock); 1351 + list_for_each_entry(brk_op, &ci->m_op_list, op_entry) { 1343 1352 if (brk_op->conn == NULL) 1344 1353 continue; 1345 1354 ··· 1349 1358 if (ksmbd_conn_releasing(brk_op->conn)) 1350 1359 continue; 1351 1360 1352 - rcu_read_unlock(); 1353 1361 if (brk_op->is_lease && (brk_op->o_lease->state & 1354 1362 (~(SMB2_LEASE_READ_CACHING_LE | 1355 1363 SMB2_LEASE_HANDLE_CACHING_LE)))) { ··· 1378 1388 oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE, NULL); 1379 1389 next: 1380 1390 opinfo_put(brk_op); 1381 - rcu_read_lock(); 1382 1391 } 1383 - rcu_read_unlock(); 1392 + up_read(&ci->m_lock); 1384 1393 1385 1394 if (op) 1386 1395 opinfo_put(op);
-1
fs/smb/server/oplock.h
··· 71 71 struct list_head lease_entry; 72 72 wait_queue_head_t oplock_q; /* Other server threads */ 73 73 wait_queue_head_t oplock_brk; /* oplock breaking wait */ 74 - struct rcu_head rcu_head; 75 74 }; 76 75 77 76 struct lease_break_info {