]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: fix lease break and ack state handling
authorNamjae Jeon <linkinjeon@kernel.org>
Thu, 18 Jun 2026 01:33:53 +0000 (10:33 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:03 +0000 (20:15 -0500)
Do not skip valid lease states containing WRITE_CACHING when breaking
level-II/read leases for writes and truncates.

Handle lease break acknowledgments according to the SMB2 rule that the
acknowledged state must be a subset of the server's break target. Apply
the acknowledged state directly and keep the break pending on failed ACKs.

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

index 627cea7fd7ea559ef68bddcf69429b922c2a9c87..cc5eb95ab3f2d690f85bc01726ff50c132525cda 100644 (file)
@@ -1419,14 +1419,8 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
                        continue;
                }
 
-               if (brk_op->is_lease && (brk_op->o_lease->state &
-                   (~(SMB2_LEASE_READ_CACHING_LE |
-                               SMB2_LEASE_HANDLE_CACHING_LE)))) {
-                       ksmbd_debug(OPLOCK, "unexpected lease state(0x%x)\n",
-                                   brk_op->o_lease->state);
-                       goto next;
-               } else if (brk_op->level !=
-                               SMB2_OPLOCK_LEVEL_II) {
+               if (!brk_op->is_lease &&
+                   brk_op->level != SMB2_OPLOCK_LEVEL_II) {
                        ksmbd_debug(OPLOCK, "unexpected oplock(0x%x)\n",
                                    brk_op->level);
                        goto next;
@@ -1478,15 +1472,13 @@ void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
  */
 __u8 smb2_map_lease_to_oplock(__le32 lease_state)
 {
-       if (lease_state == (SMB2_LEASE_HANDLE_CACHING_LE |
-                           SMB2_LEASE_READ_CACHING_LE |
-                           SMB2_LEASE_WRITE_CACHING_LE)) {
+       if ((lease_state & SMB2_LEASE_WRITE_CACHING_LE) &&
+           (lease_state & SMB2_LEASE_HANDLE_CACHING_LE)) {
                return SMB2_OPLOCK_LEVEL_BATCH;
-       } else if (lease_state != SMB2_LEASE_WRITE_CACHING_LE &&
-                lease_state & SMB2_LEASE_WRITE_CACHING_LE) {
-               if (!(lease_state & SMB2_LEASE_HANDLE_CACHING_LE))
-                       return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
-       } else if (lease_state & SMB2_LEASE_READ_CACHING_LE) {
+       } else if (lease_state & SMB2_LEASE_WRITE_CACHING_LE) {
+               return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+       } else if (lease_state & (SMB2_LEASE_READ_CACHING_LE |
+                                 SMB2_LEASE_HANDLE_CACHING_LE)) {
                return SMB2_OPLOCK_LEVEL_II;
        }
        return 0;
index 0766f0d662be3d67041ae89b7d64d750ecc607ad..75ee9184e5536032d3b5e3e3e89f37be85a9ee40 100644 (file)
@@ -9013,16 +9013,17 @@ err_out:
        ksmbd_fd_put(work, fp);
 }
 
-static int check_lease_state(struct lease *lease, __le32 req_state)
+static bool smb2_lease_state_valid(__le32 state)
 {
-       if ((lease->new_state ==
-            (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE)) &&
-           !(req_state & SMB2_LEASE_WRITE_CACHING_LE)) {
-               lease->new_state = req_state;
-               return 0;
-       }
+       return !(state & ~(SMB2_LEASE_READ_CACHING_LE |
+                          SMB2_LEASE_HANDLE_CACHING_LE |
+                          SMB2_LEASE_WRITE_CACHING_LE));
+}
 
-       if (lease->new_state == req_state)
+static int check_lease_state(struct lease *lease, __le32 req_state)
+{
+       if (smb2_lease_state_valid(req_state) &&
+           !(req_state & ~lease->new_state))
                return 0;
 
        return 1;
@@ -9040,9 +9041,7 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
        struct smb2_lease_ack *req;
        struct smb2_lease_ack *rsp;
        struct oplock_info *opinfo;
-       __le32 err = 0;
        int ret = 0;
-       unsigned int lease_change_type;
        __le32 lease_state;
        struct lease *lease;
 
@@ -9066,80 +9065,23 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
                goto err_out;
        }
 
-       if (check_lease_state(lease, req->LeaseState)) {
-               rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
-               ksmbd_debug(OPLOCK,
-                           "req lease state: 0x%x, expected state: 0x%x\n",
-                           req->LeaseState, lease->new_state);
-               goto err_out;
-       }
-
        if (!atomic_read(&opinfo->breaking_cnt)) {
                rsp->hdr.Status = STATUS_UNSUCCESSFUL;
                goto err_out;
        }
 
-       /* check for bad lease state */
-       if (req->LeaseState &
-           (~(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE))) {
-               err = STATUS_INVALID_OPLOCK_PROTOCOL;
-               if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
-                       lease_change_type = OPLOCK_WRITE_TO_NONE;
-               else
-                       lease_change_type = OPLOCK_READ_TO_NONE;
-               ksmbd_debug(OPLOCK, "handle bad lease state 0x%x -> 0x%x\n",
-                           le32_to_cpu(lease->state),
-                           le32_to_cpu(req->LeaseState));
-       } else if (lease->state == SMB2_LEASE_READ_CACHING_LE &&
-                  req->LeaseState != SMB2_LEASE_NONE_LE) {
-               err = STATUS_INVALID_OPLOCK_PROTOCOL;
-               lease_change_type = OPLOCK_READ_TO_NONE;
-               ksmbd_debug(OPLOCK, "handle bad lease state 0x%x -> 0x%x\n",
-                           le32_to_cpu(lease->state),
-                           le32_to_cpu(req->LeaseState));
-       } else {
-               /* valid lease state changes */
-               err = STATUS_INVALID_DEVICE_STATE;
-               if (req->LeaseState == SMB2_LEASE_NONE_LE) {
-                       if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
-                               lease_change_type = OPLOCK_WRITE_TO_NONE;
-                       else
-                               lease_change_type = OPLOCK_READ_TO_NONE;
-               } else if (req->LeaseState & SMB2_LEASE_READ_CACHING_LE) {
-                       if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
-                               lease_change_type = OPLOCK_WRITE_TO_READ;
-                       else
-                               lease_change_type = OPLOCK_READ_HANDLE_TO_READ;
-               } else {
-                       lease_change_type = 0;
-               }
-       }
-
-       switch (lease_change_type) {
-       case OPLOCK_WRITE_TO_READ:
-               ret = opinfo_write_to_read(opinfo);
-               break;
-       case OPLOCK_READ_HANDLE_TO_READ:
-               ret = opinfo_read_handle_to_read(opinfo);
-               break;
-       case OPLOCK_WRITE_TO_NONE:
-               ret = opinfo_write_to_none(opinfo);
-               break;
-       case OPLOCK_READ_TO_NONE:
-               ret = opinfo_read_to_none(opinfo);
-               break;
-       default:
-               ksmbd_debug(OPLOCK, "unknown lease change 0x%x -> 0x%x\n",
-                           le32_to_cpu(lease->state),
-                           le32_to_cpu(req->LeaseState));
-       }
-
-       if (ret < 0) {
-               rsp->hdr.Status = err;
+       if (check_lease_state(lease, req->LeaseState)) {
+               rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
+               ksmbd_debug(OPLOCK,
+                           "req lease state: 0x%x, expected state: 0x%x\n",
+                           req->LeaseState, lease->new_state);
                goto err_out;
        }
 
-       lease_state = lease->state;
+       lease_state = req->LeaseState;
+       lease->state = lease_state;
+       lease->new_state = SMB2_LEASE_NONE_LE;
+       opinfo->level = smb2_map_lease_to_oplock(lease_state);
 
        rsp->StructureSize = cpu_to_le16(36);
        rsp->Reserved = 0;
@@ -9148,16 +9090,20 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
        rsp->LeaseState = lease_state;
        rsp->LeaseDuration = 0;
        ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lease_ack));
-       if (ret) {
-err_out:
-               smb2_set_err_rsp(work);
-       }
+       if (ret)
+               goto err_out;
 
        opinfo->op_state = OPLOCK_STATE_NONE;
        wake_up_interruptible_all(&opinfo->oplock_q);
        atomic_dec(&opinfo->breaking_cnt);
        wake_up_interruptible_all(&opinfo->oplock_brk);
        opinfo_put(opinfo);
+       return;
+
+err_out:
+       smb2_set_err_rsp(work);
+       opinfo_put(opinfo);
+       return;
 }
 
 /**