smb: client: fix UAF in async decryption
authorEnzo Matsumiya <ematsumiya@suse.de>
Thu, 26 Sep 2024 17:46:13 +0000 (14:46 -0300)
committerSteve French <stfrench@microsoft.com>
Thu, 26 Sep 2024 23:14:48 +0000 (18:14 -0500)
Doing an async decryption (large read) crashes with a
slab-use-after-free way down in the crypto API.

Reproducer:
    # mount.cifs -o ...,seal,esize=1 //srv/share /mnt
    # dd if=/mnt/largefile of=/dev/null
    ...
    [  194.196391] ==================================================================
    [  194.196844] BUG: KASAN: slab-use-after-free in gf128mul_4k_lle+0xc1/0x110
    [  194.197269] Read of size 8 at addr ffff888112bd0448 by task kworker/u77:2/899
    [  194.197707]
    [  194.197818] CPU: 12 UID: 0 PID: 899 Comm: kworker/u77:2 Not tainted 6.11.0-lku-00028-gfca3ca14a17a-dirty #43
    [  194.198400] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.2-3-gd478f380-prebuilt.qemu.org 04/01/2014
    [  194.199046] Workqueue: smb3decryptd smb2_decrypt_offload [cifs]
    [  194.200032] Call Trace:
    [  194.200191]  <TASK>
    [  194.200327]  dump_stack_lvl+0x4e/0x70
    [  194.200558]  ? gf128mul_4k_lle+0xc1/0x110
    [  194.200809]  print_report+0x174/0x505
    [  194.201040]  ? __pfx__raw_spin_lock_irqsave+0x10/0x10
    [  194.201352]  ? srso_return_thunk+0x5/0x5f
    [  194.201604]  ? __virt_addr_valid+0xdf/0x1c0
    [  194.201868]  ? gf128mul_4k_lle+0xc1/0x110
    [  194.202128]  kasan_report+0xc8/0x150
    [  194.202361]  ? gf128mul_4k_lle+0xc1/0x110
    [  194.202616]  gf128mul_4k_lle+0xc1/0x110
    [  194.202863]  ghash_update+0x184/0x210
    [  194.203103]  shash_ahash_update+0x184/0x2a0
    [  194.203377]  ? __pfx_shash_ahash_update+0x10/0x10
    [  194.203651]  ? srso_return_thunk+0x5/0x5f
    [  194.203877]  ? crypto_gcm_init_common+0x1ba/0x340
    [  194.204142]  gcm_hash_assoc_remain_continue+0x10a/0x140
    [  194.204434]  crypt_message+0xec1/0x10a0 [cifs]
    [  194.206489]  ? __pfx_crypt_message+0x10/0x10 [cifs]
    [  194.208507]  ? srso_return_thunk+0x5/0x5f
    [  194.209205]  ? srso_return_thunk+0x5/0x5f
    [  194.209925]  ? srso_return_thunk+0x5/0x5f
    [  194.210443]  ? srso_return_thunk+0x5/0x5f
    [  194.211037]  decrypt_raw_data+0x15f/0x250 [cifs]
    [  194.212906]  ? __pfx_decrypt_raw_data+0x10/0x10 [cifs]
    [  194.214670]  ? srso_return_thunk+0x5/0x5f
    [  194.215193]  smb2_decrypt_offload+0x12a/0x6c0 [cifs]

This is because TFM is being used in parallel.

Fix this by allocating a new AEAD TFM for async decryption, but keep
the existing one for synchronous READ cases (similar to what is done
in smb3_calc_signature()).

Also remove the calls to aead_request_set_callback() and
crypto_wait_req() since it's always going to be a synchronous operation.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/smb2ops.c
fs/smb/client/smb2pdu.c

index 1ee2dd4a1cae0a82c76bf00dbacfd22b1f96f614..177173072bfa9e9731de498cdd5d7e7f0ea7ae36 100644 (file)
@@ -4309,7 +4309,7 @@ smb2_get_enc_key(struct TCP_Server_Info *server, __u64 ses_id, int enc, u8 *key)
  */
 static int
 crypt_message(struct TCP_Server_Info *server, int num_rqst,
-             struct smb_rqst *rqst, int enc)
+             struct smb_rqst *rqst, int enc, struct crypto_aead *tfm)
 {
        struct smb2_transform_hdr *tr_hdr =
                (struct smb2_transform_hdr *)rqst[0].rq_iov[0].iov_base;
@@ -4320,8 +4320,6 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
        u8 key[SMB3_ENC_DEC_KEY_SIZE];
        struct aead_request *req;
        u8 *iv;
-       DECLARE_CRYPTO_WAIT(wait);
-       struct crypto_aead *tfm;
        unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
        void *creq;
        size_t sensitive_size;
@@ -4333,14 +4331,6 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
                return rc;
        }
 
-       rc = smb3_crypto_aead_allocate(server);
-       if (rc) {
-               cifs_server_dbg(VFS, "%s: crypto alloc failed\n", __func__);
-               return rc;
-       }
-
-       tfm = enc ? server->secmech.enc : server->secmech.dec;
-
        if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) ||
                (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM))
                rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE);
@@ -4380,11 +4370,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
        aead_request_set_crypt(req, sg, sg, crypt_len, iv);
        aead_request_set_ad(req, assoc_data_len);
 
-       aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
-                                 crypto_req_done, &wait);
-
-       rc = crypto_wait_req(enc ? crypto_aead_encrypt(req)
-                               : crypto_aead_decrypt(req), &wait);
+       rc = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);
 
        if (!rc && enc)
                memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);
@@ -4526,7 +4512,7 @@ smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst,
        /* fill the 1st iov with a transform header */
        fill_transform_hdr(tr_hdr, orig_len, old_rq, server->cipher_type);
 
-       rc = crypt_message(server, num_rqst, new_rq, 1);
+       rc = crypt_message(server, num_rqst, new_rq, 1, server->secmech.enc);
        cifs_dbg(FYI, "Encrypt message returned %d\n", rc);
        if (rc)
                goto err_free;
@@ -4551,8 +4537,9 @@ decrypt_raw_data(struct TCP_Server_Info *server, char *buf,
                 unsigned int buf_data_size, struct iov_iter *iter,
                 bool is_offloaded)
 {
-       struct kvec iov[2];
+       struct crypto_aead *tfm;
        struct smb_rqst rqst = {NULL};
+       struct kvec iov[2];
        size_t iter_size = 0;
        int rc;
 
@@ -4568,9 +4555,31 @@ decrypt_raw_data(struct TCP_Server_Info *server, char *buf,
                iter_size = iov_iter_count(iter);
        }
 
-       rc = crypt_message(server, 1, &rqst, 0);
+       if (is_offloaded) {
+               if ((server->cipher_type == SMB2_ENCRYPTION_AES128_GCM) ||
+                   (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM))
+                       tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
+               else
+                       tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
+               if (IS_ERR(tfm)) {
+                       rc = PTR_ERR(tfm);
+                       cifs_server_dbg(VFS, "%s: Failed alloc decrypt TFM, rc=%d\n", __func__, rc);
+
+                       return rc;
+               }
+       } else {
+               if (unlikely(!server->secmech.dec))
+                       return -EIO;
+
+               tfm = server->secmech.dec;
+       }
+
+       rc = crypt_message(server, 1, &rqst, 0, tfm);
        cifs_dbg(FYI, "Decrypt message returned %d\n", rc);
 
+       if (is_offloaded)
+               crypto_free_aead(tfm);
+
        if (rc)
                return rc;
 
index bb225758448a6ae8daf79f819ed92f8d212ca4d8..cb423bfe7377a27fe68bbf4cc19f5300cd05b659 100644 (file)
@@ -1266,6 +1266,12 @@ SMB2_negotiate(const unsigned int xid,
                else
                        cifs_server_dbg(VFS, "Missing expected negotiate contexts\n");
        }
+
+       if (server->cipher_type && !rc) {
+               rc = smb3_crypto_aead_allocate(server);
+               if (rc)
+                       cifs_server_dbg(VFS, "%s: crypto alloc failed, rc=%d\n", __func__, rc);
+       }
 neg_exit:
        free_rsp_buf(resp_buftype, rsp);
        return rc;