]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: fix multichannel binding and enforce channel limit
authorNamjae Jeon <linkinjeon@kernel.org>
Thu, 2 Jul 2026 06:49:53 +0000 (15:49 +0900)
committerSteve French <stfrench@microsoft.com>
Mon, 6 Jul 2026 12:55:41 +0000 (07:55 -0500)
A signed multichannel SESSION_SETUP binding request can require multiple
authentication rounds. ksmbd excludes SESSION_SETUP from the signed
request check and tries to sign every binding response with the channel
signing key. The channel does not exist for
STATUS_MORE_PROCESSING_REQUIRED, so that response is sent unsigned.
Clients reject it with STATUS_ACCESS_DENIED.

The final channel signing key also needs the key exported by the binding
authentication context. Keep that key in the channel instead of
overwriting the established session key, and use the session signing key
for intermediate and failed binding responses. Retain the binding session
reference until an error response has been signed and sent.

Limit a session to 32 channels while holding the channel lock. Return
STATUS_INSUFFICIENT_RESOURCES for an additional binding, matching the
server limit expected by clients.

This fixes smb2.multichannel.generic.num_channels, which previously
failed the first binding with STATUS_ACCESS_DENIED and returned the same
status instead of STATUS_INSUFFICIENT_RESOURCES for channel 33.

Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/auth.c
fs/smb/server/auth.h
fs/smb/server/mgmt/user_session.c
fs/smb/server/mgmt/user_session.h
fs/smb/server/smb2pdu.c

index 86f521e849d5e235ea8e53551b0977cac5075f22..2c7096a782daa358ed287daefc70391de9a3dc63 100644 (file)
@@ -133,16 +133,17 @@ out:
  * @blen:              NTLMv2 blob length
  * @domain_name:       domain name
  * @cryptkey:          session crypto key
+ * @sess_key:          derived session key output buffer
  *
  * Return:     0 on success, error number on error
  */
 int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
                      struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
-                     char *cryptkey)
+                     char *cryptkey, char *sess_key)
 {
        char ntlmv2_hash[CIFS_ENCPWD_SIZE];
        char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
-       char sess_key[SMB2_NTLMV2_SESSKEY_SIZE];
+       char base_key[SMB2_NTLMV2_SESSKEY_SIZE];
        struct hmac_md5_ctx ctx;
        int rc;
 
@@ -165,7 +166,7 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
        /* Generate the session key */
        hmac_md5_usingrawkey(ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE,
                             ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE,
-                            sess_key);
+                            base_key);
 
        if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp,
                          CIFS_HMAC_MD5_HASH_SIZE)) {
@@ -173,12 +174,12 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
                goto out;
        }
 
-       memcpy(sess->sess_key, sess_key, sizeof(sess_key));
+       memcpy(sess_key, base_key, sizeof(base_key));
        rc = 0;
 out:
        memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash));
        memzero_explicit(ntlmv2_rsp, sizeof(ntlmv2_rsp));
-       memzero_explicit(sess_key, sizeof(sess_key));
+       memzero_explicit(base_key, sizeof(base_key));
        return rc;
 }
 
@@ -189,12 +190,13 @@ out:
  * @blob_len:  length of the @authblob message
  * @conn:      connection
  * @sess:      session of connection
+ * @sess_key:  derived session key output buffer
  *
  * Return:     0 on success, error number on error
  */
 int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
                                   int blob_len, struct ksmbd_conn *conn,
-                                  struct ksmbd_session *sess)
+                                  struct ksmbd_session *sess, char *sess_key)
 {
        char *domain_name;
        unsigned int nt_off, dn_off;
@@ -234,7 +236,7 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
        ret = ksmbd_auth_ntlmv2(conn, sess,
                                (struct ntlmv2_resp *)((char *)authblob + nt_off),
                                nt_len - CIFS_ENCPWD_SIZE,
-                               domain_name, conn->ntlmssp.cryptkey);
+                               domain_name, conn->ntlmssp.cryptkey, sess_key);
        kfree(domain_name);
        if (ret)
                return ret;
@@ -257,8 +259,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
                if (!ctx_arc4)
                        return -ENOMEM;
 
-               arc4_setkey(ctx_arc4, sess->sess_key, SMB2_NTLMV2_SESSKEY_SIZE);
-               arc4_crypt(ctx_arc4, sess->sess_key,
+               arc4_setkey(ctx_arc4, sess_key, SMB2_NTLMV2_SESSKEY_SIZE);
+               arc4_crypt(ctx_arc4, sess_key,
                           (char *)authblob + sess_key_off, sess_key_len);
                kfree_sensitive(ctx_arc4);
        }
@@ -400,7 +402,8 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
 
 #ifdef CONFIG_SMB_SERVER_KERBEROS5
 int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
-                           int in_len, char *out_blob, int *out_len)
+                           int in_len, char *out_blob, int *out_len,
+                           char *sess_key)
 {
        struct ksmbd_spnego_authen_response *resp;
        struct ksmbd_login_response_ext *resp_ext = NULL;
@@ -455,7 +458,7 @@ int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
                ksmbd_free_user(user);
        }
 
-       memcpy(sess->sess_key, resp->payload, resp->session_key_len);
+       memcpy(sess_key, resp->payload, resp->session_key_len);
        memcpy(out_blob, resp->payload + resp->session_key_len,
               resp->spnego_blob_len);
        *out_len = resp->spnego_blob_len;
@@ -466,7 +469,8 @@ out:
 }
 #else
 int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
-                           int in_len, char *out_blob, int *out_len)
+                           int in_len, char *out_blob, int *out_len,
+                           char *sess_key)
 {
        return -EOPNOTSUPP;
 }
@@ -525,7 +529,7 @@ struct derivation {
        bool binding;
 };
 
-static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess,
+static void generate_key(struct ksmbd_conn *conn, const char *sess_key,
                         struct kvec label, struct kvec context, __u8 *key,
                         unsigned int key_size)
 {
@@ -536,7 +540,7 @@ static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess,
        unsigned char prfhash[SMB2_HMACSHA256_SIZE];
        struct hmac_sha256_ctx ctx;
 
-       hmac_sha256_init_usingrawkey(&ctx, sess->sess_key,
+       hmac_sha256_init_usingrawkey(&ctx, sess_key,
                                     SMB2_NTLMV2_SESSKEY_SIZE);
        hmac_sha256_update(&ctx, i, 4);
        hmac_sha256_update(&ctx, label.iov_base, label.iov_len);
@@ -559,18 +563,21 @@ static int generate_smb3signingkey(struct ksmbd_session *sess,
                                   const struct derivation *signing)
 {
        struct channel *chann;
-       char *key;
+       char *key, *sess_key;
 
        chann = lookup_chann_list(sess, conn);
        if (!chann)
                return 0;
 
-       if (conn->dialect >= SMB30_PROT_ID && signing->binding)
+       if (conn->dialect >= SMB30_PROT_ID && signing->binding) {
                key = chann->smb3signingkey;
-       else
+               sess_key = chann->sess_key;
+       } else {
                key = sess->smb3signingkey;
+               sess_key = sess->sess_key;
+       }
 
-       generate_key(conn, sess, signing->label, signing->context, key,
+       generate_key(conn, sess_key, signing->label, signing->context, key,
                     SMB3_SIGN_KEY_SIZE);
 
        if (!(conn->dialect >= SMB30_PROT_ID && signing->binding))
@@ -627,11 +634,11 @@ static void generate_smb3encryptionkey(struct ksmbd_conn *conn,
                                       struct ksmbd_session *sess,
                                       const struct derivation_twin *ptwin)
 {
-       generate_key(conn, sess, ptwin->encryption.label,
+       generate_key(conn, sess->sess_key, ptwin->encryption.label,
                     ptwin->encryption.context, sess->smb3encryptionkey,
                     SMB3_ENC_DEC_KEY_SIZE);
 
-       generate_key(conn, sess, ptwin->decryption.label,
+       generate_key(conn, sess->sess_key, ptwin->decryption.label,
                     ptwin->decryption.context,
                     sess->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE);
 
index 5767aabc63c9be030139e7d4007ad243d45dafde..f14b7c0332646830b659f283c07805e926b8c0ac 100644 (file)
@@ -41,17 +41,18 @@ int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
 void ksmbd_copy_gss_neg_header(void *buf);
 int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
                      struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
-                     char *cryptkey);
+                     char *cryptkey, char *sess_key);
 int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
                                   int blob_len, struct ksmbd_conn *conn,
-                                  struct ksmbd_session *sess);
+                                  struct ksmbd_session *sess, char *sess_key);
 int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob,
                                  int blob_len, struct ksmbd_conn *conn);
 unsigned int
 ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
                                   struct ksmbd_conn *conn);
 int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
-                           int in_len, char *out_blob, int *out_len);
+                           int in_len, char *out_blob, int *out_len,
+                           char *sess_key);
 void ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
                         int n_vec, char *sig);
 void ksmbd_sign_smb3_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
index de58aed76cb42661d3cdbfd5c47d05f020218bc2..d6331184ebfcbd9b167f064e3c27c371945876b7 100644 (file)
@@ -255,7 +255,7 @@ static void free_channel_list(struct ksmbd_session *sess)
        down_write(&sess->chann_lock);
        xa_for_each(&sess->ksmbd_chann_list, index, chann) {
                xa_erase(&sess->ksmbd_chann_list, index);
-               kfree(chann);
+               kfree_sensitive(chann);
        }
 
        xa_destroy(&sess->ksmbd_chann_list);
@@ -449,7 +449,7 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
        if (!chann)
                return -ENOENT;
 
-       kfree(chann);
+       kfree_sensitive(chann);
        return 0;
 }
 
index 6aebd385be8474898894359139d1a8a53948aec9..8893a9aaede795ac23ed0f57d5e6cbb45ffe00f8 100644 (file)
@@ -19,6 +19,7 @@
 struct ksmbd_file_table;
 
 struct channel {
+       char                    sess_key[SMB2_NTLMV2_SESSKEY_SIZE];
        __u8                    smb3signingkey[SMB3_SIGN_KEY_SIZE];
        struct ksmbd_conn       *conn;
 };
index 097f51fc7ed6a5926462a19dab17da077fff7b22..9c00f944fa3b51970c3d90be50a850c61c1edab8 100644 (file)
@@ -95,6 +95,47 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn
        return chann;
 }
 
+#define KSMBD_MAX_CHANNELS     32
+
+static int register_session_channel(struct ksmbd_session *sess,
+                                   struct ksmbd_conn *conn,
+                                   const char *sess_key)
+{
+       struct channel *chann, *old;
+       unsigned long index;
+       unsigned int count = 0;
+       int rc = 0;
+
+       down_write(&sess->chann_lock);
+       if (xa_load(&sess->ksmbd_chann_list, (long)conn))
+               goto out;
+
+       xa_for_each(&sess->ksmbd_chann_list, index, chann)
+               count++;
+       if (count >= KSMBD_MAX_CHANNELS) {
+               rc = -ENOSPC;
+               goto out;
+       }
+
+       chann = kmalloc_obj(struct channel, KSMBD_DEFAULT_GFP);
+       if (!chann) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       chann->conn = conn;
+       memcpy(chann->sess_key, sess_key, sizeof(chann->sess_key));
+       old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann,
+                      KSMBD_DEFAULT_GFP);
+       if (xa_is_err(old)) {
+               kfree_sensitive(chann);
+               rc = xa_err(old);
+       }
+out:
+       up_write(&sess->chann_lock);
+       return rc;
+}
+
 /**
  * smb2_get_ksmbd_tcon() - get tree connection information using a tree id.
  * @work:      smb work
@@ -1644,9 +1685,11 @@ static int ntlm_authenticate(struct ksmbd_work *work,
 {
        struct ksmbd_conn *conn = work->conn;
        struct ksmbd_session *sess = work->sess;
-       struct channel *chann = NULL, *old;
        struct ksmbd_user *user;
+       char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {};
+       char *auth_key = conn->binding ? channel_key : sess->sess_key;
        u64 prev_id;
+       bool binding = conn->binding;
        int sz, rc;
 
        ksmbd_debug(SMB, "authenticate phase\n");
@@ -1705,11 +1748,13 @@ static int ntlm_authenticate(struct ksmbd_work *work,
                        sz = conn->mechTokenLen;
                else
                        sz = le16_to_cpu(req->SecurityBufferLength);
-               rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, conn, sess);
+               rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, conn, sess,
+                                                   auth_key);
                if (rc) {
                        set_user_flag(sess->user, KSMBD_USER_FLAG_BAD_PASSWORD);
                        ksmbd_debug(SMB, "authentication failed\n");
-                       return -EPERM;
+                       rc = -EPERM;
+                       goto out;
                }
        }
 
@@ -1744,37 +1789,30 @@ static int ntlm_authenticate(struct ksmbd_work *work,
 
 binding_session:
        if (conn->dialect >= SMB30_PROT_ID) {
-               chann = lookup_chann_list(sess, conn);
-               if (!chann) {
-                       chann = kmalloc_obj(struct channel, KSMBD_DEFAULT_GFP);
-                       if (!chann)
-                               return -ENOMEM;
-
-                       chann->conn = conn;
-                       down_write(&sess->chann_lock);
-                       old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann,
-                                       KSMBD_DEFAULT_GFP);
-                       up_write(&sess->chann_lock);
-                       if (xa_is_err(old)) {
-                               kfree(chann);
-                               return xa_err(old);
-                       }
-               }
+               rc = register_session_channel(sess, conn, auth_key);
+               if (rc)
+                       goto out;
        }
 
        if (conn->ops->generate_signingkey) {
                rc = conn->ops->generate_signingkey(sess, conn);
                if (rc) {
                        ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
-                       return -EINVAL;
+                       rc = -EINVAL;
+                       goto out;
                }
        }
 
        if (!ksmbd_conn_lookup_dialect(conn)) {
                pr_err("fail to verify the dialect\n");
-               return -ENOENT;
+               rc = -ENOENT;
+               goto out;
        }
-       return 0;
+       rc = 0;
+out:
+       if (binding)
+               memzero_explicit(channel_key, sizeof(channel_key));
+       return rc;
 }
 
 #ifdef CONFIG_SMB_SERVER_KERBEROS5
@@ -1785,8 +1823,10 @@ static int krb5_authenticate(struct ksmbd_work *work,
        struct ksmbd_conn *conn = work->conn;
        struct ksmbd_session *sess = work->sess;
        char *in_blob, *out_blob;
-       struct channel *chann = NULL, *old;
+       char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {};
+       char *auth_key = conn->binding ? channel_key : sess->sess_key;
        u64 prev_sess_id;
+       bool binding = conn->binding;
        int in_len, out_len;
        int retval;
 
@@ -1799,10 +1839,11 @@ static int krb5_authenticate(struct ksmbd_work *work,
                (le16_to_cpu(rsp->SecurityBufferOffset) + 4);
 
        retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
-                                        out_blob, &out_len);
+                                        out_blob, &out_len, auth_key);
        if (retval) {
                ksmbd_debug(SMB, "krb5 authentication failed\n");
-               return -EINVAL;
+               retval = -EINVAL;
+               goto out;
        }
 
        /* Check previous session */
@@ -1839,37 +1880,30 @@ static int krb5_authenticate(struct ksmbd_work *work,
 
 binding_session:
        if (conn->dialect >= SMB30_PROT_ID) {
-               chann = lookup_chann_list(sess, conn);
-               if (!chann) {
-                       chann = kmalloc_obj(struct channel, KSMBD_DEFAULT_GFP);
-                       if (!chann)
-                               return -ENOMEM;
-
-                       chann->conn = conn;
-                       down_write(&sess->chann_lock);
-                       old = xa_store(&sess->ksmbd_chann_list, (long)conn,
-                                       chann, KSMBD_DEFAULT_GFP);
-                       up_write(&sess->chann_lock);
-                       if (xa_is_err(old)) {
-                               kfree(chann);
-                               return xa_err(old);
-                       }
-               }
+               retval = register_session_channel(sess, conn, auth_key);
+               if (retval)
+                       goto out;
        }
 
        if (conn->ops->generate_signingkey) {
                retval = conn->ops->generate_signingkey(sess, conn);
                if (retval) {
                        ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
-                       return -EINVAL;
+                       retval = -EINVAL;
+                       goto out;
                }
        }
 
        if (!ksmbd_conn_lookup_dialect(conn)) {
                pr_err("fail to verify the dialect\n");
-               return -ENOENT;
+               retval = -ENOENT;
+               goto out;
        }
-       return 0;
+       retval = 0;
+out:
+       if (binding)
+               memzero_explicit(channel_key, sizeof(channel_key));
+       return retval;
 }
 #else
 static int krb5_authenticate(struct ksmbd_work *work,
@@ -2091,7 +2125,7 @@ out_err:
                rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
        else if (rc == -EFAULT)
                rsp->hdr.Status = STATUS_NETWORK_SESSION_EXPIRED;
-       else if (rc == -ENOMEM)
+       else if (rc == -ENOMEM || rc == -ENOSPC)
                rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
        else if (rc == -EOPNOTSUPP)
                rsp->hdr.Status = STATUS_NOT_SUPPORTED;
@@ -2130,8 +2164,16 @@ out_err:
                                sess->last_active = jiffies;
                                sess->state = SMB2_SESSION_EXPIRED;
                        }
-                       ksmbd_user_session_put(sess);
-                       work->sess = NULL;
+                       /*
+                        * Keep the binding session reference until the response is
+                        * signed and sent.  Error responses for a signed binding
+                        * request are signed with the existing session signing key.
+                        */
+                       if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) ||
+                           work->sess != sess) {
+                               ksmbd_user_session_put(sess);
+                               work->sess = NULL;
+                       }
                        if (try_delay) {
                                ksmbd_conn_set_need_reconnect(conn);
                                ssleep(5);
@@ -9483,7 +9525,6 @@ bool smb2_is_sign_req(struct ksmbd_work *work, unsigned int command)
 
        if ((rcv_hdr2->Flags & SMB2_FLAGS_SIGNED) &&
            command != SMB2_NEGOTIATE_HE &&
-           command != SMB2_SESSION_SETUP_HE &&
            command != SMB2_OPLOCK_BREAK_HE)
                return true;
 
@@ -9632,13 +9673,14 @@ void smb3_set_sign_rsp(struct ksmbd_work *work)
        struct channel *chann;
        char signature[SMB2_CMACAES_SIZE];
        struct kvec *iov;
+       u16 command = conn->ops->get_cmd_val(work);
        int n_vec = 1;
        char *signing_key;
 
        hdr = ksmbd_resp_buf_curr(work);
 
-       if (conn->binding == false &&
-           le16_to_cpu(hdr->Command) == SMB2_SESSION_SETUP_HE) {
+       if (command == SMB2_SESSION_SETUP_HE &&
+           (!conn->binding || hdr->Status != STATUS_SUCCESS)) {
                signing_key = work->sess->smb3signingkey;
        } else {
                chann = lookup_chann_list(work->sess, work->conn);