From: Berkant Koc <[email protected]> Sent: Tuesday, May 19, 2026 1:09 PM
> 
> hyperv_receive_sub() reads msg->vid_hdr.type and dispatches into one
> of four message-type branches without knowing how many bytes the host
> wrote into hv->recv_buf. The completion path then runs
> memcpy(hv->init_buf, msg, VMBUS_MAX_PACKET_SIZE), so the consumer that
> wakes on wait_for_completion_timeout() can read up to 16 KiB of
> residue from a prior message as if it were the response payload.
> 
> Pass bytes_recvd into hyperv_receive_sub() and reject any packet that
> does not cover the pipe + synthvid header. For the three
> completion-driving types (SYNTHVID_VERSION_RESPONSE,
> SYNTHVID_RESOLUTION_RESPONSE, SYNTHVID_VRAM_LOCATION_ACK) require the
> type-specific payload before memcpy/complete, and apply the same rule
> to SYNTHVID_FEATURE_CHANGE before reading is_dirt_needed.
> 
> SYNTHVID_RESOLUTION_RESPONSE is variable length: the host fills
> resolution_count entries, not the full SYNTHVID_MAX_RESOLUTION_COUNT
> array. Validate the fixed prefix first so resolution_count can be
> read, bound it against the array, then require only the count-sized
> array, so the shorter responses the host actually sends are accepted.
> 
> Only run the sub-handler when vmbus_recvpacket() returned success. The
> memcpy length is bytes_recvd, which is bounded by VMBUS_MAX_PACKET_SIZE
> only on a successful receive; on -ENOBUFS vmbus_recvpacket() instead
> reports the required length, which can exceed hv->recv_buf, so copying
> bytes_recvd would read and write past the 16 KiB buffers. Gating on the
> success return keeps the copy bounded.
> 
> Rejected packets are reported via drm_err_ratelimited() rather than
> silently dropped, matching the CoCo-hardened pattern in
> hv_kvp_onchannelcallback().
> 
> Fixes: 76c56a5affeb ("drm/hyperv: Add DRM driver for hyperv synthetic video 
> device")
> Cc: [email protected] # 5.14+
> Signed-off-by: Berkant Koc <[email protected]>
> Assisted-by: Claude:claude-opus-4-7 berkoc-pipeline
> ---
>  drivers/gpu/drm/hyperv/hyperv_drm_proto.c | 63 +++++++++++++++++++++--
>  1 file changed, 59 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> index c3d0ff229..48054b607 100644
> --- a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> +++ b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c
> @@ -420,26 +420,81 @@ static int hyperv_get_supported_resolution(struct 
> hv_device *hdev)
>       return 0;
>  }
> 
> -static void hyperv_receive_sub(struct hv_device *hdev)
> +static void hyperv_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
>  {
>       struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
>       struct synthvid_msg *msg;
> +     size_t hdr_size;
> 
>       if (!hv)
>               return;
> 
> +     hdr_size = sizeof(struct pipe_msg_hdr) +
> +                sizeof(struct synthvid_msg_hdr);
> +     if (bytes_recvd < hdr_size) {
> +             drm_err_ratelimited(&hv->dev,
> +                                 "synthvid packet too small for header: 
> %u\n",
> +                                 bytes_recvd);
> +             return;
> +     }
> +
>       msg = (struct synthvid_msg *)hv->recv_buf;
> 
>       /* Complete the wait event */
>       if (msg->vid_hdr.type == SYNTHVID_VERSION_RESPONSE ||
>           msg->vid_hdr.type == SYNTHVID_RESOLUTION_RESPONSE ||
>           msg->vid_hdr.type == SYNTHVID_VRAM_LOCATION_ACK) {
> -             memcpy(hv->init_buf, msg, VMBUS_MAX_PACKET_SIZE);
> +             size_t need = hdr_size;
> +
> +             switch (msg->vid_hdr.type) {

Having the combination of the above "if" tests followed by the
switch statement on the same value is logical duplication that
suggests to me that some code reorganization is appropriate.
Consider the following approach:

1) The drop the big "if" statement and make the switch and cases
be the main decision point. Include SYNTHVID_FEATURE_CHANGE
as a case in the switch statement.

2) The check against "need" followed by the memcpy() and complete()
are used by the original three cases in the switch. Make that the normal
exit path for the function so that the "break" statements for those
three cases still do what you want.

3) For the SYNTHVID_FEATURE_CHANGE case, just do a "return"
when done since that case doesn't want the check against "need" or 
the memcpy()/complete() operations.

I haven't coded this up, but I think it should be cleaner and be fewer
lines of code.

> +             case SYNTHVID_VERSION_RESPONSE:
> +                     need += sizeof(struct synthvid_version_resp);
> +                     break;
> +             case SYNTHVID_RESOLUTION_RESPONSE:
> +                     /*
> +                      * The resolution response is variable length: the host
> +                      * fills resolution_count entries, not the full
> +                      * SYNTHVID_MAX_RESOLUTION_COUNT array. Require the 
> fixed
> +                      * prefix first so resolution_count can be read, then
> +                      * demand exactly the count-sized array.
> +                      */
> +                     need += offsetof(struct 
> synthvid_supported_resolution_resp,
> +                                      supported_resolution);
> +                     if (bytes_recvd < need)
> +                             break;
> +                     if (msg->resolution_resp.resolution_count >
> +                         SYNTHVID_MAX_RESOLUTION_COUNT) {
> +                             drm_err_ratelimited(&hv->dev,
> +                                                 "synthvid resolution count 
> too large: %u\n",
> +                                                 
> msg->resolution_resp.resolution_count);
> +                             return;
> +                     }
> +                     need += msg->resolution_resp.resolution_count *
> +                             sizeof(struct hvd_screen_info);
> +                     break;
> +             case SYNTHVID_VRAM_LOCATION_ACK:
> +                     need += sizeof(struct synthvid_vram_location_ack);
> +                     break;
> +             }
> +             if (bytes_recvd < need) {
> +                     drm_err_ratelimited(&hv->dev,
> +                                         "synthvid packet too small for type 
> %u: %u < %zu\n",
> +                                         msg->vid_hdr.type, bytes_recvd, 
> need);
> +                     return;
> +             }
> +             memcpy(hv->init_buf, msg, bytes_recvd);
>               complete(&hv->wait);
>               return;
>       }
> 
>       if (msg->vid_hdr.type == SYNTHVID_FEATURE_CHANGE) {
> +             if (bytes_recvd < hdr_size +
> +                 sizeof(struct synthvid_feature_change)) {
> +                     drm_err_ratelimited(&hv->dev,
> +                                         "synthvid feature change packet too 
> small: %u\n",
> +                                         bytes_recvd);
> +                     return;
> +             }
>               hv->dirt_needed = msg->feature_chg.is_dirt_needed;
>               if (hv->dirt_needed)
>                       hyperv_hide_hw_ptr(hv->hdev);
> @@ -464,9 +519,9 @@ static void hyperv_receive(void *ctx)
>               ret = vmbus_recvpacket(hdev->channel, recv_buf,
>                                      VMBUS_MAX_PACKET_SIZE,
>                                      &bytes_recvd, &req_id);
> -             if (bytes_recvd > 0 &&
> +             if (!ret && bytes_recvd > 0 &&

This patch is all about detecting malformed messages from Hyper-V, 
and the ret != 0 case is another example of a malformed message because
the message is too big.  Since malformed messages are no longer being
silently ignored, output a rate limited error message in that case, just
like in the other malformed message cases.

As Sashiko pointed out, a message that's too big will block receipt of any
further messages on this channel, but I don't think it's worth trying to
code any kind of recovery. The channel is broken, presumably due to
some bug in Hyper-V. The only recovery is for a sysadmin to manually
unbind the driver from the synthetic device (which should close the
channel), then manually rebind and try to start over again. And then
report the problem to the Hyper-V team. :-)

>                   recv_buf->pipe_hdr.type == PIPE_MSG_DATA)
> -                     hyperv_receive_sub(hdev);
> +                     hyperv_receive_sub(hdev, bytes_recvd);
>       } while (bytes_recvd > 0 && ret == 0);
>  }
> 
> --
> 2.47.3
> 


Reply via email to