From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EB54437BE64; Sat, 17 Oct 2026 16:36:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792254975; cv=none; b=E6er/HEYZ1lq5Mn+dfxzgxo/CpVsQrbfggR/JwUxLx+ey11wV2P8YV7awca5PSGjCbzKGEDutDYSy0Dp1C/QdC/2xzwIlTFhtqTxbic3NYc7z12yiEsfZTTPnRDn9tcLViW/HqOlF6hiqW1ywl8ZZ+ezBYxh6tsVyn/qv+9M+WY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792254975; c=relaxed/simple; bh=cWbARBaY9YiJIfw1T7SUx/g78PolM8VUehTa4ZbNrs0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TxNmmgwlqoAmYo0joMxjnY5+rmmFOFACYUkjwPHe3KVmcyE4xWr/DxgCxa9wgGMj/a18zXaWgG/7VxCReIv4uesb+oiinre7i+9P8uqyHbWrvzfWBG998nwwk8LhgVj2HeeQxKVsKy19uO/pzzdwt+vZwtT8uXDCcg46YY0HSvg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=VDOO0eFw; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="VDOO0eFw" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4614D1F000C4; Sat, 17 Oct 2026 16:36:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1792254973; bh=oaeiCM+gP7WhcaErHQ99MOeFdec3kyAzHuTQJQCqbQw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=VDOO0eFwTqONvzmTXdRduOZRPJITyfAG6gf5xfdK2vxyMGyZ5jNYkL7XxZN8SHSuz ppwq6IDKSESgx5L7BpM56PtSbOkelRJINY0sv/oMW9gtArRDP1WrrP6640lEGr0JvU o718CXDFwM5IAujYPloMh9nC2WD9qyvkD/tEZYC4= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Yunseong Kim , Namjae Jeon , Sasha Levin Subject: [PATCH 6.12 623/869] ksmbd: serialize oplock close with pending break ownership Date: Sat, 17 Oct 2026 17:57:44 +0200 Message-ID: <20261017160053.002826316@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261017160036.979869304@linuxfoundation.org> References: <20261017160036.979869304@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Namjae Jeon [ Upstream commit b0148dc5625dbfd50596ac63c7487c12e8a8ab03 ] close may abort an in-flight oplock break while another breaker already holds an opinfo reference. Releasing pending_break wakes that waiter, but without serializing the close transition with bit acquisition it can become a new break owner through the test_and_set_bit() fast path. It can then overwrite OPLOCK_CLOSING with OPLOCK_ACK_WAIT and continue a break for a dying opinfo. Make OPLOCK_CLOSING terminal once the opinfo is removed from the inode list. Serialize that transition, pending_break acquisition, and OPLOCK_ACK_WAIT setup with an opinfo state lock. A breaker which loses the race releases its ownership and returns -ENOENT. Explicitly wake pending_break waiters during close so they can observe the terminal state. Also prevent ACK and timeout paths from replacing OPLOCK_CLOSING with OPLOCK_STATE_NONE. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Co-developed-by: Yunseong Kim Signed-off-by: Yunseong Kim Signed-off-by: Namjae Jeon Signed-off-by: Sasha Levin --- fs/smb/server/oplock.c | 81 ++++++++++++++++++++++++++++++++++------- fs/smb/server/oplock.h | 1 + fs/smb/server/smb2pdu.c | 12 ++++-- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 6183bd52ca9d1..08e3e4504b03f 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -56,6 +56,7 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work, opinfo->conn = ksmbd_conn_get(work->conn); opinfo->level = SMB2_OPLOCK_LEVEL_NONE; opinfo->op_state = OPLOCK_STATE_NONE; + spin_lock_init(&opinfo->state_lock); opinfo->pending_break = 0; opinfo->fid = id; opinfo->Tid = Tid; @@ -424,14 +425,23 @@ void close_id_del_oplock(struct ksmbd_file *fp) opinfo_del(opinfo); rcu_assign_pointer(fp->f_opinfo, NULL); - if (opinfo->op_state == OPLOCK_ACK_WAIT) { - opinfo->op_state = OPLOCK_CLOSING; - wake_up_interruptible_all(&opinfo->oplock_q); - if (opinfo->is_lease) { - atomic_set(&opinfo->breaking_cnt, 0); - wake_up_interruptible_all(&opinfo->oplock_brk); - } - } + spin_lock(&opinfo->state_lock); + if (opinfo->op_state == OPLOCK_ACK_WAIT && opinfo->is_lease) + atomic_set(&opinfo->breaking_cnt, 0); + /* + * An opinfo that has been removed from the inode list is terminal. Keep + * this transition and releasing pending_break under state_lock. a breaker + * takes the same lock before it acquires pending_break or sets ACK_WAIT. + */ + opinfo->op_state = OPLOCK_CLOSING; + clear_bit_unlock(0, &opinfo->pending_break); + spin_unlock(&opinfo->state_lock); + wake_up_interruptible_all(&opinfo->oplock_q); + if (opinfo->is_lease) + wake_up_interruptible_all(&opinfo->oplock_brk); + /* memory barrier is needed for wake_up_bit() */ + smp_mb__after_atomic(); + wake_up_bit(&opinfo->pending_break, 0); opinfo_count_dec(fp); atomic_dec(&opinfo->refcount); @@ -610,10 +620,16 @@ static void wait_for_break_ack(struct oplock_info *opinfo) /* is this a timeout ? */ if (!rc) { + spin_lock(&opinfo->state_lock); + if (opinfo->op_state == OPLOCK_CLOSING) { + spin_unlock(&opinfo->state_lock); + return; + } if (opinfo->is_lease) opinfo->o_lease->state = SMB2_LEASE_NONE_LE; opinfo->level = SMB2_OPLOCK_LEVEL_NONE; opinfo->op_state = OPLOCK_STATE_NONE; + spin_unlock(&opinfo->state_lock); } } @@ -625,17 +641,46 @@ static void wake_up_oplock_break(struct oplock_info *opinfo) wake_up_bit(&opinfo->pending_break, 0); } +static bool oplock_break_set_ack_wait(struct oplock_info *opinfo) +{ + bool ret = false; + + spin_lock(&opinfo->state_lock); + if (opinfo->op_state != OPLOCK_CLOSING) { + opinfo->op_state = OPLOCK_ACK_WAIT; + ret = true; + } + spin_unlock(&opinfo->state_lock); + + return ret; +} + static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level) { - while (test_and_set_bit(0, &opinfo->pending_break)) { + for (;;) { + bool closing; + + spin_lock(&opinfo->state_lock); + closing = opinfo->op_state == OPLOCK_CLOSING; + if (!closing && !test_and_set_bit(0, &opinfo->pending_break)) { + spin_unlock(&opinfo->state_lock); + break; + } + spin_unlock(&opinfo->state_lock); + if (closing) + return -ENOENT; + wait_on_bit(&opinfo->pending_break, 0, TASK_UNINTERRUPTIBLE); /* Not immediately break to none. */ opinfo->open_trunc = 0; - if (opinfo->op_state == OPLOCK_CLOSING) + spin_lock(&opinfo->state_lock); + closing = opinfo->op_state == OPLOCK_CLOSING; + spin_unlock(&opinfo->state_lock); + if (closing) return -ENOENT; - else if (opinfo->level <= req_op_level) { + if (opinfo->level <= req_op_level) { if (opinfo->is_lease == false) return 1; @@ -1005,7 +1050,11 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci, release_async_work(in_work); } - brk_opinfo->op_state = OPLOCK_ACK_WAIT; + if (!oplock_break_set_ack_wait(brk_opinfo)) { + atomic_dec_if_positive(&brk_opinfo->breaking_cnt); + wake_up_oplock_break(brk_opinfo); + return -ENOENT; + } } else atomic_dec(&brk_opinfo->breaking_cnt); } else { @@ -1014,8 +1063,12 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci, return err < 0 ? err : 0; if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH || - brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) - brk_opinfo->op_state = OPLOCK_ACK_WAIT; + brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) { + if (!oplock_break_set_ack_wait(brk_opinfo)) { + wake_up_oplock_break(brk_opinfo); + return -ENOENT; + } + } } if (brk_opinfo->is_lease) diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h index 795a9119dad9a..6e8680c06db97 100644 --- a/fs/smb/server/oplock.h +++ b/fs/smb/server/oplock.h @@ -59,6 +59,7 @@ struct oplock_info { struct ksmbd_file *o_fp; int level; int op_state; + spinlock_t state_lock; unsigned long pending_break; u64 fid; atomic_t breaking_cnt; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index e46b58a768c5d..6212e615aa0a1 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -8827,7 +8827,10 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work) smb2_set_err_rsp(work); out: - opinfo->op_state = OPLOCK_STATE_NONE; + spin_lock(&opinfo->state_lock); + if (opinfo->op_state != OPLOCK_CLOSING) + opinfo->op_state = OPLOCK_STATE_NONE; + spin_unlock(&opinfo->state_lock); wake_up_interruptible_all(&opinfo->oplock_q); opinfo_put(opinfo); ksmbd_fd_put(work, fp); @@ -8913,9 +8916,12 @@ static void smb21_lease_break_ack(struct ksmbd_work *work) if (ret) goto err_out; - opinfo->op_state = OPLOCK_STATE_NONE; + spin_lock(&opinfo->state_lock); + if (opinfo->op_state != OPLOCK_CLOSING) + opinfo->op_state = OPLOCK_STATE_NONE; + spin_unlock(&opinfo->state_lock); wake_up_interruptible_all(&opinfo->oplock_q); - atomic_dec(&opinfo->breaking_cnt); + atomic_dec_if_positive(&opinfo->breaking_cnt); wake_up_interruptible_all(&opinfo->oplock_brk); opinfo_put(opinfo); return; -- 2.53.0