hv_utils: Add validation for untrusted Hyper-V values
authorAndres Beltran <lkmlabelt@gmail.com>
Mon, 9 Nov 2020 10:07:04 +0000 (11:07 +0100)
committerWei Liu <wei.liu@kernel.org>
Fri, 5 Feb 2021 09:55:42 +0000 (09:55 +0000)
For additional robustness in the face of Hyper-V errors or malicious
behavior, validate all values that originate from packets that Hyper-V
has sent to the guest in the host-to-guest ring buffer. Ensure that
invalid values cannot cause indexing off the end of the icversion_data
array in vmbus_prep_negotiate_resp().

Signed-off-by: Andres Beltran <lkmlabelt@gmail.com>
Co-developed-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com>
Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com>
Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Link: https://lore.kernel.org/r/20201109100704.9152-1-parri.andrea@gmail.com
Signed-off-by: Wei Liu <wei.liu@kernel.org>
drivers/hv/channel_mgmt.c
drivers/hv/hv_fcopy.c
drivers/hv/hv_kvp.c
drivers/hv/hv_snapshot.c
drivers/hv/hv_util.c
include/linux/hyperv.h

index 1d44bb635bb8450eece2e5f655314475ce33f896..5bc5eef5da15948d73334410f31a11199eae9431 100644 (file)
@@ -190,6 +190,7 @@ static u16 hv_get_dev_type(const struct vmbus_channel *channel)
  * vmbus_prep_negotiate_resp() - Create default response for Negotiate message
  * @icmsghdrp: Pointer to msg header structure
  * @buf: Raw buffer channel data
+ * @buflen: Length of the raw buffer channel data.
  * @fw_version: The framework versions we can support.
  * @fw_vercnt: The size of @fw_version.
  * @srv_version: The service versions we can support.
@@ -202,8 +203,8 @@ static u16 hv_get_dev_type(const struct vmbus_channel *channel)
  * Set up and fill in default negotiate response message.
  * Mainly used by Hyper-V drivers.
  */
-bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
-                               u8 *buf, const int *fw_version, int fw_vercnt,
+bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, u8 *buf,
+                               u32 buflen, const int *fw_version, int fw_vercnt,
                                const int *srv_version, int srv_vercnt,
                                int *nego_fw_version, int *nego_srv_version)
 {
@@ -215,10 +216,14 @@ bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
        bool found_match = false;
        struct icmsg_negotiate *negop;
 
+       /* Check that there's enough space for icframe_vercnt, icmsg_vercnt */
+       if (buflen < ICMSG_HDR + offsetof(struct icmsg_negotiate, reserved)) {
+               pr_err_ratelimited("Invalid icmsg negotiate\n");
+               return false;
+       }
+
        icmsghdrp->icmsgsize = 0x10;
-       negop = (struct icmsg_negotiate *)&buf[
-               sizeof(struct vmbuspipe_hdr) +
-               sizeof(struct icmsg_hdr)];
+       negop = (struct icmsg_negotiate *)&buf[ICMSG_HDR];
 
        icframe_major = negop->icframe_vercnt;
        icframe_minor = 0;
@@ -226,6 +231,15 @@ bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
        icmsg_major = negop->icmsg_vercnt;
        icmsg_minor = 0;
 
+       /* Validate negop packet */
+       if (icframe_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT ||
+           icmsg_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT ||
+           ICMSG_NEGOTIATE_PKT_SIZE(icframe_major, icmsg_major) > buflen) {
+               pr_err_ratelimited("Invalid icmsg negotiate - icframe_major: %u, icmsg_major: %u\n",
+                                  icframe_major, icmsg_major);
+               goto fw_error;
+       }
+
        /*
         * Select the framework version number we will
         * support.
index 85784a9e6a36081fd686546c3d40a9d7a3b9b18c..660036da744953ef93ccd204df470b04e64daaee 100644 (file)
@@ -235,15 +235,27 @@ void hv_fcopy_onchannelcallback(void *context)
        if (fcopy_transaction.state > HVUTIL_READY)
                return;
 
-       vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 2, &recvlen,
-                        &requestid);
-       if (recvlen <= 0)
+       if (vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 2, &recvlen, &requestid)) {
+               pr_err_ratelimited("Fcopy request received. Could not read into recv buf\n");
                return;
+       }
+
+       if (!recvlen)
+               return;
+
+       /* Ensure recvlen is big enough to read header data */
+       if (recvlen < ICMSG_HDR) {
+               pr_err_ratelimited("Fcopy request received. Packet length too small: %d\n",
+                                  recvlen);
+               return;
+       }
 
        icmsghdr = (struct icmsg_hdr *)&recv_buffer[
                        sizeof(struct vmbuspipe_hdr)];
+
        if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) {
-               if (vmbus_prep_negotiate_resp(icmsghdr, recv_buffer,
+               if (vmbus_prep_negotiate_resp(icmsghdr,
+                               recv_buffer, recvlen,
                                fw_versions, FW_VER_COUNT,
                                fcopy_versions, FCOPY_VER_COUNT,
                                NULL, &fcopy_srv_version)) {
@@ -252,10 +264,14 @@ void hv_fcopy_onchannelcallback(void *context)
                                fcopy_srv_version >> 16,
                                fcopy_srv_version & 0xFFFF);
                }
-       } else {
-               fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[
-                               sizeof(struct vmbuspipe_hdr) +
-                               sizeof(struct icmsg_hdr)];
+       } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) {
+               /* Ensure recvlen is big enough to contain hv_fcopy_hdr */
+               if (recvlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) {
+                       pr_err_ratelimited("Invalid Fcopy hdr. Packet length too small: %u\n",
+                                          recvlen);
+                       return;
+               }
+               fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[ICMSG_HDR];
 
                /*
                 * Stash away this global state for completing the
@@ -280,6 +296,10 @@ void hv_fcopy_onchannelcallback(void *context)
                schedule_delayed_work(&fcopy_timeout_work,
                                      HV_UTIL_TIMEOUT * HZ);
                return;
+       } else {
+               pr_err_ratelimited("Fcopy request received. Invalid msg type: %d\n",
+                                  icmsghdr->icmsgtype);
+               return;
        }
        icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
        vmbus_sendpacket(channel, recv_buffer, recvlen, requestid,
index 7d2a7b9188473470c8dd2e9aeb5cf3f56f00c21a..c698592b83e421a6e98c1701ddc76b3333710ef3 100644 (file)
@@ -662,71 +662,87 @@ void hv_kvp_onchannelcallback(void *context)
        if (kvp_transaction.state > HVUTIL_READY)
                return;
 
-       vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 4, &recvlen,
-                        &requestid);
-
-       if (recvlen > 0) {
-               icmsghdrp = (struct icmsg_hdr *)&recv_buffer[
-                       sizeof(struct vmbuspipe_hdr)];
-
-               if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
-                       if (vmbus_prep_negotiate_resp(icmsghdrp,
-                                recv_buffer, fw_versions, FW_VER_COUNT,
-                                kvp_versions, KVP_VER_COUNT,
-                                NULL, &kvp_srv_version)) {
-                               pr_info("KVP IC version %d.%d\n",
-                                       kvp_srv_version >> 16,
-                                       kvp_srv_version & 0xFFFF);
-                       }
-               } else {
-                       kvp_msg = (struct hv_kvp_msg *)&recv_buffer[
-                               sizeof(struct vmbuspipe_hdr) +
-                               sizeof(struct icmsg_hdr)];
+       if (vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 4, &recvlen, &requestid)) {
+               pr_err_ratelimited("KVP request received. Could not read into recv buf\n");
+               return;
+       }
 
-                       /*
-                        * Stash away this global state for completing the
-                        * transaction; note transactions are serialized.
-                        */
+       if (!recvlen)
+               return;
 
-                       kvp_transaction.recv_len = recvlen;
-                       kvp_transaction.recv_req_id = requestid;
-                       kvp_transaction.kvp_msg = kvp_msg;
+       /* Ensure recvlen is big enough to read header data */
+       if (recvlen < ICMSG_HDR) {
+               pr_err_ratelimited("KVP request received. Packet length too small: %d\n",
+                                  recvlen);
+               return;
+       }
 
-                       if (kvp_transaction.state < HVUTIL_READY) {
-                               /* Userspace is not registered yet */
-                               kvp_respond_to_host(NULL, HV_E_FAIL);
-                               return;
-                       }
-                       kvp_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
+       icmsghdrp = (struct icmsg_hdr *)&recv_buffer[sizeof(struct vmbuspipe_hdr)];
+
+       if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
+               if (vmbus_prep_negotiate_resp(icmsghdrp,
+                               recv_buffer, recvlen,
+                               fw_versions, FW_VER_COUNT,
+                               kvp_versions, KVP_VER_COUNT,
+                               NULL, &kvp_srv_version)) {
+                       pr_info("KVP IC version %d.%d\n",
+                               kvp_srv_version >> 16,
+                               kvp_srv_version & 0xFFFF);
+               }
+       } else if (icmsghdrp->icmsgtype == ICMSGTYPE_KVPEXCHANGE) {
+               /*
+                * recvlen is not checked against sizeof(struct kvp_msg) because kvp_msg contains
+                * a union of structs and the msg type received is not known. Code using this
+                * struct should provide validation when accessing its fields.
+                */
+               kvp_msg = (struct hv_kvp_msg *)&recv_buffer[ICMSG_HDR];
 
-                       /*
-                        * Get the information from the
-                        * user-mode component.
-                        * component. This transaction will be
-                        * completed when we get the value from
-                        * the user-mode component.
-                        * Set a timeout to deal with
-                        * user-mode not responding.
-                        */
-                       schedule_work(&kvp_sendkey_work);
-                       schedule_delayed_work(&kvp_timeout_work,
-                                             HV_UTIL_TIMEOUT * HZ);
+               /*
+                * Stash away this global state for completing the
+                * transaction; note transactions are serialized.
+                */
 
-                       return;
+               kvp_transaction.recv_len = recvlen;
+               kvp_transaction.recv_req_id = requestid;
+               kvp_transaction.kvp_msg = kvp_msg;
 
+               if (kvp_transaction.state < HVUTIL_READY) {
+                       /* Userspace is not registered yet */
+                       kvp_respond_to_host(NULL, HV_E_FAIL);
+                       return;
                }
+               kvp_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
 
-               icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
-                       | ICMSGHDRFLAG_RESPONSE;
+               /*
+                * Get the information from the
+                * user-mode component.
+                * component. This transaction will be
+                * completed when we get the value from
+                * the user-mode component.
+                * Set a timeout to deal with
+                * user-mode not responding.
+                */
+               schedule_work(&kvp_sendkey_work);
+               schedule_delayed_work(&kvp_timeout_work,
+                                       HV_UTIL_TIMEOUT * HZ);
 
-               vmbus_sendpacket(channel, recv_buffer,
-                                      recvlen, requestid,
-                                      VM_PKT_DATA_INBAND, 0);
+               return;
 
-               host_negotiatied = NEGO_FINISHED;
-               hv_poll_channel(kvp_transaction.recv_channel, kvp_poll_wrapper);
+       } else {
+               pr_err_ratelimited("KVP request received. Invalid msg type: %d\n",
+                                  icmsghdrp->icmsgtype);
+               return;
        }
 
+       icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
+               | ICMSGHDRFLAG_RESPONSE;
+
+       vmbus_sendpacket(channel, recv_buffer,
+                        recvlen, requestid,
+                        VM_PKT_DATA_INBAND, 0);
+
+       host_negotiatied = NEGO_FINISHED;
+       hv_poll_channel(kvp_transaction.recv_channel, kvp_poll_wrapper);
 }
 
 static void kvp_on_reset(void)
index 783779e4cc1a5c7dd41037591b6412e75a5e9eb8..2267bd4c347259f944203863ba772f48ad208567 100644 (file)
@@ -298,49 +298,64 @@ void hv_vss_onchannelcallback(void *context)
        if (vss_transaction.state > HVUTIL_READY)
                return;
 
-       vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 2, &recvlen,
-                        &requestid);
-
-       if (recvlen > 0) {
-               icmsghdrp = (struct icmsg_hdr *)&recv_buffer[
-                       sizeof(struct vmbuspipe_hdr)];
-
-               if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
-                       if (vmbus_prep_negotiate_resp(icmsghdrp,
-                                recv_buffer, fw_versions, FW_VER_COUNT,
-                                vss_versions, VSS_VER_COUNT,
-                                NULL, &vss_srv_version)) {
-
-                               pr_info("VSS IC version %d.%d\n",
-                                       vss_srv_version >> 16,
-                                       vss_srv_version & 0xFFFF);
-                       }
-               } else {
-                       vss_msg = (struct hv_vss_msg *)&recv_buffer[
-                               sizeof(struct vmbuspipe_hdr) +
-                               sizeof(struct icmsg_hdr)];
-
-                       /*
-                        * Stash away this global state for completing the
-                        * transaction; note transactions are serialized.
-                        */
-
-                       vss_transaction.recv_len = recvlen;
-                       vss_transaction.recv_req_id = requestid;
-                       vss_transaction.msg = (struct hv_vss_msg *)vss_msg;
-
-                       schedule_work(&vss_handle_request_work);
+       if (vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 2, &recvlen, &requestid)) {
+               pr_err_ratelimited("VSS request received. Could not read into recv buf\n");
+               return;
+       }
+
+       if (!recvlen)
+               return;
+
+       /* Ensure recvlen is big enough to read header data */
+       if (recvlen < ICMSG_HDR) {
+               pr_err_ratelimited("VSS request received. Packet length too small: %d\n",
+                                  recvlen);
+               return;
+       }
+
+       icmsghdrp = (struct icmsg_hdr *)&recv_buffer[sizeof(struct vmbuspipe_hdr)];
+
+       if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
+               if (vmbus_prep_negotiate_resp(icmsghdrp,
+                               recv_buffer, recvlen,
+                               fw_versions, FW_VER_COUNT,
+                               vss_versions, VSS_VER_COUNT,
+                               NULL, &vss_srv_version)) {
+
+                       pr_info("VSS IC version %d.%d\n",
+                               vss_srv_version >> 16,
+                               vss_srv_version & 0xFFFF);
+               }
+       } else if (icmsghdrp->icmsgtype == ICMSGTYPE_VSS) {
+               /* Ensure recvlen is big enough to contain hv_vss_msg */
+               if (recvlen < ICMSG_HDR + sizeof(struct hv_vss_msg)) {
+                       pr_err_ratelimited("Invalid VSS msg. Packet length too small: %u\n",
+                                          recvlen);
                        return;
                }
+               vss_msg = (struct hv_vss_msg *)&recv_buffer[ICMSG_HDR];
+
+               /*
+                * Stash away this global state for completing the
+                * transaction; note transactions are serialized.
+                */
 
-               icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
-                       | ICMSGHDRFLAG_RESPONSE;
+               vss_transaction.recv_len = recvlen;
+               vss_transaction.recv_req_id = requestid;
+               vss_transaction.msg = (struct hv_vss_msg *)vss_msg;
 
-               vmbus_sendpacket(channel, recv_buffer,
-                                      recvlen, requestid,
-                                      VM_PKT_DATA_INBAND, 0);
+               schedule_work(&vss_handle_request_work);
+               return;
+       } else {
+               pr_err_ratelimited("VSS request received. Invalid msg type: %d\n",
+                                  icmsghdrp->icmsgtype);
+               return;
        }
 
+       icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION |
+               ICMSGHDRFLAG_RESPONSE;
+       vmbus_sendpacket(channel, recv_buffer, recvlen, requestid,
+                        VM_PKT_DATA_INBAND, 0);
 }
 
 static void vss_on_reset(void)
index 05566ecdbe4b4b2fa0504c1d485dbfcbc8f2b083..34f3e789cc9a20b19a50aed6ca72a8d00493bc73 100644 (file)
@@ -195,73 +195,88 @@ static void shutdown_onchannelcallback(void *context)
 
        struct icmsg_hdr *icmsghdrp;
 
-       vmbus_recvpacket(channel, shut_txf_buf,
-                        HV_HYP_PAGE_SIZE, &recvlen, &requestid);
+       if (vmbus_recvpacket(channel, shut_txf_buf, HV_HYP_PAGE_SIZE, &recvlen, &requestid)) {
+               pr_err_ratelimited("Shutdown request received. Could not read into shut txf buf\n");
+               return;
+       }
 
-       if (recvlen > 0) {
-               icmsghdrp = (struct icmsg_hdr *)&shut_txf_buf[
-                       sizeof(struct vmbuspipe_hdr)];
+       if (!recvlen)
+               return;
 
-               if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
-                       if (vmbus_prep_negotiate_resp(icmsghdrp, shut_txf_buf,
-                                       fw_versions, FW_VER_COUNT,
-                                       sd_versions, SD_VER_COUNT,
-                                       NULL, &sd_srv_version)) {
-                               pr_info("Shutdown IC version %d.%d\n",
-                                       sd_srv_version >> 16,
-                                       sd_srv_version & 0xFFFF);
-                       }
-               } else {
-                       shutdown_msg =
-                               (struct shutdown_msg_data *)&shut_txf_buf[
-                                       sizeof(struct vmbuspipe_hdr) +
-                                       sizeof(struct icmsg_hdr)];
+       /* Ensure recvlen is big enough to read header data */
+       if (recvlen < ICMSG_HDR) {
+               pr_err_ratelimited("Shutdown request received. Packet length too small: %d\n",
+                                  recvlen);
+               return;
+       }
 
-                       /*
-                        * shutdown_msg->flags can be 0(shut down), 2(reboot),
-                        * or 4(hibernate). It may bitwise-OR 1, which means
-                        * performing the request by force. Linux always tries
-                        * to perform the request by force.
-                        */
-                       switch (shutdown_msg->flags) {
-                       case 0:
-                       case 1:
-                               icmsghdrp->status = HV_S_OK;
-                               work = &shutdown_work;
-                               pr_info("Shutdown request received -"
-                                           " graceful shutdown initiated\n");
-                               break;
-                       case 2:
-                       case 3:
-                               icmsghdrp->status = HV_S_OK;
-                               work = &restart_work;
-                               pr_info("Restart request received -"
-                                           " graceful restart initiated\n");
-                               break;
-                       case 4:
-                       case 5:
-                               pr_info("Hibernation request received\n");
-                               icmsghdrp->status = hibernation_supported ?
-                                       HV_S_OK : HV_E_FAIL;
-                               if (hibernation_supported)
-                                       work = &hibernate_context.work;
-                               break;
-                       default:
-                               icmsghdrp->status = HV_E_FAIL;
-                               pr_info("Shutdown request received -"
-                                           " Invalid request\n");
-                               break;
-                       }
+       icmsghdrp = (struct icmsg_hdr *)&shut_txf_buf[sizeof(struct vmbuspipe_hdr)];
+
+       if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
+               if (vmbus_prep_negotiate_resp(icmsghdrp,
+                               shut_txf_buf, recvlen,
+                               fw_versions, FW_VER_COUNT,
+                               sd_versions, SD_VER_COUNT,
+                               NULL, &sd_srv_version)) {
+                       pr_info("Shutdown IC version %d.%d\n",
+                               sd_srv_version >> 16,
+                               sd_srv_version & 0xFFFF);
+               }
+       } else if (icmsghdrp->icmsgtype == ICMSGTYPE_SHUTDOWN) {
+               /* Ensure recvlen is big enough to contain shutdown_msg_data struct */
+               if (recvlen < ICMSG_HDR + sizeof(struct shutdown_msg_data)) {
+                       pr_err_ratelimited("Invalid shutdown msg data. Packet length too small: %u\n",
+                                          recvlen);
+                       return;
                }
 
-               icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
-                       | ICMSGHDRFLAG_RESPONSE;
-
-               vmbus_sendpacket(channel, shut_txf_buf,
-                                      recvlen, requestid,
-                                      VM_PKT_DATA_INBAND, 0);
+               shutdown_msg = (struct shutdown_msg_data *)&shut_txf_buf[ICMSG_HDR];
+
+               /*
+                * shutdown_msg->flags can be 0(shut down), 2(reboot),
+                * or 4(hibernate). It may bitwise-OR 1, which means
+                * performing the request by force. Linux always tries
+                * to perform the request by force.
+                */
+               switch (shutdown_msg->flags) {
+               case 0:
+               case 1:
+                       icmsghdrp->status = HV_S_OK;
+                       work = &shutdown_work;
+                       pr_info("Shutdown request received - graceful shutdown initiated\n");
+                       break;
+               case 2:
+               case 3:
+                       icmsghdrp->status = HV_S_OK;
+                       work = &restart_work;
+                       pr_info("Restart request received - graceful restart initiated\n");
+                       break;
+               case 4:
+               case 5:
+                       pr_info("Hibernation request received\n");
+                       icmsghdrp->status = hibernation_supported ?
+                               HV_S_OK : HV_E_FAIL;
+                       if (hibernation_supported)
+                               work = &hibernate_context.work;
+                       break;
+               default:
+                       icmsghdrp->status = HV_E_FAIL;
+                       pr_info("Shutdown request received - Invalid request\n");
+                       break;
+               }
+       } else {
+               icmsghdrp->status = HV_E_FAIL;
+               pr_err_ratelimited("Shutdown request received. Invalid msg type: %d\n",
+                                  icmsghdrp->icmsgtype);
        }
 
+       icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
+               | ICMSGHDRFLAG_RESPONSE;
+
+       vmbus_sendpacket(channel, shut_txf_buf,
+                        recvlen, requestid,
+                        VM_PKT_DATA_INBAND, 0);
+
        if (work)
                schedule_work(work);
 }
@@ -396,19 +411,27 @@ static void timesync_onchannelcallback(void *context)
                                           HV_HYP_PAGE_SIZE, &recvlen,
                                           &requestid);
                if (ret) {
-                       pr_warn_once("TimeSync IC pkt recv failed (Err: %d)\n",
-                                    ret);
+                       pr_err_ratelimited("TimeSync IC pkt recv failed (Err: %d)\n",
+                                          ret);
                        break;
                }
 
                if (!recvlen)
                        break;
 
+               /* Ensure recvlen is big enough to read header data */
+               if (recvlen < ICMSG_HDR) {
+                       pr_err_ratelimited("Timesync request received. Packet length too small: %d\n",
+                                          recvlen);
+                       break;
+               }
+
                icmsghdrp = (struct icmsg_hdr *)&time_txf_buf[
                                sizeof(struct vmbuspipe_hdr)];
 
                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
-                       if (vmbus_prep_negotiate_resp(icmsghdrp, time_txf_buf,
+                       if (vmbus_prep_negotiate_resp(icmsghdrp,
+                                               time_txf_buf, recvlen,
                                                fw_versions, FW_VER_COUNT,
                                                ts_versions, TS_VER_COUNT,
                                                NULL, &ts_srv_version)) {
@@ -416,33 +439,44 @@ static void timesync_onchannelcallback(void *context)
                                        ts_srv_version >> 16,
                                        ts_srv_version & 0xFFFF);
                        }
-               } else {
+               } else if (icmsghdrp->icmsgtype == ICMSGTYPE_TIMESYNC) {
                        if (ts_srv_version > TS_VERSION_3) {
-                               refdata = (struct ictimesync_ref_data *)
-                                       &time_txf_buf[
-                                       sizeof(struct vmbuspipe_hdr) +
-                                       sizeof(struct icmsg_hdr)];
+                               /* Ensure recvlen is big enough to read ictimesync_ref_data */
+                               if (recvlen < ICMSG_HDR + sizeof(struct ictimesync_ref_data)) {
+                                       pr_err_ratelimited("Invalid ictimesync ref data. Length too small: %u\n",
+                                                          recvlen);
+                                       break;
+                               }
+                               refdata = (struct ictimesync_ref_data *)&time_txf_buf[ICMSG_HDR];
 
                                adj_guesttime(refdata->parenttime,
                                                refdata->vmreferencetime,
                                                refdata->flags);
                        } else {
-                               timedatap = (struct ictimesync_data *)
-                                       &time_txf_buf[
-                                       sizeof(struct vmbuspipe_hdr) +
-                                       sizeof(struct icmsg_hdr)];
+                               /* Ensure recvlen is big enough to read ictimesync_data */
+                               if (recvlen < ICMSG_HDR + sizeof(struct ictimesync_data)) {
+                                       pr_err_ratelimited("Invalid ictimesync data. Length too small: %u\n",
+                                                          recvlen);
+                                       break;
+                               }
+                               timedatap = (struct ictimesync_data *)&time_txf_buf[ICMSG_HDR];
+
                                adj_guesttime(timedatap->parenttime,
                                              hv_read_reference_counter(),
                                              timedatap->flags);
                        }
+               } else {
+                       icmsghdrp->status = HV_E_FAIL;
+                       pr_err_ratelimited("Timesync request received. Invalid msg type: %d\n",
+                                          icmsghdrp->icmsgtype);
                }
 
                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
                        | ICMSGHDRFLAG_RESPONSE;
 
                vmbus_sendpacket(channel, time_txf_buf,
-                               recvlen, requestid,
-                               VM_PKT_DATA_INBAND, 0);
+                                recvlen, requestid,
+                                VM_PKT_DATA_INBAND, 0);
        }
 }
 
@@ -462,18 +496,28 @@ static void heartbeat_onchannelcallback(void *context)
 
        while (1) {
 
-               vmbus_recvpacket(channel, hbeat_txf_buf,
-                                HV_HYP_PAGE_SIZE, &recvlen, &requestid);
+               if (vmbus_recvpacket(channel, hbeat_txf_buf, HV_HYP_PAGE_SIZE,
+                                    &recvlen, &requestid)) {
+                       pr_err_ratelimited("Heartbeat request received. Could not read into hbeat txf buf\n");
+                       return;
+               }
 
                if (!recvlen)
                        break;
 
+               /* Ensure recvlen is big enough to read header data */
+               if (recvlen < ICMSG_HDR) {
+                       pr_err_ratelimited("Hearbeat request received. Packet length too small: %d\n",
+                                          recvlen);
+                       break;
+               }
+
                icmsghdrp = (struct icmsg_hdr *)&hbeat_txf_buf[
                                sizeof(struct vmbuspipe_hdr)];
 
                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
                        if (vmbus_prep_negotiate_resp(icmsghdrp,
-                                       hbeat_txf_buf,
+                                       hbeat_txf_buf, recvlen,
                                        fw_versions, FW_VER_COUNT,
                                        hb_versions, HB_VER_COUNT,
                                        NULL, &hb_srv_version)) {
@@ -482,21 +526,31 @@ static void heartbeat_onchannelcallback(void *context)
                                        hb_srv_version >> 16,
                                        hb_srv_version & 0xFFFF);
                        }
-               } else {
-                       heartbeat_msg =
-                               (struct heartbeat_msg_data *)&hbeat_txf_buf[
-                                       sizeof(struct vmbuspipe_hdr) +
-                                       sizeof(struct icmsg_hdr)];
+               } else if (icmsghdrp->icmsgtype == ICMSGTYPE_HEARTBEAT) {
+                       /*
+                        * Ensure recvlen is big enough to read seq_num. Reserved area is not
+                        * included in the check as the host may not fill it up entirely
+                        */
+                       if (recvlen < ICMSG_HDR + sizeof(u64)) {
+                               pr_err_ratelimited("Invalid heartbeat msg data. Length too small: %u\n",
+                                                  recvlen);
+                               break;
+                       }
+                       heartbeat_msg = (struct heartbeat_msg_data *)&hbeat_txf_buf[ICMSG_HDR];
 
                        heartbeat_msg->seq_num += 1;
+               } else {
+                       icmsghdrp->status = HV_E_FAIL;
+                       pr_err_ratelimited("Heartbeat request received. Invalid msg type: %d\n",
+                                          icmsghdrp->icmsgtype);
                }
 
                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
                        | ICMSGHDRFLAG_RESPONSE;
 
                vmbus_sendpacket(channel, hbeat_txf_buf,
-                                      recvlen, requestid,
-                                      VM_PKT_DATA_INBAND, 0);
+                                recvlen, requestid,
+                                VM_PKT_DATA_INBAND, 0);
        }
 }
 
index fbae8406d5d455a3f9638ec6a55f4422a3d250d1..2ea967bc17adfc38c0e0cc2cb976e3815d6224d2 100644 (file)
@@ -1480,6 +1480,7 @@ void vmbus_free_mmio(resource_size_t start, resource_size_t size);
 #define ICMSGTYPE_SHUTDOWN             3
 #define ICMSGTYPE_TIMESYNC             4
 #define ICMSGTYPE_VSS                  5
+#define ICMSGTYPE_FCOPY                        7
 
 #define ICMSGHDRFLAG_TRANSACTION       1
 #define ICMSGHDRFLAG_REQUEST           2
@@ -1523,6 +1524,12 @@ struct icmsg_hdr {
        u8 reserved[2];
 } __packed;
 
+#define IC_VERSION_NEGOTIATION_MAX_VER_COUNT 100
+#define ICMSG_HDR (sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr))
+#define ICMSG_NEGOTIATE_PKT_SIZE(icframe_vercnt, icmsg_vercnt) \
+       (ICMSG_HDR + offsetof(struct icmsg_negotiate, icversion_data) + \
+        (((icframe_vercnt) + (icmsg_vercnt)) * sizeof(struct ic_version)))
+
 struct icmsg_negotiate {
        u16 icframe_vercnt;
        u16 icmsg_vercnt;
@@ -1578,7 +1585,7 @@ struct hyperv_service_callback {
 };
 
 #define MAX_SRV_VER    0x7ffffff
-extern bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, u8 *buf,
+extern bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, u8 *buf, u32 buflen,
                                const int *fw_version, int fw_vercnt,
                                const int *srv_version, int srv_vercnt,
                                int *nego_fw_version, int *nego_srv_version);