]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ksmbd: fix use-after-free in ksmbd_free_work_struct
authorNamjae Jeon <linkinjeon@kernel.org>
Wed, 5 Mar 2025 12:21:43 +0000 (21:21 +0900)
committerSteve French <stfrench@microsoft.com>
Mon, 10 Mar 2025 17:54:28 +0000 (12:54 -0500)
->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>
fs/smb/server/ksmbd_work.c
fs/smb/server/ksmbd_work.h
fs/smb/server/oplock.c
fs/smb/server/oplock.h

index 4af2e6007c29d3ca3f92a728c8417e4d39581f79..72b00ca6e455172bca35b616a777dac805dc4099 100644 (file)
@@ -26,7 +26,6 @@ struct ksmbd_work *ksmbd_alloc_work_struct(void)
                INIT_LIST_HEAD(&work->request_entry);
                INIT_LIST_HEAD(&work->async_request_entry);
                INIT_LIST_HEAD(&work->fp_entry);
-               INIT_LIST_HEAD(&work->interim_entry);
                INIT_LIST_HEAD(&work->aux_read_list);
                work->iov_alloc_cnt = 4;
                work->iov = kcalloc(work->iov_alloc_cnt, sizeof(struct kvec),
@@ -56,8 +55,6 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
        kfree(work->tr_buf);
        kvfree(work->request_buf);
        kfree(work->iov);
-       if (!list_empty(&work->interim_entry))
-               list_del(&work->interim_entry);
 
        if (work->async_id)
                ksmbd_release_id(&work->conn->async_ida, work->async_id);
index 8ca2c813246e613b6735eb795efb60973d2bec6f..d36393ff8310cd7e9eb6ea6e18314a51ab15e76f 100644 (file)
@@ -89,7 +89,6 @@ struct ksmbd_work {
        /* List head at conn->async_requests */
        struct list_head                async_request_entry;
        struct list_head                fp_entry;
-       struct list_head                interim_entry;
 };
 
 /**
index 3a3fe4afbdf0df677a63a332a21a2d90567faf75..2febd1c8e278cbd1ab10f483e26e3e914e6951c3 100644 (file)
@@ -46,7 +46,6 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
        opinfo->fid = id;
        opinfo->Tid = Tid;
        INIT_LIST_HEAD(&opinfo->op_entry);
-       INIT_LIST_HEAD(&opinfo->interim_list);
        init_waitqueue_head(&opinfo->oplock_q);
        init_waitqueue_head(&opinfo->oplock_brk);
        atomic_set(&opinfo->refcount, 1);
@@ -803,7 +802,6 @@ out:
 static int smb2_lease_break_noti(struct oplock_info *opinfo)
 {
        struct ksmbd_conn *conn = opinfo->conn;
-       struct list_head *tmp, *t;
        struct ksmbd_work *work;
        struct lease_break_info *br_info;
        struct lease *lease = opinfo->o_lease;
@@ -831,16 +829,6 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo)
        work->sess = opinfo->sess;
 
        if (opinfo->op_state == OPLOCK_ACK_WAIT) {
-               list_for_each_safe(tmp, t, &opinfo->interim_list) {
-                       struct ksmbd_work *in_work;
-
-                       in_work = list_entry(tmp, struct ksmbd_work,
-                                            interim_entry);
-                       setup_async_work(in_work, NULL, NULL);
-                       smb2_send_interim_resp(in_work, STATUS_PENDING);
-                       list_del_init(&in_work->interim_entry);
-                       release_async_work(in_work);
-               }
                INIT_WORK(&work->work, __smb2_lease_break_noti);
                ksmbd_queue_work(work);
                wait_for_break_ack(opinfo);
@@ -871,7 +859,8 @@ static void wait_lease_breaking(struct oplock_info *opinfo)
        }
 }
 
-static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level)
+static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
+                       struct ksmbd_work *in_work)
 {
        int err = 0;
 
@@ -914,9 +903,15 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level)
                }
 
                if (lease->state & (SMB2_LEASE_WRITE_CACHING_LE |
-                               SMB2_LEASE_HANDLE_CACHING_LE))
+                               SMB2_LEASE_HANDLE_CACHING_LE)) {
+                       if (in_work) {
+                               setup_async_work(in_work, NULL, NULL);
+                               smb2_send_interim_resp(in_work, STATUS_PENDING);
+                               release_async_work(in_work);
+                       }
+
                        brk_opinfo->op_state = OPLOCK_ACK_WAIT;
-               else
+               else
                        atomic_dec(&brk_opinfo->breaking_cnt);
        } else {
                err = oplock_break_pending(brk_opinfo, req_op_level);
@@ -1116,7 +1111,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
                        if (ksmbd_conn_releasing(opinfo->conn))
                                continue;
 
-                       oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE);
+                       oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
                        opinfo_put(opinfo);
                }
        }
@@ -1152,7 +1147,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
 
                        if (ksmbd_conn_releasing(opinfo->conn))
                                continue;
-                       oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE);
+                       oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
                        opinfo_put(opinfo);
                }
        }
@@ -1252,8 +1247,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
                goto op_break_not_needed;
        }
 
-       list_add(&work->interim_entry, &prev_opinfo->interim_list);
-       err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II);
+       err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II, work);
        opinfo_put(prev_opinfo);
        if (err == -ENOENT)
                goto set_lev;
@@ -1322,8 +1316,7 @@ static void smb_break_all_write_oplock(struct ksmbd_work *work,
        }
 
        brk_opinfo->open_trunc = is_trunc;
-       list_add(&work->interim_entry, &brk_opinfo->interim_list);
-       oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II);
+       oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work);
        opinfo_put(brk_opinfo);
 }
 
@@ -1386,7 +1379,7 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
                            SMB2_LEASE_KEY_SIZE))
                        goto next;
                brk_op->open_trunc = is_trunc;
-               oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE);
+               oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE, NULL);
 next:
                opinfo_put(brk_op);
                rcu_read_lock();
index 72bc88a63a408204fa768ca41fa51f5857667092..3f64f07872638e8a0bfb89342b90a24ff2935edc 100644 (file)
@@ -67,7 +67,6 @@ struct oplock_info {
        bool                    is_lease;
        bool                    open_trunc;     /* truncate on open */
        struct lease            *o_lease;
-       struct list_head        interim_list;
        struct list_head        op_entry;
        struct list_head        lease_entry;
        wait_queue_head_t oplock_q; /* Other server threads */