[PATCH v4 3/6] ksmbd: use the channel signing key for session setup on a bound channel
From: Michail Andreas Michalopoulos
Date: Tue Nov 10 2026 - 16:11:39 EST
smb3_check_sign_req() and smb3_set_sign_rsp() always use the session
signing key for SESSION_SETUP. That is right for a binding request and
its non-final responses (MS-SMB2 3.3.5.2.4, 3.3.5.5.3), but a
reauthentication sent on a channel that is already bound is then also
verified and signed with the session key instead of that channel's
key.
Use the session key for binding requests, for their non-final
responses, and for SESSION_SETUP on a connection that is not yet a
channel of the session. Use the channel key for everything else on a
channel. Samba does the same. MS-SMB2 is ambiguous here: 3.2.4.1.1
has the client sign any session setup with Session.SigningKey, while
3.3.5.2.4 has the server verify non-binding requests with
Channel.SigningKey.
The signature of a reauthentication was not checked at all, because
SESSION_SETUP is exempt from the check in __process_request(). A
reauthentication of a live session with a forged signature therefore
got STATUS_MORE_PROCESSING_REQUIRED signed with the session key, with or
without multichannel: the same kind of signing oracle as an unverified
binding request. Verify it with the same helper as binding requests.
Also refuse an unsigned reauthentication of a session that requires
signing, as smbtorture's bind_negative tests expect from Windows,
unless the request is encrypted. A session that negotiated encryption
has sess->sign cleared even when the connection required signing, so
take that and enforced encryption into account too; merely having
negotiated encryption is not enough, since clients such as the Linux
one always offer it and then send unsigned, unencrypted requests. Do
not expire the session when a check fails, since the request did not
come from its owner.
An AI assistant was used to draft the patch and as an independent
reviewer.
Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Michail Andreas Michalopoulos <mike.michalo@xxxxxxxxx>
---
fs/smb/server/smb2pdu.c | 112 ++++++++++++++++++++++++++++++----------
1 file changed, 86 insertions(+), 26 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index ddaa98055154a..293e660265c95 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2561,23 +2561,51 @@ static int krb5_authenticate(struct ksmbd_work *work,
#endif
/*
- * A binding request must be signed with the signing key of the session it
- * binds to (MS-SMB2 3.3.5.2.4). Check it before the request is processed
- * and before any response is signed with that key.
+ * SESSION_SETUP is not covered by the signature check in __process_request(),
+ * because work->sess is not set for it. A binding request must be signed
+ * with the signing key of the session it binds to (MS-SMB2 3.3.5.2.4), and a
+ * signed reauthentication with the key of the channel it arrives on. Check
+ * them before the request is processed and before any response is signed
+ * with that key.
*/
-static bool smb2_check_binding_sign_req(struct ksmbd_work *work,
- struct smb2_hdr *hdr,
- struct ksmbd_session *sess)
+static bool smb2_check_sess_setup_sign_req(struct ksmbd_work *work,
+ struct smb2_hdr *hdr,
+ struct ksmbd_session *sess)
{
struct ksmbd_session *prev_sess = work->sess;
int ret;
work->sess = sess;
- ret = smb3_check_sign_req(work);
+ /* The key, and so the way it signs, belongs to the session */
+ if (sess->dialect >= SMB30_PROT_ID)
+ ret = smb3_check_sign_req(work);
+ else
+ ret = smb2_check_sign_req(work);
work->sess = prev_sess;
return ret;
}
+/*
+ * Whether a reauthentication of @sess must be signed or encrypted. A session
+ * that negotiated encryption has sess->sign cleared even when signing was
+ * required on the connection, while encryption itself is only enforced when
+ * configured, so check what the session actually requires. Merely having
+ * negotiated encryption does not count: clients such as the Linux one always
+ * offer it, and then legitimately send unsigned, unencrypted requests.
+ */
+static bool smb2_sess_needs_protection(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess)
+{
+ if (sess->sign)
+ return true;
+ if (!sess->enc)
+ return false;
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION)
+ return true;
+ return sess->user && !user_guest(sess->user) &&
+ (conn->sign || server_conf.enforced_signing);
+}
+
int smb2_sess_setup(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
@@ -2660,7 +2688,7 @@ int smb2_sess_setup(struct ksmbd_work *work)
goto out_err;
}
- if (!smb2_check_binding_sign_req(work, &req->hdr, sess)) {
+ if (!smb2_check_sess_setup_sign_req(work, &req->hdr, sess)) {
rc = -EKEYREJECTED;
goto out_err;
}
@@ -2754,6 +2782,29 @@ int smb2_sess_setup(struct ksmbd_work *work)
goto out_err;
}
+ /*
+ * A reauthentication of a session that has keys must prove it
+ * comes from the session's owner: a signed request must verify,
+ * and a session that requires signing or encryption must not
+ * accept an unsigned request unless it is encrypted, since
+ * encryption has already authenticated it. The session has
+ * keys once this connection is one of its channels, including
+ * while a reauthentication is in progress. Do not expire the
+ * session if the check fails: the request did not come from
+ * its owner.
+ */
+ if ((READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS ||
+ lookup_chann_list(sess, conn)) &&
+ ((req->hdr.Flags & SMB2_FLAGS_SIGNED) ?
+ !smb2_check_sess_setup_sign_req(work, &req->hdr, sess) :
+ (!work->encrypted &&
+ smb2_sess_needs_protection(conn, sess)))) {
+ rc = -EKEYREJECTED;
+ ksmbd_user_session_put(sess);
+ sess = NULL;
+ goto out_err;
+ }
+
if (work->session_setup_reauth)
WRITE_ONCE(sess->state, SMB2_SESSION_IN_PROGRESS);
@@ -12979,22 +13030,24 @@ int smb3_check_sign_req(struct ksmbd_work *work)
* A key must be used with the algorithm it was generated for: the
* session key with the one of the connection that created the
* session, which can differ from this connection's when binding.
+ *
+ * A binding request, or a SESSION_SETUP on a connection that is not
+ * yet a channel of the session, is verified with the session key. A
+ * request on an existing channel, including a reauthentication, is
+ * verified with that channel's key, so that the session key is not
+ * used at the MessageIds of another connection.
*/
- if (le16_to_cpu(hdr->Command) == SMB2_SESSION_SETUP_HE) {
+ chann = lookup_chann_list(work->sess, conn);
+ if (le16_to_cpu(hdr->Command) == SMB2_SESSION_SETUP_HE &&
+ (!chann || (((struct smb2_sess_setup_req *)hdr)->Flags &
+ SMB2_SESSION_REQ_FLAG_BINDING))) {
signing_key = work->sess->smb3signingkey;
signing_algorithm = work->sess->signing_algorithm;
+ } else if (chann) {
+ signing_key = chann->smb3signingkey;
+ signing_algorithm = conn->signing_algorithm;
} else {
- chann = lookup_chann_list(work->sess, conn);
- if (!chann) {
- if (le16_to_cpu(hdr->Command) != SMB2_SESSION_SETUP_HE ||
- !(hdr->Flags & SMB2_FLAGS_SIGNED))
- return 0;
- signing_key = work->sess->smb3signingkey;
- signing_algorithm = work->sess->signing_algorithm;
- } else {
- signing_key = chann->smb3signingkey;
- signing_algorithm = conn->signing_algorithm;
- }
+ return 0;
}
if (!signing_key) {
@@ -13040,17 +13093,24 @@ void smb3_set_sign_rsp(struct ksmbd_work *work)
hdr = ksmbd_resp_buf_curr(work);
command = le16_to_cpu(hdr->Command);
+ /*
+ * Responses to binding requests, other than the final one, are signed
+ * with the session key (MS-SMB2 3.3.5.5.3), as is any SESSION_SETUP
+ * response on a connection that is not yet a channel. Everything else
+ * on a channel, including a reauthentication on a secondary channel,
+ * is signed with that channel's key.
+ */
+ chann = lookup_chann_list(work->sess, work->conn);
if (command == SMB2_SESSION_SETUP_HE &&
- (!work->session_setup_binding || hdr->Status != STATUS_SUCCESS)) {
+ (!chann || (work->session_setup_binding &&
+ hdr->Status != STATUS_SUCCESS))) {
signing_key = work->sess->smb3signingkey;
signing_algorithm = work->sess->signing_algorithm;
- } else {
- chann = lookup_chann_list(work->sess, work->conn);
- if (!chann) {
- return;
- }
+ } else if (chann) {
signing_key = chann->smb3signingkey;
signing_algorithm = conn->signing_algorithm;
+ } else {
+ return;
}
if (!signing_key)
--
2.43.0