public inbox for linux-input@vger.kernel.org
 help / color / mirror / Atom feed
From: Nikita Zhandarovich <n.zhandarovich@fintech.ru>
To: Terry Junge <linuxhid@cosmicgizmosystems.com>,
	Kees Cook <kees@kernel.org>
Cc: Jiri Kosina <jikos@kernel.org>,
	Alan Stern <stern@rowland.harvard.edu>,
	Benjamin Tissoires <bentiss@kernel.org>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	"Gustavo A. R. Silva" <gustavoars@kernel.org>,
	<linux-usb@vger.kernel.org>, <linux-input@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>, <linux-hardening@vger.kernel.org>,
	syzbot <syzbot+c52569baf0c843f35495@syzkaller.appspotmail.com>,
	<syzkaller-bugs@googlegroups.com>, <lvc-project@linuxtesting.org>
Subject: Re: [PATCH v2] HID: usbhid: fix recurrent out-of-bounds bug in usbhid_parse()
Date: Thu, 6 Feb 2025 17:44:37 +0300	[thread overview]
Message-ID: <f9bd3a41-0cf7-45f1-ac66-12f3047f5e9a@fintech.ru> (raw)
In-Reply-To: <dd0d2992-ed72-4f9f-b0f6-8b8ff1d9b377@cosmicgizmosystems.com>



On 2/6/25 06:02, Terry Junge wrote:
> On 2/2/25 1:55 AM, Nikita Zhandarovich wrote:
>>
>>
>> On 1/31/25 23:12, Kees Cook wrote:
>>> On Fri, Jan 31, 2025 at 06:15:58PM +0300, Nikita Zhandarovich wrote:
>>>> Syzbot reports [1] a reemerging out-of-bounds bug regarding hid
>>>> descriptors supposedly having unpredictable bNumDescriptors values in
>>>> usbhid_parse().
>>>>
>>>> The issue stems from the fact that hid_class_descriptor is supposed
>>>> to be a flexible array, however it was sized as desc[1], using only
>>>> one element. Therefore, going beyond 1 element, courtesy of
>>>> bNumDescriptors, may lead to an error.
>>>>
>>>> Modify struct hid_descriptor by employing __counted_by macro, tying
>>>> together struct hid_class_descriptor desc[] and number of descriptors
>>>> bNumDescriptors. Also, fix places where this change affects work with
>>>> newly updated struct.
>>>>
>>>> [1] Syzbot report:
>>>>
>>>> UBSAN: array-index-out-of-bounds in drivers/hid/usbhid/hid-core.c:1024:7
>>>> index 1 is out of range for type 'struct hid_class_descriptor[1]'
>>>> ...
>>>> Workqueue: usb_hub_wq hub_event
>>>> Call Trace:
>>>>  <TASK>
>>>>  __dump_stack lib/dump_stack.c:88 [inline]
>>>>  dump_stack_lvl+0x241/0x360 lib/dump_stack.c:114
>>>>  ubsan_epilogue lib/ubsan.c:231 [inline]
>>>>  __ubsan_handle_out_of_bounds+0x121/0x150 lib/ubsan.c:429
>>>>  usbhid_parse+0x5a7/0xc80 drivers/hid/usbhid/hid-core.c:1024
>>>>  hid_add_device+0x132/0x520 drivers/hid/hid-core.c:2790
>>>>  usbhid_probe+0xb38/0xea0 drivers/hid/usbhid/hid-core.c:1429
>>>>  usb_probe_interface+0x645/0xbb0 drivers/usb/core/driver.c:399
>>>>  really_probe+0x2b8/0xad0 drivers/base/dd.c:656
>>>>  __driver_probe_device+0x1a2/0x390 drivers/base/dd.c:798
>>>>  driver_probe_device+0x50/0x430 drivers/base/dd.c:828
>>>>  __device_attach_driver+0x2d6/0x530 drivers/base/dd.c:956
>>>>  bus_for_each_drv+0x24e/0x2e0 drivers/base/bus.c:457
>>>>  __device_attach+0x333/0x520 drivers/base/dd.c:1028
>>>>  bus_probe_device+0x189/0x260 drivers/base/bus.c:532
>>>>  device_add+0x8ff/0xca0 drivers/base/core.c:3720
>>>>  usb_set_configuration+0x1976/0x1fb0 drivers/usb/core/message.c:2210
>>>>  usb_generic_driver_probe+0x88/0x140 drivers/usb/core/generic.c:254
>>>>  usb_probe_device+0x1b8/0x380 drivers/usb/core/driver.c:294
>>>>
>>>> Reported-by: syzbot+c52569baf0c843f35495@syzkaller.appspotmail.com
>>>> Closes: https://syzkaller.appspot.com/bug?extid=c52569baf0c843f35495
>>>> Fixes: f043bfc98c19 ("HID: usbhid: fix out-of-bounds bug")
>>>> Cc: stable@vger.kernel.org
>>>> Signed-off-by: Nikita Zhandarovich <n.zhandarovich@fintech.ru>
>>>> ---
>>>> v1: https://lore.kernel.org/all/20240524120112.28076-1-n.zhandarovich@fintech.ru/
>>>>
>>>> v2: Instead of essentially forcing usbhid_parse() to only check
>>>> the first descriptor, modify hid_descriptor struct to anticipate
>>>> multiple hid_class_descriptor in desc[] by utilizing __counted_by
>>>> macro. This change, in turn, requires several other ones wherever
>>>> that struct is used. Adjust commit description accordingly.
>>>>
>>>> P.S. Since syzbot currently breaks trying to reproduce the issue,
>>>> with or without this patch, I only managed to test it locally with
>>>> syz repros. Would greatly appreciate other people's effort to test
>>>> it as well.
>>>>
>>>> P.P.S. Terry Junge <linuxhid@cosmicgizmosystems.com> suggested a
>>>> different approach to this issue, see:
>>>> Link: https://lore.kernel.org/all/f7963a1d-e069-4ec9-82a1-e17fd324a44f@cosmicgizmosystems.com/
>>>>
>>>>  drivers/hid/usbhid/hid-core.c       |  2 +-
>>>>  drivers/usb/gadget/function/f_fs.c  |  3 ++-
>>>>  drivers/usb/gadget/function/f_hid.c | 22 ++++++++++++++--------
>>>>  include/linux/hid.h                 |  2 +-
>>>>  4 files changed, 18 insertions(+), 11 deletions(-)
>>>>
>>>> diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
>>>> index a6eb6fe6130d..eb4807785d6d 100644
>>>> --- a/drivers/hid/usbhid/hid-core.c
>>>> +++ b/drivers/hid/usbhid/hid-core.c
>>>> @@ -1010,7 +1010,7 @@ static int usbhid_parse(struct hid_device *hid)
>>>>  		return -ENODEV;
>>>>  	}
>>>>  
>>>> -	if (hdesc->bLength < sizeof(struct hid_descriptor)) {
>>>> +	if (hdesc->bLength < struct_size(hdesc, desc, hdesc->bNumDescriptors)) {
>>>>  		dbg_hid("hid descriptor is too short\n");
>>>>  		return -EINVAL;
>>>>  	}
>>>
>>> I don't think you want this hunk in the patch. The existing logic will
>>> correctly adapt num_descriptors to a size that fits within
>>> hdesc->bLength. In theory, the above change could break a weird but
>>> working device that reported too high bNumDescriptors but still worked
>>> with what num_descriptors walks.
>>>
>>
>> Thank you for mentioning this and you are right about possibly breaking
>> a working device.
>>
>> However, sizeof(struct hid_descriptor) doesn't count flex array sizes.
>> So, leaving this check as is will miss cases when hdesc->bLength >=
>> sizeof(struct hid_descriptor) but short enough to have a desc[0]
>> element. Maybe doing struct_size(hdesc, desc, 1) is better? Then we
>> make sure that at least one mandatory hid_class_desriptor is there.
>>
>>>> diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
>>>> index 2dea9e42a0f8..a4b6d7cbf56d 100644
>>>> --- a/drivers/usb/gadget/function/f_fs.c
>>>> +++ b/drivers/usb/gadget/function/f_fs.c
>>>> @@ -2550,7 +2550,8 @@ static int __must_check ffs_do_single_desc(char *data, unsigned len,
>>>>  	case USB_TYPE_CLASS | 0x01:
>>>>  		if (*current_class == USB_INTERFACE_CLASS_HID) {
>>>>  			pr_vdebug("hid descriptor\n");
>>>> -			if (length != sizeof(struct hid_descriptor))
>>>> +			if (length < sizeof(struct hid_descriptor) +
>>>> +				     sizeof(struct hid_class_descriptor))
> 
> Don't change the logic of the if statement in this function, leave it as !=
> if it failed before this change it should still fail with this change.
> 

Terry, that's a good point. However, while it's more than possible that
I missed something, I nonetheless failed to find proof that the
descriptors expected in ffs_do_single_desc() only have 1 element of
hid_class_descriptor. If we do != instead of <, we will discard any
descriptors with multiple instances of hid_class_descriptor.

If you still think that it's better your way, I don't mind sending v3.

>>>>  				goto inv_length;
>>>>  			break;
>>>>  		} else if (*current_class == USB_INTERFACE_CLASS_CCID) {
>>>
>>> Same here, I think? This isn't needed unless I'm misunderstanding
>>> something about the fix.
>>
>> Once again, sizeof(struct hid_descriptor) will not count struct
>> hid_class_descriptor desc[] __counted_by(...) so even correct and
>> predictable lengths will fail the check. We need to test length against
>> hid_descriptor with at least one element of its flex array.
>>
>> I would prefer struct_size() here as well but it's not optimal in this
>> case.
>>>
>>>> diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
>>>> index 740311c4fa24..ec8c2e2d6812 100644
>>>> --- a/drivers/usb/gadget/function/f_hid.c
>>>> +++ b/drivers/usb/gadget/function/f_hid.c
>>>> @@ -139,13 +139,17 @@ static struct usb_interface_descriptor hidg_interface_desc = {
>>>>  };
>>>>  
>>>>  static struct hid_descriptor hidg_desc = {
>>>> -	.bLength			= sizeof hidg_desc,
>>>> +	.bLength			= struct_size(&hidg_desc, desc, 1),
>>>>  	.bDescriptorType		= HID_DT_HID,
>>>>  	.bcdHID				= cpu_to_le16(0x0101),
>>>>  	.bCountryCode			= 0x00,
>>>>  	.bNumDescriptors		= 0x1,
>>>> -	/*.desc[0].bDescriptorType	= DYNAMIC */
>>>> -	/*.desc[0].wDescriptorLenght	= DYNAMIC */
>>>> +	.desc				= {
>>>> +		{
>>>> +			.bDescriptorType	= 0, /* DYNAMIC */
>>>> +			.wDescriptorLength	= 0, /* DYNAMIC */
>>>> +		}
>>>> +	}
>>>>  };
>>>>  
>>>>  /* Super-Speed Support */
>>>> @@ -936,16 +940,18 @@ static int hidg_setup(struct usb_function *f,
>>>>  		switch (value >> 8) {
>>>>  		case HID_DT_HID:
>>>>  		{
>>>> -			struct hid_descriptor hidg_desc_copy = hidg_desc;
>>>> +			DEFINE_FLEX(struct hid_descriptor, hidg_desc_copy,
>>>> +				desc, bNumDescriptors, 1);
>>>> +			*hidg_desc_copy = hidg_desc;
>>>>  
>>>>  			VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n");
>>>> -			hidg_desc_copy.desc[0].bDescriptorType = HID_DT_REPORT;
>>>> -			hidg_desc_copy.desc[0].wDescriptorLength =
>>>> +			hidg_desc_copy->desc[0].bDescriptorType = HID_DT_REPORT;
>>>> +			hidg_desc_copy->desc[0].wDescriptorLength =
>>>>  				cpu_to_le16(hidg->report_desc_length);
>>>>  
>>>>  			length = min_t(unsigned short, length,
>>>> -						   hidg_desc_copy.bLength);
>>>> -			memcpy(req->buf, &hidg_desc_copy, length);
>>>> +						   hidg_desc_copy->bLength);
>>>> +			memcpy(req->buf, hidg_desc_copy, length);
>>>>  			goto respond;
>>>>  			break;
>>>>  		}
>>>> diff --git a/include/linux/hid.h b/include/linux/hid.h
>>>> index cdc0dc13c87f..85a58ae2c4a0 100644
>>>> --- a/include/linux/hid.h
>>>> +++ b/include/linux/hid.h
>>>> @@ -739,7 +739,7 @@ struct hid_descriptor {
>>>>  	__u8  bCountryCode;
>>>>  	__u8  bNumDescriptors;
>>>>  
>>>> -	struct hid_class_descriptor desc[1];
>>>> +	struct hid_class_descriptor desc[] __counted_by(bNumDescriptors);
>>>>  } __attribute__ ((packed));
>>>>  
>>>>  #define HID_DEVICE(b, g, ven, prod)					\
>>>
>>> Otherwise, this looks correct to me.
>>>
>>
>> Regards,
>> Nikita
>>
> 
> In drivers/hid/hid-hyperv.c there are additional struct hid_descriptor references
> 
> struct synthhid_device_info {
> 	struct synthhid_msg_hdr header;
> 	struct hv_input_dev_info hid_dev_info;
> 	struct hid_descriptor hid_descriptor;
> };
> 
> Have you checked that there are no changes necessary in that file due to the
> size change of struct hid_descriptor?
> 
> Regards,
> Terry
> 

I have checked drivers/hid/hid-hyperv.c and to my knowledge changes to
hid_descriptor should not adversely affect dependent structs. However,
as my current testing environment is broken, I was selfishly counting
on maintainers' tests in that regard, as they are more extensive, just
in case.

Regards,
Nikita


      reply	other threads:[~2025-02-06 14:44 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-31 15:15 [PATCH v2] HID: usbhid: fix recurrent out-of-bounds bug in usbhid_parse() Nikita Zhandarovich
2025-01-31 20:12 ` Kees Cook
2025-02-02  9:55   ` Nikita Zhandarovich
2025-02-06  3:02     ` Terry Junge
2025-02-06 14:44       ` Nikita Zhandarovich [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f9bd3a41-0cf7-45f1-ac66-12f3047f5e9a@fintech.ru \
    --to=n.zhandarovich@fintech.ru \
    --cc=bentiss@kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=gustavoars@kernel.org \
    --cc=jikos@kernel.org \
    --cc=kees@kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=linuxhid@cosmicgizmosystems.com \
    --cc=lvc-project@linuxtesting.org \
    --cc=stern@rowland.harvard.edu \
    --cc=syzbot+c52569baf0c843f35495@syzkaller.appspotmail.com \
    --cc=syzkaller-bugs@googlegroups.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox