]> 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 7e448df3f8474c49cea0e1a361f4220caf83abca..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"
@@ -301,16 +302,15 @@ 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;
+       unsigned short credits_requested, aux_max;
        unsigned short credit_charge, credits_granted = 0;
-       unsigned short aux_max, aux_credits;
 
        if (work->send_no_response)
                return 0;
 
        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);
                return -EINVAL;
@@ -318,6 +318,14 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
 
        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;
+       }
+
+       conn->total_credits -= credit_charge;
+       conn->outstanding_credits -= credit_charge;
        credits_requested = max_t(unsigned short,
                                  le16_to_cpu(req_hdr->CreditRequest), 1);
 
@@ -327,16 +335,14 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
         * TODO: Need to adjuct CreditRequest value according to
         * current cpu load
         */
-       aux_credits = credits_requested - 1;
        if (hdr->Command == SMB2_NEGOTIATE)
-               aux_max = 0;
+               aux_max = 1;
        else
-               aux_max = conn->max_credits - credit_charge;
-       aux_credits = min_t(unsigned short, aux_credits, aux_max);
-       credits_granted = credit_charge + aux_credits;
+               aux_max = conn->vals->max_credits - credit_charge;
+       credits_granted = min_t(unsigned short, credits_requested, aux_max);
 
-       if (conn->max_credits - conn->total_credits < credits_granted)
-               credits_granted = conn->max_credits -
+       if (conn->vals->max_credits - conn->total_credits < credits_granted)
+               credits_granted = conn->vals->max_credits -
                        conn->total_credits;
 
        conn->total_credits += credits_granted;
@@ -917,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)
 {
@@ -1438,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;
@@ -1455,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:
@@ -1562,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,
@@ -1700,8 +1719,10 @@ int smb2_sess_setup(struct ksmbd_work *work)
        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))
-               return -EINVAL;
+           negblob_len < offsetof(struct negotiate_message, NegotiateFlags)) {
+               rc = -EINVAL;
+               goto out_err;
+       }
 
        negblob = (struct negotiate_message *)((char *)&req->hdr.ProtocolId +
                        negblob_off);
@@ -2670,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;
                                }
@@ -2962,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,
@@ -3398,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;
@@ -3952,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);
@@ -4450,6 +4476,12 @@ static void get_file_stream_info(struct ksmbd_work *work,
                         &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;
@@ -4458,12 +4490,6 @@ static void get_file_stream_info(struct ksmbd_work *work,
                goto out;
        }
 
-       buf_free_len =
-               smb2_calc_max_out_buf_len(work, 8,
-                                         le32_to_cpu(req->OutputBufferLength));
-       if (buf_free_len < 0)
-               goto out;
-
        while (idx < xattr_list_len) {
                stream_name = xattr_list + idx;
                streamlen = strlen(stream_name);
@@ -4489,8 +4515,10 @@ static void get_file_stream_info(struct ksmbd_work *work,
                                     ":%s", &stream_name[XATTR_NAME_STREAM_LEN]);
 
                next = sizeof(struct smb2_file_stream_info) + streamlen * 2;
-               if (next > buf_free_len)
+               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,
@@ -4507,6 +4535,7 @@ static void get_file_stream_info(struct ksmbd_work *work,
                file_info->NextEntryOffset = cpu_to_le32(next);
        }
 
+out:
        if (!S_ISDIR(stat.mode) &&
            buf_free_len >= sizeof(struct smb2_file_stream_info) + 7 * 2) {
                file_info = (struct smb2_file_stream_info *)
@@ -4515,14 +4544,13 @@ static void get_file_stream_info(struct ksmbd_work *work,
                                              "::$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);
@@ -4891,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);
@@ -4963,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;
@@ -5053,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);
@@ -5734,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,
@@ -7312,7 +7351,7 @@ static int fsctl_validate_negotiate_info(struct ksmbd_conn *conn,
        int ret = 0;
        int dialect;
 
-       if (in_buf_len < sizeof(struct validate_negotiate_info_req) +
+       if (in_buf_len < offsetof(struct validate_negotiate_info_req, Dialects) +
                        le16_to_cpu(neg_req->DialectCount) * sizeof(__le16))
                return -EINVAL;
 
@@ -7645,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,
@@ -7662,19 +7701,26 @@ int smb2_ioctl(struct ksmbd_work *work)
                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:
@@ -7748,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;