]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - fs/ksmbd/smb2pdu.c
vfs: fix copy_file_range() regression in cross-fs copies
[mirror_ubuntu-jammy-kernel.git] / fs / ksmbd / smb2pdu.c
index 005aa93a49d692d2ffe173970b9ba4ff8b728c7f..876afde0ea6609bd870f61eb763e0741f54e9a0f 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/statfs.h>
 #include <linux/ethtool.h>
 #include <linux/falloc.h>
+#include <linux/mount.h>
 
 #include "glob.h"
 #include "smb2pdu.h"
@@ -292,22 +293,6 @@ int init_smb2_neg_rsp(struct ksmbd_work *work)
        return 0;
 }
 
-static int smb2_consume_credit_charge(struct ksmbd_work *work,
-                                     unsigned short credit_charge)
-{
-       struct ksmbd_conn *conn = work->conn;
-       unsigned int rsp_credits = 1;
-
-       if (!conn->total_credits)
-               return 0;
-
-       if (credit_charge > 0)
-               rsp_credits = credit_charge;
-
-       conn->total_credits -= rsp_credits;
-       return rsp_credits;
-}
-
 /**
  * smb2_set_rsp_credits() - set number of credits in response buffer
  * @work:      smb work containing smb response buffer
@@ -317,49 +302,48 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
        struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
        struct smb2_hdr *hdr = ksmbd_resp_buf_next(work);
        struct ksmbd_conn *conn = work->conn;
-       unsigned short credits_requested = le16_to_cpu(req_hdr->CreditRequest);
-       unsigned short credit_charge = 1, credits_granted = 0;
-       unsigned short aux_max, aux_credits, min_credits;
-       int rsp_credit_charge;
+       unsigned short credits_requested, aux_max;
+       unsigned short credit_charge, credits_granted = 0;
 
-       if (hdr->Command == SMB2_CANCEL)
-               goto out;
+       if (work->send_no_response)
+               return 0;
 
-       /* get default minimum credits by shifting maximum credits by 4 */
-       min_credits = conn->max_credits >> 4;
+       hdr->CreditCharge = req_hdr->CreditCharge;
 
-       if (conn->total_credits >= conn->max_credits) {
+       if (conn->total_credits > conn->vals->max_credits) {
+               hdr->CreditRequest = 0;
                pr_err("Total credits overflow: %d\n", conn->total_credits);
-               conn->total_credits = min_credits;
+               return -EINVAL;
        }
 
-       rsp_credit_charge =
-               smb2_consume_credit_charge(work, le16_to_cpu(req_hdr->CreditCharge));
-       if (rsp_credit_charge < 0)
+       credit_charge = max_t(unsigned short,
+                             le16_to_cpu(req_hdr->CreditCharge), 1);
+       if (credit_charge > conn->total_credits) {
+               ksmbd_debug(SMB, "Insufficient credits granted, given: %u, granted: %u\n",
+                           credit_charge, conn->total_credits);
                return -EINVAL;
+       }
 
-       hdr->CreditCharge = cpu_to_le16(rsp_credit_charge);
+       conn->total_credits -= credit_charge;
+       conn->outstanding_credits -= credit_charge;
+       credits_requested = max_t(unsigned short,
+                                 le16_to_cpu(req_hdr->CreditRequest), 1);
 
-       if (credits_requested > 0) {
-               aux_credits = credits_requested - 1;
-               aux_max = 32;
-               if (hdr->Command == SMB2_NEGOTIATE)
-                       aux_max = 0;
-               aux_credits = (aux_credits < aux_max) ? aux_credits : aux_max;
-               credits_granted = aux_credits + credit_charge;
+       /* according to smb2.credits smbtorture, Windows server
+        * 2016 or later grant up to 8192 credits at once.
+        *
+        * TODO: Need to adjuct CreditRequest value according to
+        * current cpu load
+        */
+       if (hdr->Command == SMB2_NEGOTIATE)
+               aux_max = 1;
+       else
+               aux_max = conn->vals->max_credits - credit_charge;
+       credits_granted = min_t(unsigned short, credits_requested, aux_max);
 
-               /* if credits granted per client is getting bigger than default
-                * minimum credits then we should wrap it up within the limits.
-                */
-               if ((conn->total_credits + credits_granted) > min_credits)
-                       credits_granted = min_credits - conn->total_credits;
-               /*
-                * TODO: Need to adjuct CreditRequest value according to
-                * current cpu load
-                */
-       } else if (conn->total_credits == 0) {
-               credits_granted = 1;
-       }
+       if (conn->vals->max_credits - conn->total_credits < credits_granted)
+               credits_granted = conn->vals->max_credits -
+                       conn->total_credits;
 
        conn->total_credits += credits_granted;
        work->credits_granted += credits_granted;
@@ -368,7 +352,6 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
                /* Update CreditRequest in last request */
                hdr->CreditRequest = cpu_to_le16(work->credits_granted);
        }
-out:
        ksmbd_debug(SMB,
                    "credits: requested[%d] granted[%d] total_granted[%d]\n",
                    credits_requested, credits_granted,
@@ -472,6 +455,12 @@ bool is_chained_smb2_message(struct ksmbd_work *work)
                        return false;
                }
 
+               if ((u64)get_rfc1002_len(work->response_buf) + MAX_CIFS_SMALL_BUFFER_SIZE >
+                   work->response_sz) {
+                       pr_err("next response offset exceeds response buffer size\n");
+                       return false;
+               }
+
                ksmbd_debug(SMB, "got SMB2 chained command\n");
                init_chained_smb2_rsp(work);
                return true;
@@ -541,7 +530,7 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work)
 {
        struct smb2_hdr *hdr = work->request_buf;
        size_t small_sz = MAX_CIFS_SMALL_BUFFER_SIZE;
-       size_t large_sz = work->conn->vals->max_trans_size + MAX_SMB2_HDR_SIZE;
+       size_t large_sz = small_sz + work->conn->vals->max_trans_size;
        size_t sz = small_sz;
        int cmd = le16_to_cpu(hdr->Command);
 
@@ -934,6 +923,25 @@ static void decode_encrypt_ctxt(struct ksmbd_conn *conn,
        }
 }
 
+/**
+ * smb3_encryption_negotiated() - checks if server and client agreed on enabling encryption
+ * @conn:      smb connection
+ *
+ * Return:     true if connection should be encrypted, else false
+ */
+static bool smb3_encryption_negotiated(struct ksmbd_conn *conn)
+{
+       if (!conn->ops->generate_encryptionkey)
+               return false;
+
+       /*
+        * SMB 3.0 and 3.0.2 dialects use the SMB2_GLOBAL_CAP_ENCRYPTION flag.
+        * SMB 3.1.1 uses the cipher_type field.
+        */
+       return (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) ||
+           conn->cipher_type;
+}
+
 static void decode_compress_ctxt(struct ksmbd_conn *conn,
                                 struct smb2_compression_ctx *pneg_ctxt)
 {
@@ -1274,19 +1282,13 @@ static int generate_preauth_hash(struct ksmbd_work *work)
        return 0;
 }
 
-static int decode_negotiation_token(struct ksmbd_work *work,
-                                   struct negotiate_message *negblob)
+static int decode_negotiation_token(struct ksmbd_conn *conn,
+                                   struct negotiate_message *negblob,
+                                   size_t sz)
 {
-       struct ksmbd_conn *conn = work->conn;
-       struct smb2_sess_setup_req *req;
-       int sz;
-
        if (!conn->use_spnego)
                return -EINVAL;
 
-       req = work->request_buf;
-       sz = le16_to_cpu(req->SecurityBufferLength);
-
        if (ksmbd_decode_negTokenInit((char *)negblob, sz, conn)) {
                if (ksmbd_decode_negTokenTarg((char *)negblob, sz, conn)) {
                        conn->auth_mechs |= KSMBD_AUTH_NTLMSSP;
@@ -1298,9 +1300,9 @@ static int decode_negotiation_token(struct ksmbd_work *work,
 }
 
 static int ntlm_negotiate(struct ksmbd_work *work,
-                         struct negotiate_message *negblob)
+                         struct negotiate_message *negblob,
+                         size_t negblob_len)
 {
-       struct smb2_sess_setup_req *req = work->request_buf;
        struct smb2_sess_setup_rsp *rsp = work->response_buf;
        struct challenge_message *chgblob;
        unsigned char *spnego_blob = NULL;
@@ -1309,8 +1311,7 @@ static int ntlm_negotiate(struct ksmbd_work *work,
        int sz, rc;
 
        ksmbd_debug(SMB, "negotiate phase\n");
-       sz = le16_to_cpu(req->SecurityBufferLength);
-       rc = ksmbd_decode_ntlmssp_neg_blob(negblob, sz, work->sess);
+       rc = ksmbd_decode_ntlmssp_neg_blob(negblob, negblob_len, work->sess);
        if (rc)
                return rc;
 
@@ -1378,12 +1379,23 @@ static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
        struct authenticate_message *authblob;
        struct ksmbd_user *user;
        char *name;
-       int sz;
+       unsigned int auth_msg_len, name_off, name_len, secbuf_len;
 
+       secbuf_len = le16_to_cpu(req->SecurityBufferLength);
+       if (secbuf_len < sizeof(struct authenticate_message)) {
+               ksmbd_debug(SMB, "blob len %d too small\n", secbuf_len);
+               return NULL;
+       }
        authblob = user_authblob(conn, req);
-       sz = le32_to_cpu(authblob->UserName.BufferOffset);
-       name = smb_strndup_from_utf16((const char *)authblob + sz,
-                                     le16_to_cpu(authblob->UserName.Length),
+       name_off = le32_to_cpu(authblob->UserName.BufferOffset);
+       name_len = le16_to_cpu(authblob->UserName.Length);
+       auth_msg_len = le16_to_cpu(req->SecurityBufferOffset) + secbuf_len;
+
+       if (auth_msg_len < (u64)name_off + name_len)
+               return NULL;
+
+       name = smb_strndup_from_utf16((const char *)authblob + name_off,
+                                     name_len,
                                      true,
                                      conn->local_nls);
        if (IS_ERR(name)) {
@@ -1451,11 +1463,6 @@ static int ntlm_authenticate(struct ksmbd_work *work)
 
        sess->user = user;
        if (user_guest(sess->user)) {
-               if (conn->sign) {
-                       ksmbd_debug(SMB, "Guest login not allowed when signing enabled\n");
-                       return -EPERM;
-               }
-
                rsp->SessionFlags = SMB2_SESSION_FLAG_IS_GUEST_LE;
        } else {
                struct authenticate_message *authblob;
@@ -1468,39 +1475,39 @@ static int ntlm_authenticate(struct ksmbd_work *work)
                        ksmbd_debug(SMB, "authentication failed\n");
                        return -EPERM;
                }
+       }
 
-               /*
-                * If session state is SMB2_SESSION_VALID, We can assume
-                * that it is reauthentication. And the user/password
-                * has been verified, so return it here.
-                */
-               if (sess->state == SMB2_SESSION_VALID) {
-                       if (conn->binding)
-                               goto binding_session;
-                       return 0;
-               }
+       /*
+        * If session state is SMB2_SESSION_VALID, We can assume
+        * that it is reauthentication. And the user/password
+        * has been verified, so return it here.
+        */
+       if (sess->state == SMB2_SESSION_VALID) {
+               if (conn->binding)
+                       goto binding_session;
+               return 0;
+       }
 
-               if ((conn->sign || server_conf.enforced_signing) ||
-                   (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
-                       sess->sign = true;
+       if ((rsp->SessionFlags != SMB2_SESSION_FLAG_IS_GUEST_LE &&
+            (conn->sign || server_conf.enforced_signing)) ||
+           (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
+               sess->sign = true;
 
-               if (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION &&
-                   conn->ops->generate_encryptionkey &&
-                   !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
-                       rc = conn->ops->generate_encryptionkey(sess);
-                       if (rc) {
-                               ksmbd_debug(SMB,
-                                           "SMB3 encryption key generation failed\n");
-                               return -EINVAL;
-                       }
-                       sess->enc = true;
-                       rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
-                       /*
-                        * signing is disable if encryption is enable
-                        * on this session
-                        */
-                       sess->sign = false;
+       if (smb3_encryption_negotiated(conn) &&
+                       !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
+               rc = conn->ops->generate_encryptionkey(sess);
+               if (rc) {
+                       ksmbd_debug(SMB,
+                                       "SMB3 encryption key generation failed\n");
+                       return -EINVAL;
                }
+               sess->enc = true;
+               rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
+               /*
+                * signing is disable if encryption is enable
+                * on this session
+                */
+               sess->sign = false;
        }
 
 binding_session:
@@ -1575,8 +1582,7 @@ static int krb5_authenticate(struct ksmbd_work *work)
            (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
                sess->sign = true;
 
-       if ((conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) &&
-           conn->ops->generate_encryptionkey) {
+       if (smb3_encryption_negotiated(conn)) {
                retval = conn->ops->generate_encryptionkey(sess);
                if (retval) {
                        ksmbd_debug(SMB,
@@ -1629,6 +1635,7 @@ int smb2_sess_setup(struct ksmbd_work *work)
        struct smb2_sess_setup_rsp *rsp = work->response_buf;
        struct ksmbd_session *sess;
        struct negotiate_message *negblob;
+       unsigned int negblob_len, negblob_off;
        int rc = 0;
 
        ksmbd_debug(SMB, "Received request for session setup\n");
@@ -1709,10 +1716,18 @@ int smb2_sess_setup(struct ksmbd_work *work)
        if (sess->state == SMB2_SESSION_EXPIRED)
                sess->state = SMB2_SESSION_IN_PROGRESS;
 
+       negblob_off = le16_to_cpu(req->SecurityBufferOffset);
+       negblob_len = le16_to_cpu(req->SecurityBufferLength);
+       if (negblob_off < (offsetof(struct smb2_sess_setup_req, Buffer) - 4) ||
+           negblob_len < offsetof(struct negotiate_message, NegotiateFlags)) {
+               rc = -EINVAL;
+               goto out_err;
+       }
+
        negblob = (struct negotiate_message *)((char *)&req->hdr.ProtocolId +
-                       le16_to_cpu(req->SecurityBufferOffset));
+                       negblob_off);
 
-       if (decode_negotiation_token(work, negblob) == 0) {
+       if (decode_negotiation_token(conn, negblob, negblob_len) == 0) {
                if (conn->mechToken)
                        negblob = (struct negotiate_message *)conn->mechToken;
        }
@@ -1736,7 +1751,7 @@ int smb2_sess_setup(struct ksmbd_work *work)
                        sess->Preauth_HashValue = NULL;
                } else if (conn->preferred_auth_mech == KSMBD_AUTH_NTLMSSP) {
                        if (negblob->MessageType == NtLmNegotiate) {
-                               rc = ntlm_negotiate(work, negblob);
+                               rc = ntlm_negotiate(work, negblob, negblob_len);
                                if (rc)
                                        goto out_err;
                                rsp->hdr.Status =
@@ -1796,9 +1811,30 @@ out_err:
                conn->mechToken = NULL;
        }
 
-       if (rc < 0 && sess) {
-               ksmbd_session_destroy(sess);
-               work->sess = NULL;
+       if (rc < 0) {
+               /*
+                * SecurityBufferOffset should be set to zero
+                * in session setup error response.
+                */
+               rsp->SecurityBufferOffset = 0;
+
+               if (sess) {
+                       bool try_delay = false;
+
+                       /*
+                        * To avoid dictionary attacks (repeated session setups rapidly sent) to
+                        * connect to server, ksmbd make a delay of a 5 seconds on session setup
+                        * failure to make it harder to send enough random connection requests
+                        * to break into a server.
+                        */
+                       if (sess->user && sess->user->flags & KSMBD_USER_FLAG_DELAY_SESSION)
+                               try_delay = true;
+
+                       ksmbd_session_destroy(sess);
+                       work->sess = NULL;
+                       if (try_delay)
+                               ssleep(5);
+               }
        }
 
        return rc;
@@ -2655,7 +2691,7 @@ int smb2_open(struct ksmbd_work *work)
                                        (struct create_posix *)context;
                                if (le16_to_cpu(context->DataOffset) +
                                    le32_to_cpu(context->DataLength) <
-                                   sizeof(struct create_posix)) {
+                                   sizeof(struct create_posix) - 4) {
                                        rc = -EINVAL;
                                        goto err_out1;
                                }
@@ -2947,6 +2983,10 @@ int smb2_open(struct ksmbd_work *work)
                                                            &pntsd_size, &fattr);
                                        posix_acl_release(fattr.cf_acls);
                                        posix_acl_release(fattr.cf_dacls);
+                                       if (rc) {
+                                               kfree(pntsd);
+                                               goto err_out;
+                                       }
 
                                        rc = ksmbd_vfs_set_sd_xattr(conn,
                                                                    user_ns,
@@ -3383,9 +3423,9 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
                goto free_conv_name;
        }
 
-       struct_sz = readdir_info_level_struct_sz(info_level);
-       next_entry_offset = ALIGN(struct_sz - 1 + conv_len,
-                                 KSMBD_DIR_INFO_ALIGNMENT);
+       struct_sz = readdir_info_level_struct_sz(info_level) - 1 + conv_len;
+       next_entry_offset = ALIGN(struct_sz, KSMBD_DIR_INFO_ALIGNMENT);
+       d_info->last_entry_off_align = next_entry_offset - struct_sz;
 
        if (next_entry_offset > d_info->out_buf_len) {
                d_info->out_buf_len = 0;
@@ -3779,6 +3819,24 @@ static int verify_info_level(int info_level)
        return 0;
 }
 
+static int smb2_calc_max_out_buf_len(struct ksmbd_work *work,
+                                    unsigned short hdr2_len,
+                                    unsigned int out_buf_len)
+{
+       int free_len;
+
+       if (out_buf_len > work->conn->vals->max_trans_size)
+               return -EINVAL;
+
+       free_len = (int)(work->response_sz -
+                        (get_rfc1002_len(work->response_buf) + 4)) -
+               hdr2_len;
+       if (free_len < 0)
+               return -EINVAL;
+
+       return min_t(int, out_buf_len, free_len);
+}
+
 int smb2_query_dir(struct ksmbd_work *work)
 {
        struct ksmbd_conn *conn = work->conn;
@@ -3855,9 +3913,13 @@ int smb2_query_dir(struct ksmbd_work *work)
        memset(&d_info, 0, sizeof(struct ksmbd_dir_info));
        d_info.wptr = (char *)rsp->Buffer;
        d_info.rptr = (char *)rsp->Buffer;
-       d_info.out_buf_len = (work->response_sz - (get_rfc1002_len(rsp_org) + 4));
-       d_info.out_buf_len = min_t(int, d_info.out_buf_len, le32_to_cpu(req->OutputBufferLength)) -
-               sizeof(struct smb2_query_directory_rsp);
+       d_info.out_buf_len =
+               smb2_calc_max_out_buf_len(work, 8,
+                                         le32_to_cpu(req->OutputBufferLength));
+       if (d_info.out_buf_len < 0) {
+               rc = -EINVAL;
+               goto err_out;
+       }
        d_info.flags = srch_flag;
 
        /*
@@ -3915,6 +3977,7 @@ int smb2_query_dir(struct ksmbd_work *work)
                ((struct file_directory_info *)
                ((char *)rsp->Buffer + d_info.last_entry_offset))
                ->NextEntryOffset = 0;
+               d_info.data_count -= d_info.last_entry_off_align;
 
                rsp->StructureSize = cpu_to_le16(9);
                rsp->OutputBufferOffset = cpu_to_le16(72);
@@ -4091,12 +4154,11 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
                                    le32_to_cpu(req->Flags));
        }
 
-       buf_free_len = work->response_sz -
-                       (get_rfc1002_len(rsp_org) + 4) -
-                       sizeof(struct smb2_query_info_rsp);
-
-       if (le32_to_cpu(req->OutputBufferLength) < buf_free_len)
-               buf_free_len = le32_to_cpu(req->OutputBufferLength);
+       buf_free_len =
+               smb2_calc_max_out_buf_len(work, 8,
+                                         le32_to_cpu(req->OutputBufferLength));
+       if (buf_free_len < 0)
+               return -EINVAL;
 
        rc = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
        if (rc < 0) {
@@ -4407,11 +4469,19 @@ static void get_file_stream_info(struct ksmbd_work *work,
        struct path *path = &fp->filp->f_path;
        ssize_t xattr_list_len;
        int nbytes = 0, streamlen, stream_name_len, next, idx = 0;
+       int buf_free_len;
+       struct smb2_query_info_req *req = ksmbd_req_buf_next(work);
 
        generic_fillattr(file_mnt_user_ns(fp->filp), file_inode(fp->filp),
                         &stat);
        file_info = (struct smb2_file_stream_info *)rsp->Buffer;
 
+       buf_free_len =
+               smb2_calc_max_out_buf_len(work, 8,
+                                         le32_to_cpu(req->OutputBufferLength));
+       if (buf_free_len < 0)
+               goto out;
+
        xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
        if (xattr_list_len < 0) {
                goto out;
@@ -4444,6 +4514,12 @@ static void get_file_stream_info(struct ksmbd_work *work,
                streamlen = snprintf(stream_buf, streamlen + 1,
                                     ":%s", &stream_name[XATTR_NAME_STREAM_LEN]);
 
+               next = sizeof(struct smb2_file_stream_info) + streamlen * 2;
+               if (next > buf_free_len) {
+                       kfree(stream_buf);
+                       break;
+               }
+
                file_info = (struct smb2_file_stream_info *)&rsp->Buffer[nbytes];
                streamlen  = smbConvertToUTF16((__le16 *)file_info->StreamName,
                                               stream_buf, streamlen,
@@ -4454,26 +4530,27 @@ static void get_file_stream_info(struct ksmbd_work *work,
                file_info->StreamSize = cpu_to_le64(stream_name_len);
                file_info->StreamAllocationSize = cpu_to_le64(stream_name_len);
 
-               next = sizeof(struct smb2_file_stream_info) + streamlen;
                nbytes += next;
+               buf_free_len -= next;
                file_info->NextEntryOffset = cpu_to_le32(next);
        }
 
-       if (!S_ISDIR(stat.mode)) {
+out:
+       if (!S_ISDIR(stat.mode) &&
+           buf_free_len >= sizeof(struct smb2_file_stream_info) + 7 * 2) {
                file_info = (struct smb2_file_stream_info *)
                        &rsp->Buffer[nbytes];
                streamlen = smbConvertToUTF16((__le16 *)file_info->StreamName,
                                              "::$DATA", 7, conn->local_nls, 0);
                streamlen *= 2;
                file_info->StreamNameLength = cpu_to_le32(streamlen);
-               file_info->StreamSize = 0;
-               file_info->StreamAllocationSize = 0;
+               file_info->StreamSize = cpu_to_le64(stat.size);
+               file_info->StreamAllocationSize = cpu_to_le64(stat.blocks << 9);
                nbytes += sizeof(struct smb2_file_stream_info) + streamlen;
        }
 
        /* last entry offset should be 0 */
        file_info->NextEntryOffset = 0;
-out:
        kvfree(xattr_list);
 
        rsp->OutputBufferLength = cpu_to_le32(nbytes);
@@ -4842,11 +4919,18 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
        {
                struct filesystem_vol_info *info;
                size_t sz;
+               unsigned int serial_crc = 0;
 
                info = (struct filesystem_vol_info *)(rsp->Buffer);
                info->VolumeCreationTime = 0;
+               serial_crc = crc32_le(serial_crc, share->name,
+                                     strlen(share->name));
+               serial_crc = crc32_le(serial_crc, share->path,
+                                     strlen(share->path));
+               serial_crc = crc32_le(serial_crc, ksmbd_netbios_name(),
+                                     strlen(ksmbd_netbios_name()));
                /* Taking dummy value of serial number*/
-               info->SerialNumber = cpu_to_le32(0xbc3ac512);
+               info->SerialNumber = cpu_to_le32(serial_crc);
                len = smbConvertToUTF16((__le16 *)info->VolumeLabel,
                                        share->name, PATH_MAX,
                                        conn->local_nls, 0);
@@ -4914,15 +4998,17 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
        case FS_SECTOR_SIZE_INFORMATION:
        {
                struct smb3_fs_ss_info *info;
+               unsigned int sector_size =
+                       min_t(unsigned int, path.mnt->mnt_sb->s_blocksize, 4096);
 
                info = (struct smb3_fs_ss_info *)(rsp->Buffer);
 
-               info->LogicalBytesPerSector = cpu_to_le32(stfs.f_bsize);
+               info->LogicalBytesPerSector = cpu_to_le32(sector_size);
                info->PhysicalBytesPerSectorForAtomicity =
-                               cpu_to_le32(stfs.f_bsize);
-               info->PhysicalBytesPerSectorForPerf = cpu_to_le32(stfs.f_bsize);
+                               cpu_to_le32(sector_size);
+               info->PhysicalBytesPerSectorForPerf = cpu_to_le32(sector_size);
                info->FSEffPhysicalBytesPerSectorForAtomicity =
-                               cpu_to_le32(stfs.f_bsize);
+                               cpu_to_le32(sector_size);
                info->Flags = cpu_to_le32(SSINFO_FLAGS_ALIGNED_DEVICE |
                                    SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE);
                info->ByteOffsetForSectorAlignment = 0;
@@ -5004,7 +5090,7 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
        if (addition_info & ~(OWNER_SECINFO | GROUP_SECINFO | DACL_SECINFO |
                              PROTECTED_DACL_SECINFO |
                              UNPROTECTED_DACL_SECINFO)) {
-               pr_err("Unsupported addition info: 0x%x)\n",
+               ksmbd_debug(SMB, "Unsupported addition info: 0x%x)\n",
                       addition_info);
 
                pntsd->revision = cpu_to_le16(1);
@@ -5685,8 +5771,10 @@ static int set_rename_info(struct ksmbd_work *work, struct ksmbd_file *fp,
        if (parent_fp) {
                if (parent_fp->daccess & FILE_DELETE_LE) {
                        pr_err("parent dir is opened with delete access\n");
+                       ksmbd_fd_put(work, parent_fp);
                        return -ESHARE;
                }
+               ksmbd_fd_put(work, parent_fp);
        }
 next:
        return smb2_rename(work, fp, user_ns, rename_info,
@@ -6220,8 +6308,7 @@ static noinline int smb2_write_pipe(struct ksmbd_work *work)
            (offsetof(struct smb2_write_req, Buffer) - 4)) {
                data_buf = (char *)&req->Buffer[0];
        } else {
-               if ((le16_to_cpu(req->DataOffset) > get_rfc1002_len(req)) ||
-                   (le16_to_cpu(req->DataOffset) + length > get_rfc1002_len(req))) {
+               if ((u64)le16_to_cpu(req->DataOffset) + length > get_rfc1002_len(req)) {
                        pr_err("invalid write data offset %u, smb_len %u\n",
                               le16_to_cpu(req->DataOffset),
                               get_rfc1002_len(req));
@@ -6379,8 +6466,7 @@ int smb2_write(struct ksmbd_work *work)
                    (offsetof(struct smb2_write_req, Buffer) - 4)) {
                        data_buf = (char *)&req->Buffer[0];
                } else {
-                       if ((le16_to_cpu(req->DataOffset) > get_rfc1002_len(req)) ||
-                           (le16_to_cpu(req->DataOffset) + length > get_rfc1002_len(req))) {
+                       if ((u64)le16_to_cpu(req->DataOffset) + length > get_rfc1002_len(req)) {
                                pr_err("invalid write data offset %u, smb_len %u\n",
                                       le16_to_cpu(req->DataOffset),
                                       get_rfc1002_len(req));
@@ -7023,24 +7109,26 @@ out2:
        return err;
 }
 
-static int fsctl_copychunk(struct ksmbd_work *work, struct smb2_ioctl_req *req,
+static int fsctl_copychunk(struct ksmbd_work *work,
+                          struct copychunk_ioctl_req *ci_req,
+                          unsigned int cnt_code,
+                          unsigned int input_count,
+                          unsigned long long volatile_id,
+                          unsigned long long persistent_id,
                           struct smb2_ioctl_rsp *rsp)
 {
-       struct copychunk_ioctl_req *ci_req;
        struct copychunk_ioctl_rsp *ci_rsp;
        struct ksmbd_file *src_fp = NULL, *dst_fp = NULL;
        struct srv_copychunk *chunks;
        unsigned int i, chunk_count, chunk_count_written = 0;
        unsigned int chunk_size_written = 0;
        loff_t total_size_written = 0;
-       int ret, cnt_code;
+       int ret = 0;
 
-       cnt_code = le32_to_cpu(req->CntCode);
-       ci_req = (struct copychunk_ioctl_req *)&req->Buffer[0];
        ci_rsp = (struct copychunk_ioctl_rsp *)&rsp->Buffer[0];
 
-       rsp->VolatileFileId = req->VolatileFileId;
-       rsp->PersistentFileId = req->PersistentFileId;
+       rsp->VolatileFileId = cpu_to_le64(volatile_id);
+       rsp->PersistentFileId = cpu_to_le64(persistent_id);
        ci_rsp->ChunksWritten =
                cpu_to_le32(ksmbd_server_side_copy_max_chunk_count());
        ci_rsp->ChunkBytesWritten =
@@ -7050,12 +7138,13 @@ static int fsctl_copychunk(struct ksmbd_work *work, struct smb2_ioctl_req *req,
 
        chunks = (struct srv_copychunk *)&ci_req->Chunks[0];
        chunk_count = le32_to_cpu(ci_req->ChunkCount);
+       if (chunk_count == 0)
+               goto out;
        total_size_written = 0;
 
        /* verify the SRV_COPYCHUNK_COPY packet */
        if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
-           le32_to_cpu(req->InputCount) <
-            offsetof(struct copychunk_ioctl_req, Chunks) +
+           input_count < offsetof(struct copychunk_ioctl_req, Chunks) +
             chunk_count * sizeof(struct srv_copychunk)) {
                rsp->hdr.Status = STATUS_INVALID_PARAMETER;
                return -EINVAL;
@@ -7076,9 +7165,7 @@ static int fsctl_copychunk(struct ksmbd_work *work, struct smb2_ioctl_req *req,
 
        src_fp = ksmbd_lookup_foreign_fd(work,
                                         le64_to_cpu(ci_req->ResumeKey[0]));
-       dst_fp = ksmbd_lookup_fd_slow(work,
-                                     le64_to_cpu(req->VolatileFileId),
-                                     le64_to_cpu(req->PersistentFileId));
+       dst_fp = ksmbd_lookup_fd_slow(work, volatile_id, persistent_id);
        ret = -EINVAL;
        if (!src_fp ||
            src_fp->persistent_id != le64_to_cpu(ci_req->ResumeKey[1])) {
@@ -7153,8 +7240,8 @@ static __be32 idev_ipv4_address(struct in_device *idev)
 }
 
 static int fsctl_query_iface_info_ioctl(struct ksmbd_conn *conn,
-                                       struct smb2_ioctl_req *req,
-                                       struct smb2_ioctl_rsp *rsp)
+                                       struct smb2_ioctl_rsp *rsp,
+                                       unsigned int out_buf_len)
 {
        struct network_interface_info_ioctl_rsp *nii_rsp = NULL;
        int nbytes = 0;
@@ -7166,6 +7253,12 @@ static int fsctl_query_iface_info_ioctl(struct ksmbd_conn *conn,
 
        rtnl_lock();
        for_each_netdev(&init_net, netdev) {
+               if (out_buf_len <
+                   nbytes + sizeof(struct network_interface_info_ioctl_rsp)) {
+                       rtnl_unlock();
+                       return -ENOSPC;
+               }
+
                if (netdev->type == ARPHRD_LOOPBACK)
                        continue;
 
@@ -7245,11 +7338,6 @@ static int fsctl_query_iface_info_ioctl(struct ksmbd_conn *conn,
        if (nii_rsp)
                nii_rsp->Next = 0;
 
-       if (!nbytes) {
-               rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
-               return -EINVAL;
-       }
-
        rsp->PersistentFileId = cpu_to_le64(SMB2_NO_FID);
        rsp->VolatileFileId = cpu_to_le64(SMB2_NO_FID);
        return nbytes;
@@ -7257,11 +7345,16 @@ static int fsctl_query_iface_info_ioctl(struct ksmbd_conn *conn,
 
 static int fsctl_validate_negotiate_info(struct ksmbd_conn *conn,
                                         struct validate_negotiate_info_req *neg_req,
-                                        struct validate_negotiate_info_rsp *neg_rsp)
+                                        struct validate_negotiate_info_rsp *neg_rsp,
+                                        unsigned int in_buf_len)
 {
        int ret = 0;
        int dialect;
 
+       if (in_buf_len < offsetof(struct validate_negotiate_info_req, Dialects) +
+                       le16_to_cpu(neg_req->DialectCount) * sizeof(__le16))
+               return -EINVAL;
+
        dialect = ksmbd_lookup_dialect_by_id(neg_req->Dialects,
                                             neg_req->DialectCount);
        if (dialect == BAD_PROT_ID || dialect != conn->dialect) {
@@ -7295,7 +7388,7 @@ err_out:
 static int fsctl_query_allocated_ranges(struct ksmbd_work *work, u64 id,
                                        struct file_allocated_range_buffer *qar_req,
                                        struct file_allocated_range_buffer *qar_rsp,
-                                       int in_count, int *out_count)
+                                       unsigned int in_count, unsigned int *out_count)
 {
        struct ksmbd_file *fp;
        loff_t start, length;
@@ -7322,7 +7415,8 @@ static int fsctl_query_allocated_ranges(struct ksmbd_work *work, u64 id,
 }
 
 static int fsctl_pipe_transceive(struct ksmbd_work *work, u64 id,
-                                int out_buf_len, struct smb2_ioctl_req *req,
+                                unsigned int out_buf_len,
+                                struct smb2_ioctl_req *req,
                                 struct smb2_ioctl_rsp *rsp)
 {
        struct ksmbd_rpc_command *rpc_resp;
@@ -7436,8 +7530,7 @@ int smb2_ioctl(struct ksmbd_work *work)
 {
        struct smb2_ioctl_req *req;
        struct smb2_ioctl_rsp *rsp, *rsp_org;
-       int cnt_code, nbytes = 0;
-       int out_buf_len;
+       unsigned int cnt_code, nbytes = 0, out_buf_len, in_buf_len;
        u64 id = KSMBD_NO_FID;
        struct ksmbd_conn *conn = work->conn;
        int ret = 0;
@@ -7465,8 +7558,14 @@ int smb2_ioctl(struct ksmbd_work *work)
        }
 
        cnt_code = le32_to_cpu(req->CntCode);
-       out_buf_len = le32_to_cpu(req->MaxOutputResponse);
-       out_buf_len = min(KSMBD_IPC_MAX_PAYLOAD, out_buf_len);
+       ret = smb2_calc_max_out_buf_len(work, 48,
+                                       le32_to_cpu(req->MaxOutputResponse));
+       if (ret < 0) {
+               rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+               goto out;
+       }
+       out_buf_len = (unsigned int)ret;
+       in_buf_len = le32_to_cpu(req->InputCount);
 
        switch (cnt_code) {
        case FSCTL_DFS_GET_REFERRALS:
@@ -7494,6 +7593,7 @@ int smb2_ioctl(struct ksmbd_work *work)
                break;
        }
        case FSCTL_PIPE_TRANSCEIVE:
+               out_buf_len = min_t(u32, KSMBD_IPC_MAX_PAYLOAD, out_buf_len);
                nbytes = fsctl_pipe_transceive(work, id, out_buf_len, req, rsp);
                break;
        case FSCTL_VALIDATE_NEGOTIATE_INFO:
@@ -7502,9 +7602,16 @@ int smb2_ioctl(struct ksmbd_work *work)
                        goto out;
                }
 
+               if (in_buf_len < sizeof(struct validate_negotiate_info_req))
+                       return -EINVAL;
+
+               if (out_buf_len < sizeof(struct validate_negotiate_info_rsp))
+                       return -EINVAL;
+
                ret = fsctl_validate_negotiate_info(conn,
                        (struct validate_negotiate_info_req *)&req->Buffer[0],
-                       (struct validate_negotiate_info_rsp *)&rsp->Buffer[0]);
+                       (struct validate_negotiate_info_rsp *)&rsp->Buffer[0],
+                       in_buf_len);
                if (ret < 0)
                        goto out;
 
@@ -7513,9 +7620,10 @@ int smb2_ioctl(struct ksmbd_work *work)
                rsp->VolatileFileId = cpu_to_le64(SMB2_NO_FID);
                break;
        case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
-               nbytes = fsctl_query_iface_info_ioctl(conn, req, rsp);
-               if (nbytes < 0)
+               ret = fsctl_query_iface_info_ioctl(conn, rsp, out_buf_len);
+               if (ret < 0)
                        goto out;
+               nbytes = ret;
                break;
        case FSCTL_REQUEST_RESUME_KEY:
                if (out_buf_len < sizeof(struct resume_key_ioctl_rsp)) {
@@ -7540,15 +7648,33 @@ int smb2_ioctl(struct ksmbd_work *work)
                        goto out;
                }
 
+               if (in_buf_len < sizeof(struct copychunk_ioctl_req)) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
                if (out_buf_len < sizeof(struct copychunk_ioctl_rsp)) {
                        ret = -EINVAL;
                        goto out;
                }
 
                nbytes = sizeof(struct copychunk_ioctl_rsp);
-               fsctl_copychunk(work, req, rsp);
+               rsp->VolatileFileId = req->VolatileFileId;
+               rsp->PersistentFileId = req->PersistentFileId;
+               fsctl_copychunk(work,
+                               (struct copychunk_ioctl_req *)&req->Buffer[0],
+                               le32_to_cpu(req->CntCode),
+                               le32_to_cpu(req->InputCount),
+                               le64_to_cpu(req->VolatileFileId),
+                               le64_to_cpu(req->PersistentFileId),
+                               rsp);
                break;
        case FSCTL_SET_SPARSE:
+               if (in_buf_len < sizeof(struct file_sparse)) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
                ret = fsctl_set_sparse(work, id,
                                       (struct file_sparse *)&req->Buffer[0]);
                if (ret < 0)
@@ -7558,7 +7684,7 @@ int smb2_ioctl(struct ksmbd_work *work)
        {
                struct file_zero_data_information *zero_data;
                struct ksmbd_file *fp;
-               loff_t off, len;
+               loff_t off, len, bfz;
 
                if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
                        ksmbd_debug(SMB,
@@ -7567,25 +7693,42 @@ int smb2_ioctl(struct ksmbd_work *work)
                        goto out;
                }
 
+               if (in_buf_len < sizeof(struct file_zero_data_information)) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
                zero_data =
                        (struct file_zero_data_information *)&req->Buffer[0];
 
-               fp = ksmbd_lookup_fd_fast(work, id);
-               if (!fp) {
-                       ret = -ENOENT;
+               off = le64_to_cpu(zero_data->FileOffset);
+               bfz = le64_to_cpu(zero_data->BeyondFinalZero);
+               if (off > bfz) {
+                       ret = -EINVAL;
                        goto out;
                }
 
-               off = le64_to_cpu(zero_data->FileOffset);
-               len = le64_to_cpu(zero_data->BeyondFinalZero) - off;
+               len = bfz - off;
+               if (len) {
+                       fp = ksmbd_lookup_fd_fast(work, id);
+                       if (!fp) {
+                               ret = -ENOENT;
+                               goto out;
+                       }
 
-               ret = ksmbd_vfs_zero_data(work, fp, off, len);
-               ksmbd_fd_put(work, fp);
-               if (ret < 0)
-                       goto out;
+                       ret = ksmbd_vfs_zero_data(work, fp, off, len);
+                       ksmbd_fd_put(work, fp);
+                       if (ret < 0)
+                               goto out;
+               }
                break;
        }
        case FSCTL_QUERY_ALLOCATED_RANGES:
+               if (in_buf_len < sizeof(struct file_allocated_range_buffer)) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
                ret = fsctl_query_allocated_ranges(work, id,
                        (struct file_allocated_range_buffer *)&req->Buffer[0],
                        (struct file_allocated_range_buffer *)&rsp->Buffer[0],
@@ -7626,6 +7769,11 @@ int smb2_ioctl(struct ksmbd_work *work)
                struct duplicate_extents_to_file *dup_ext;
                loff_t src_off, dst_off, length, cloned;
 
+               if (in_buf_len < sizeof(struct duplicate_extents_to_file)) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
                dup_ext = (struct duplicate_extents_to_file *)&req->Buffer[0];
 
                fp_in = ksmbd_lookup_fd_slow(work, dup_ext->VolatileFileHandle,
@@ -7646,14 +7794,24 @@ int smb2_ioctl(struct ksmbd_work *work)
                src_off = le64_to_cpu(dup_ext->SourceFileOffset);
                dst_off = le64_to_cpu(dup_ext->TargetFileOffset);
                length = le64_to_cpu(dup_ext->ByteCount);
-               cloned = vfs_clone_file_range(fp_in->filp, src_off, fp_out->filp,
-                                             dst_off, length, 0);
+               /*
+                * XXX: It is not clear if FSCTL_DUPLICATE_EXTENTS_TO_FILE
+                * should fall back to vfs_copy_file_range().  This could be
+                * beneficial when re-exporting nfs/smb mount, but note that
+                * this can result in partial copy that returns an error status.
+                * If/when FSCTL_DUPLICATE_EXTENTS_TO_FILE_EX is implemented,
+                * fall back to vfs_copy_file_range(), should be avoided when
+                * the flag DUPLICATE_EXTENTS_DATA_EX_SOURCE_ATOMIC is set.
+                */
+               cloned = vfs_clone_file_range(fp_in->filp, src_off,
+                                             fp_out->filp, dst_off, length, 0);
                if (cloned == -EXDEV || cloned == -EOPNOTSUPP) {
                        ret = -EOPNOTSUPP;
                        goto dup_ext_out;
                } else if (cloned != length) {
                        cloned = vfs_copy_file_range(fp_in->filp, src_off,
-                                                    fp_out->filp, dst_off, length, 0);
+                                                    fp_out->filp, dst_off,
+                                                    length, 0);
                        if (cloned != length) {
                                if (cloned < 0)
                                        ret = cloned;
@@ -7696,6 +7854,8 @@ out:
                rsp->hdr.Status = STATUS_OBJECT_NAME_NOT_FOUND;
        else if (ret == -EOPNOTSUPP)
                rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+       else if (ret == -ENOSPC)
+               rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
        else if (ret < 0 || rsp->hdr.Status == 0)
                rsp->hdr.Status = STATUS_INVALID_PARAMETER;
        smb2_set_err_rsp(work);