]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: avoid level II oplock break notification on unlink
authorNamjae Jeon <linkinjeon@kernel.org>
Sun, 21 Jun 2026 10:49:05 +0000 (19:49 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:05 +0000 (20:15 -0500)
smb2_util_unlink() opens the target with FILE_DELETE_ON_CLOSE and then
closes that handle.  Other clients can also mark a file for delete with
SMB2 SET_INFO FileDispositionInformation.

When these unlink paths break existing SMB2 level II oplocks, ksmbd sends
an unsolicited SMB2_OPLOCK_BREAK notification to none.  This races with the
synchronous CREATE or SET_INFO response expected by the client, and
smbtorture reports NT_STATUS_INVALID_NETWORK_RESPONSE while running
smb2.oplock.exclusive2.

SMB2 level II oplock breaks do not require an acknowledgment in the delete
path.  Keep lease handling unchanged, but drop plain SMB2 level II oplocks
locally for unlink requests without sending a break notification.  Normal
write/truncate paths still send the level II to none notification,
preserving the behavior covered by smb2.oplock.levelII500.

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

index 3f35ee7f7d005ea9598d161f168ad1468b200209..5abeb90ebebb3bc76e69dd389351feca9f5fa9cf 100644 (file)
@@ -1546,7 +1546,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
  */
 static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
                                         struct ksmbd_file *fp, int is_trunc,
-                                        bool send_interim)
+                                        bool send_interim, bool send_oplock_break)
 {
        struct oplock_info *op, *brk_op;
        struct ksmbd_inode *ci;
@@ -1593,10 +1593,15 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
                            SMB2_LEASE_KEY_SIZE))
                        goto next;
                brk_op->open_trunc = is_trunc;
-               oplock_break(brk_op,
-                            brk_op->is_lease && !is_trunc ?
-                            SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
-                            send_interim && !sent_interim ? work : NULL);
+               if (!brk_op->is_lease && !send_oplock_break) {
+                       brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
+                       brk_op->op_state = OPLOCK_STATE_NONE;
+               } else {
+                       oplock_break(brk_op,
+                                    brk_op->is_lease && !is_trunc ?
+                                    SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
+                                    send_interim && !sent_interim ? work : NULL);
+               }
                sent_interim = true;
 next:
                opinfo_put(brk_op);
@@ -1610,7 +1615,19 @@ next:
 void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
                                int is_trunc)
 {
-       __smb_break_all_levII_oplock(work, fp, is_trunc, true);
+       __smb_break_all_levII_oplock(work, fp, is_trunc, true, true);
+}
+
+void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
+                                          struct ksmbd_file *fp, int is_trunc)
+{
+       __smb_break_all_levII_oplock(work, fp, is_trunc, false, true);
+}
+
+void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
+                                          struct ksmbd_file *fp)
+{
+       __smb_break_all_levII_oplock(work, fp, 0, false, false);
 }
 
 /**
@@ -1627,7 +1644,7 @@ void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
                return;
 
        sent_break = smb_break_all_write_oplock(work, fp, 1);
-       __smb_break_all_levII_oplock(work, fp, 1, !sent_break);
+       __smb_break_all_levII_oplock(work, fp, 1, !sent_break, true);
 }
 
 /**
index 8d19435862468f3de5b4e3d6364b9aa41c975ede..3f581d22bb676fb7fd85503999c05ed4da0e1581 100644 (file)
@@ -99,6 +99,10 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
                     struct lease_ctx_info *lctx, int share_ret);
 void smb_break_all_levII_oplock(struct ksmbd_work *work,
                                struct ksmbd_file *fp, int is_trunc);
+void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
+                                          struct ksmbd_file *fp, int is_trunc);
+void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
+                                          struct ksmbd_file *fp);
 int opinfo_write_to_read(struct oplock_info *opinfo);
 int opinfo_read_handle_to_read(struct oplock_info *opinfo);
 int opinfo_write_to_none(struct oplock_info *opinfo);
index 6b84a8ea5b154c83efd5bcfc73e5090e781cb53b..d4a40cede7bd0ef1647053aa8f25e7c9b0e18f7f 100644 (file)
@@ -3809,7 +3809,7 @@ int smb2_open(struct ksmbd_work *work)
        }
 
        if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
-               smb_break_all_levII_oplock(work, fp, 0);
+               smb_break_all_levII_oplock_for_delete(work, fp);
                ksmbd_fd_set_delete_on_close(fp, file_info);
        }
 
@@ -6796,7 +6796,7 @@ static int set_file_disposition_info(struct ksmbd_work *work,
                if (S_ISDIR(inode->i_mode) &&
                    ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
                        return -EBUSY;
-               smb_break_all_levII_oplock(work, fp, 0);
+               smb_break_all_levII_oplock_for_delete(work, fp);
                ksmbd_set_inode_pending_delete(fp);
        } else {
                ksmbd_clear_inode_pending_delete(fp);