]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: align SMB2 oplock break ack handling
authorNamjae Jeon <linkinjeon@kernel.org>
Thu, 18 Jun 2026 01:35:42 +0000 (10:35 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:04 +0000 (20:15 -0500)
Handle SMB2 oplock break acknowledgments according to the server-side
validation rules in MS-SMB2.

Return STATUS_INVALID_DEVICE_STATE when an ACK arrives while the open is
not breaking, reject SMB2_OPLOCK_LEVEL_LEASE with
STATUS_INVALID_PARAMETER, allow BATCH acknowledgments to EXCLUSIVE, and
make invalid ACK levels fail with STATUS_INVALID_OPLOCK_PROTOCOL after
lowering the oplock to NONE.

Update the successful response from the final granted oplock level instead
of relying on the oplock transition helpers, which could turn invalid ACKs
into successful responses.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/smb2pdu.c

index f3a57a32c4a8a85ef5c157742ed792b4f4c292b3..b84062b16a75ad156c65585dfe7a721fb0888324 100644 (file)
@@ -8900,11 +8900,10 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
        struct smb2_oplock_break *rsp;
        struct ksmbd_file *fp;
        struct oplock_info *opinfo = NULL;
-       __le32 err = 0;
-       int ret = 0;
+       __le32 status = STATUS_SUCCESS;
+       int ret;
        u64 volatile_id, persistent_id;
        char req_oplevel = 0, rsp_oplevel = 0;
-       unsigned int oplock_change_type;
 
        WORK_BUFFERS(work, req, rsp);
 
@@ -8930,71 +8929,55 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
                return;
        }
 
-       if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE) {
-               rsp->hdr.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+       if (opinfo->op_state != OPLOCK_ACK_WAIT) {
+               ksmbd_debug(SMB, "unexpected oplock state 0x%x\n",
+                           opinfo->op_state);
+               status = STATUS_INVALID_DEVICE_STATE;
                goto err_out;
        }
 
-       if (opinfo->op_state == OPLOCK_STATE_NONE) {
-               ksmbd_debug(SMB, "unexpected oplock state 0x%x\n", opinfo->op_state);
-               rsp->hdr.Status = STATUS_UNSUCCESSFUL;
+       if (req_oplevel == SMB2_OPLOCK_LEVEL_LEASE) {
+               opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+               status = STATUS_INVALID_PARAMETER;
                goto err_out;
        }
 
-       if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
-            opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
-           (req_oplevel != SMB2_OPLOCK_LEVEL_II &&
-            req_oplevel != SMB2_OPLOCK_LEVEL_NONE)) {
-               err = STATUS_INVALID_OPLOCK_PROTOCOL;
-               oplock_change_type = OPLOCK_WRITE_TO_NONE;
-       } else if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
-                  req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
-               err = STATUS_INVALID_OPLOCK_PROTOCOL;
-               oplock_change_type = OPLOCK_READ_TO_NONE;
-       } else if (req_oplevel == SMB2_OPLOCK_LEVEL_II ||
-                  req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
-               err = STATUS_INVALID_DEVICE_STATE;
-               if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
-                    opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
-                   req_oplevel == SMB2_OPLOCK_LEVEL_II) {
-                       oplock_change_type = OPLOCK_WRITE_TO_READ;
-               } else if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
-                           opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
-                          req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
-                       oplock_change_type = OPLOCK_WRITE_TO_NONE;
-               } else if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
-                          req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
-                       oplock_change_type = OPLOCK_READ_TO_NONE;
-               } else {
-                       oplock_change_type = 0;
-               }
-       } else {
-               oplock_change_type = 0;
+       if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE) {
+               status = STATUS_INVALID_OPLOCK_PROTOCOL;
+               goto err_out;
        }
 
-       switch (oplock_change_type) {
-       case OPLOCK_WRITE_TO_READ:
-               ret = opinfo_write_to_read(opinfo);
-               rsp_oplevel = SMB2_OPLOCK_LEVEL_II;
-               break;
-       case OPLOCK_WRITE_TO_NONE:
-               ret = opinfo_write_to_none(opinfo);
-               rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
-               break;
-       case OPLOCK_READ_TO_NONE:
-               ret = opinfo_read_to_none(opinfo);
-               rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
-               break;
-       default:
-               pr_err("unknown oplock change 0x%x -> 0x%x\n",
-                      opinfo->level, rsp_oplevel);
+       if (opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE &&
+           req_oplevel != SMB2_OPLOCK_LEVEL_II &&
+           req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
+               opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+               status = STATUS_INVALID_OPLOCK_PROTOCOL;
+               goto err_out;
        }
 
-       if (ret < 0) {
-               rsp->hdr.Status = err;
+       if (opinfo->level == SMB2_OPLOCK_LEVEL_BATCH &&
+           req_oplevel != SMB2_OPLOCK_LEVEL_II &&
+           req_oplevel != SMB2_OPLOCK_LEVEL_NONE &&
+           req_oplevel != SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+               opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+               status = STATUS_INVALID_OPLOCK_PROTOCOL;
+               goto err_out;
+       }
+
+       if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
+           req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
+               opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+               status = STATUS_INVALID_OPLOCK_PROTOCOL;
                goto err_out;
        }
 
+       if (req_oplevel == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
+               rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
+       else
+               rsp_oplevel = req_oplevel;
+
+       opinfo->level = rsp_oplevel;
+
        rsp->StructureSize = cpu_to_le16(24);
        rsp->OplockLevel = rsp_oplevel;
        rsp->Reserved = 0;
@@ -9002,11 +8985,16 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
        rsp->VolatileFid = volatile_id;
        rsp->PersistentFid = persistent_id;
        ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_oplock_break));
-       if (ret) {
+       if (ret)
+               ksmbd_debug(SMB, "failed to pin oplock break response: %d\n",
+                           ret);
+       goto out;
+
 err_out:
-               smb2_set_err_rsp(work);
-       }
+       rsp->hdr.Status = status;
+       smb2_set_err_rsp(work);
 
+out:
        opinfo->op_state = OPLOCK_STATE_NONE;
        wake_up_interruptible_all(&opinfo->oplock_q);
        opinfo_put(opinfo);