]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: downgrade oplock after break timeout
authorNamjae Jeon <linkinjeon@kernel.org>
Sun, 21 Jun 2026 10:48:27 +0000 (19:48 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:05 +0000 (20:15 -0500)
smb2.oplock.batch22a opens a file with a batch oplock and then issues a
second open that waits for the oplock break timeout.  After the timeout the
second open should succeed, but the granted oplock level must be level II.

When the break times out, oplock_break() returns -ENOENT after invalidating
the previous opener.  smb_grant_oplock() went straight to set_lev with the
original requested oplock level, so the second open could be granted a new
batch oplock.  Downgrade the requested oplock to level II on the -ENOENT
break-timeout path before granting the oplock to the new open.

A break that completes because the previous owner closed its handle from
the oplock break handler must be distinguished from a real timeout.
smb2.oplock.batch7 closes the first handle during the break wait, and the
second open is then expected to be granted the originally requested batch
oplock.  Return -EAGAIN from the non-lease break path when the previous
opener closed during the break wait, recheck sharing in smb_grant_oplock(),
and grant the requested oplock if the close removed the conflict. Real
break timeouts still return -ENOENT and keep the downgrade to level II.

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

index 0fddbaa5ba6394e3ee01f20a819a99bded100664..3f35ee7f7d005ea9598d161f168ad1468b200209 100644 (file)
@@ -1130,7 +1130,7 @@ again:
 
        ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
        if (brk_opinfo->op_state == OPLOCK_CLOSING)
-               err = -ENOENT;
+               err = -EAGAIN;
        wake_up_oplock_break(brk_opinfo);
 
        return err;
@@ -1434,8 +1434,19 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
        if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
                ksmbd_invalidate_durable_fd(prev_fid);
        opinfo_put(prev_opinfo);
-       if (err == -ENOENT)
+       if (err == -EAGAIN) {
+               share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
+               if (share_ret < 0) {
+                       err = share_ret;
+                       goto err_out;
+               }
                goto set_lev;
+       }
+       if (err == -ENOENT) {
+               if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
+                       req_op_level = SMB2_OPLOCK_LEVEL_II;
+               goto set_lev;
+       }
        /* Check all oplock was freed by close */
        else if (err < 0)
                goto err_out;