]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
smb: client: avoid integer overflow in SMB2 READ length check
authorJeremy Erazo <mendozayt13@gmail.com>
Thu, 14 May 2026 12:03:34 +0000 (12:03 +0000)
committerSteve French <stfrench@microsoft.com>
Thu, 14 May 2026 15:55:28 +0000 (10:55 -0500)
SMB2 READ response validation in cifs_readv_receive() and
handle_read_data() checks data_offset + data_len against the received
buffer length.  Both values are attacker-controlled fields from the
server response and are stored as unsigned int, so the addition can
wrap before the bounds check:

fs/smb/client/transport.c:1259
if (!use_rdma_mr && (data_offset + data_len > buflen))

fs/smb/client/smb2ops.c:4839
else if (buf_len >= data_offset + data_len)

A malicious SMB server can use this to bypass validation.  In the
non-encrypted receive path the client attempts an oversized socket
read and stalls for the SMB response timeout (180 seconds) before
reconnecting.  In the SMB3 encrypted path, runtime testing shows the
malformed length can reach copy_to_iter() in handle_read_data() with
attacker-controlled size, where usercopy hardening stops the oversized
copy before bytes reach userspace.

Guard both call sites with check_add_overflow(), which is already
used elsewhere in this subsystem (smb2pdu.c).  On overflow, treat the
response as malformed and reject with -EIO.

Signed-off-by: Jeremy Erazo <mendozayt13@gmail.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/smb2ops.c
fs/smb/client/transport.c

index e6cb9b144530d2c44bcd08511e23ee53c9438680..3738204984f5a05f2798f22962c40aa0c2450259 100644 (file)
@@ -4721,6 +4721,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
 {
        unsigned int data_offset;
        unsigned int data_len;
+       unsigned int end_off;
        unsigned int cur_off;
        unsigned int cur_page_idx;
        unsigned int pad_len;
@@ -4836,7 +4837,8 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
                }
                rdata->got_bytes = buffer_len;
 
-       } else if (buf_len >= data_offset + data_len) {
+       } else if (!check_add_overflow(data_offset, data_len, &end_off) &&
+                  buf_len >= end_off) {
                /* read response payload is in buf */
                WARN_ONCE(buffer, "read data can be either in buf or in buffer");
                copied = copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_iter);
index 05f8099047e1a423a12a1cea2eaec009d9dd050d..fdf4e50c27ceb4702413db289dba76b66c1ffa09 100644 (file)
@@ -1158,7 +1158,7 @@ int
 cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
 {
        int length, len;
-       unsigned int data_offset, data_len;
+       unsigned int data_offset, data_len, end_off;
        struct cifs_io_subrequest *rdata = mid->callback_data;
        char *buf = server->smallbuf;
        unsigned int buflen = server->pdu_size;
@@ -1256,11 +1256,14 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
        use_rdma_mr = rdata->mr;
 #endif
        data_len = server->ops->read_data_length(buf, use_rdma_mr);
-       if (!use_rdma_mr && (data_offset + data_len > buflen)) {
-               /* data_len is corrupt -- discard frame */
-               rdata->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,
-                                        data_offset + data_len, buflen);
-               return cifs_readv_discard(server, mid);
+       if (!use_rdma_mr) {
+               if (check_add_overflow(data_offset, data_len, &end_off) ||
+                   end_off > buflen) {
+                       /* data_len is corrupt -- discard frame */
+                       rdata->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,
+                                                end_off, buflen);
+                       return cifs_readv_discard(server, mid);
+               }
        }
 
 #ifdef CONFIG_CIFS_SMB_DIRECT