OSDN Git Service

ksmbd: add validation in smb2_ioctl
authorNamjae Jeon <linkinjeon@kernel.org>
Thu, 7 Oct 2021 22:31:03 +0000 (07:31 +0900)
committerSteve French <stfrench@microsoft.com>
Thu, 14 Oct 2021 04:37:18 +0000 (23:37 -0500)
Add validation for request/response buffer size check in smb2_ioctl and
fsctl_copychunk() take copychunk_ioctl_req pointer and the other arguments
instead of smb2_ioctl_req structure and remove an unused smb2_ioctl_req
argument of fsctl_validate_negotiate_info.

Cc: Tom Talpey <tom@talpey.com>
Cc: Ronnie Sahlberg <ronniesahlberg@gmail.com>
Cc: Ralph Böhme <slow@samba.org>
Cc: Steve French <smfrench@gmail.com>
Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
Acked-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Colin Ian King <colin.king@canonical.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/ksmbd/smb2pdu.c
fs/ksmbd/vfs.c
fs/ksmbd/vfs.h

index 005aa93..1c7b837 100644 (file)
@@ -7023,24 +7023,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 +7052,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 +7079,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 +7154,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 +7167,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 +7252,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 +7259,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 < sizeof(struct validate_negotiate_info_req) +
+                       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 +7302,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 +7329,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 +7444,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;
@@ -7466,7 +7473,11 @@ 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);
+       out_buf_len =
+               min_t(u32, work->response_sz - work->next_smb2_rsp_hdr_off -
+                               (offsetof(struct smb2_ioctl_rsp, Buffer) - 4),
+                     out_buf_len);
+       in_buf_len = le32_to_cpu(req->InputCount);
 
        switch (cnt_code) {
        case FSCTL_DFS_GET_REFERRALS:
@@ -7494,6 +7505,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 +7514,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 +7532,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 +7560,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)
@@ -7567,6 +7605,11 @@ 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];
 
@@ -7586,6 +7629,11 @@ int smb2_ioctl(struct ksmbd_work *work)
                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 +7674,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,
@@ -7696,6 +7749,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);
index b419542..835b384 100644 (file)
@@ -1023,7 +1023,7 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
 
 int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
                         struct file_allocated_range_buffer *ranges,
-                        int in_count, int *out_count)
+                        unsigned int in_count, unsigned int *out_count)
 {
        struct file *f = fp->filp;
        struct inode *inode = file_inode(fp->filp);
index 7b1dcaa..b0d5b8f 100644 (file)
@@ -166,7 +166,7 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
 struct file_allocated_range_buffer;
 int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
                         struct file_allocated_range_buffer *ranges,
-                        int in_count, int *out_count);
+                        unsigned int in_count, unsigned int *out_count);
 int ksmbd_vfs_unlink(struct user_namespace *user_ns,
                     struct dentry *dir, struct dentry *dentry);
 void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat);