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 ksmbd_free_work_struct

->interim_entry of ksmbd_work could be deleted after oplock is freed.
We don't need to manage it with linked list. The interim request could be
immediately sent whenever a oplock break wait is needed.

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
bb39ed47 80e54e84

+15 -27
-3
fs/smb/server/ksmbd_work.c
··· 26 26 INIT_LIST_HEAD(&work->request_entry); 27 27 INIT_LIST_HEAD(&work->async_request_entry); 28 28 INIT_LIST_HEAD(&work->fp_entry); 29 - INIT_LIST_HEAD(&work->interim_entry); 30 29 INIT_LIST_HEAD(&work->aux_read_list); 31 30 work->iov_alloc_cnt = 4; 32 31 work->iov = kcalloc(work->iov_alloc_cnt, sizeof(struct kvec), ··· 55 56 kfree(work->tr_buf); 56 57 kvfree(work->request_buf); 57 58 kfree(work->iov); 58 - if (!list_empty(&work->interim_entry)) 59 - list_del(&work->interim_entry); 60 59 61 60 if (work->async_id) 62 61 ksmbd_release_id(&work->conn->async_ida, work->async_id);
-1
fs/smb/server/ksmbd_work.h
··· 89 89 /* List head at conn->async_requests */ 90 90 struct list_head async_request_entry; 91 91 struct list_head fp_entry; 92 - struct list_head interim_entry; 93 92 }; 94 93 95 94 /**
+15 -22
fs/smb/server/oplock.c
··· 46 46 opinfo->fid = id; 47 47 opinfo->Tid = Tid; 48 48 INIT_LIST_HEAD(&opinfo->op_entry); 49 - INIT_LIST_HEAD(&opinfo->interim_list); 50 49 init_waitqueue_head(&opinfo->oplock_q); 51 50 init_waitqueue_head(&opinfo->oplock_brk); 52 51 atomic_set(&opinfo->refcount, 1); ··· 802 803 static int smb2_lease_break_noti(struct oplock_info *opinfo) 803 804 { 804 805 struct ksmbd_conn *conn = opinfo->conn; 805 - struct list_head *tmp, *t; 806 806 struct ksmbd_work *work; 807 807 struct lease_break_info *br_info; 808 808 struct lease *lease = opinfo->o_lease; ··· 829 831 work->sess = opinfo->sess; 830 832 831 833 if (opinfo->op_state == OPLOCK_ACK_WAIT) { 832 - list_for_each_safe(tmp, t, &opinfo->interim_list) { 833 - struct ksmbd_work *in_work; 834 - 835 - in_work = list_entry(tmp, struct ksmbd_work, 836 - interim_entry); 837 - setup_async_work(in_work, NULL, NULL); 838 - smb2_send_interim_resp(in_work, STATUS_PENDING); 839 - list_del_init(&in_work->interim_entry); 840 - release_async_work(in_work); 841 - } 842 834 INIT_WORK(&work->work, __smb2_lease_break_noti); 843 835 ksmbd_queue_work(work); 844 836 wait_for_break_ack(opinfo); ··· 859 871 } 860 872 } 861 873 862 - static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level) 874 + static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level, 875 + struct ksmbd_work *in_work) 863 876 { 864 877 int err = 0; 865 878 ··· 903 914 } 904 915 905 916 if (lease->state & (SMB2_LEASE_WRITE_CACHING_LE | 906 - SMB2_LEASE_HANDLE_CACHING_LE)) 917 + SMB2_LEASE_HANDLE_CACHING_LE)) { 918 + if (in_work) { 919 + setup_async_work(in_work, NULL, NULL); 920 + smb2_send_interim_resp(in_work, STATUS_PENDING); 921 + release_async_work(in_work); 922 + } 923 + 907 924 brk_opinfo->op_state = OPLOCK_ACK_WAIT; 908 - else 925 + } else 909 926 atomic_dec(&brk_opinfo->breaking_cnt); 910 927 } else { 911 928 err = oplock_break_pending(brk_opinfo, req_op_level); ··· 1111 1116 if (ksmbd_conn_releasing(opinfo->conn)) 1112 1117 continue; 1113 1118 1114 - oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE); 1119 + oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL); 1115 1120 opinfo_put(opinfo); 1116 1121 } 1117 1122 } ··· 1147 1152 1148 1153 if (ksmbd_conn_releasing(opinfo->conn)) 1149 1154 continue; 1150 - oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE); 1155 + oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL); 1151 1156 opinfo_put(opinfo); 1152 1157 } 1153 1158 } ··· 1247 1252 goto op_break_not_needed; 1248 1253 } 1249 1254 1250 - list_add(&work->interim_entry, &prev_opinfo->interim_list); 1251 - err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II); 1255 + err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II, work); 1252 1256 opinfo_put(prev_opinfo); 1253 1257 if (err == -ENOENT) 1254 1258 goto set_lev; ··· 1316 1322 } 1317 1323 1318 1324 brk_opinfo->open_trunc = is_trunc; 1319 - list_add(&work->interim_entry, &brk_opinfo->interim_list); 1320 - oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II); 1325 + oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work); 1321 1326 opinfo_put(brk_opinfo); 1322 1327 } 1323 1328 ··· 1379 1386 SMB2_LEASE_KEY_SIZE)) 1380 1387 goto next; 1381 1388 brk_op->open_trunc = is_trunc; 1382 - oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE); 1389 + oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE, NULL); 1383 1390 next: 1384 1391 opinfo_put(brk_op); 1385 1392 rcu_read_lock();
-1
fs/smb/server/oplock.h
··· 67 67 bool is_lease; 68 68 bool open_trunc; /* truncate on open */ 69 69 struct lease *o_lease; 70 - struct list_head interim_list; 71 70 struct list_head op_entry; 72 71 struct list_head lease_entry; 73 72 wait_queue_head_t oplock_q; /* Other server threads */