public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] HID: Fix some memory leaks in drivers/hid
@ 2026-02-17 16:01 Günther Noack
  2026-02-17 16:01 ` [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup() Günther Noack
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Günther Noack @ 2026-02-17 16:01 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Benjamin Tissoires, linux-input, linux-kernel, Günther Noack

Hello!

These patches fix a few memory leaks in HID report descriptor fixups.

FWIW, a good ad-hoc way to look for usages of allocation functions in
these is:

  awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c \
    | grep -E '(malloc|kzalloc|kcalloc|kmemdup)'

The devm_* variants are safe in this context, because they tie the
allocated memory to the lifetime of the driver.

For transparency, I generated these commits with Gemini-CLI,
starting with this prompt:

    We are working in the Linux kernel. In the HID drivers in
    `drivers/hid/hid-*.c`, the `report_fixup` driver hook is a function
    that gets a byte buffer (with size) as input and that may modify that
    byte buffer, and optionally return a pointer to a new byte buffer and
    update the size.  The returned value is *not* memory-managed by the
    caller though and will not be freed subsequently.  When the
    `report_fixup` implementation allocates a new buffer and returns that,
    that will not get freed by the caller.  Validate this assessment and
    fix up all HID drivers where that mistake is made.

(and then a little bit of additional nudging for the details).

—Günther


Günther Noack (3):
  HID: apple: avoid memory leak in apple_report_fixup()
  HID: magicmouse: avoid memory leak in magicmouse_report_fixup()
  HID: asus: avoid memory leak in asus_report_fixup()

 drivers/hid/hid-apple.c      |  4 +---
 drivers/hid/hid-asus.c       | 15 +++++++++++----
 drivers/hid/hid-magicmouse.c |  4 +---
 3 files changed, 13 insertions(+), 10 deletions(-)

-- 
2.53.0.335.g19a08e0c02-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
  2026-02-17 16:01 [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Günther Noack
@ 2026-02-17 16:01 ` Günther Noack
  2026-02-17 18:22   ` Benjamin Tissoires
  2026-02-17 16:01 ` [PATCH 2/3] HID: magicmouse: avoid memory leak in magicmouse_report_fixup() Günther Noack
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Günther Noack @ 2026-02-17 16:01 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Benjamin Tissoires, linux-input, linux-kernel, Günther Noack

The apple_report_fixup() function was allocating a new buffer with
kmemdup() but never freeing it. Since the caller of report_fixup() already
provides a writable buffer and allows returning a pointer within that
buffer, we can just modify the descriptor in-place and return the adjusted
pointer.

Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Günther Noack <gnoack@google.com>
---
 drivers/hid/hid-apple.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 233e367cce1d..894adc23367b 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		hid_info(hdev,
 			 "fixing up Magic Keyboard battery report descriptor\n");
 		*rsize = *rsize - 1;
-		rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
-		if (!rdesc)
-			return NULL;
+		rdesc = rdesc + 1;
 
 		rdesc[0] = 0x05;
 		rdesc[1] = 0x01;
-- 
2.53.0.335.g19a08e0c02-goog


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 2/3] HID: magicmouse: avoid memory leak in magicmouse_report_fixup()
  2026-02-17 16:01 [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Günther Noack
  2026-02-17 16:01 ` [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup() Günther Noack
@ 2026-02-17 16:01 ` Günther Noack
  2026-02-17 16:01 ` [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup() Günther Noack
  2026-02-17 18:36 ` [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Benjamin Tissoires
  3 siblings, 0 replies; 12+ messages in thread
From: Günther Noack @ 2026-02-17 16:01 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Benjamin Tissoires, linux-input, linux-kernel, Günther Noack

The magicmouse_report_fixup() function was allocating a new buffer with
kmemdup() but never freeing it. Since the caller already provides a
writable buffer and allows returning a pointer within it, modify the
descriptor in-place and return the adjusted pointer.

Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Günther Noack <gnoack@google.com>
---
 drivers/hid/hid-magicmouse.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 91f621ceb924..17908d52c027 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -994,9 +994,7 @@ static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		hid_info(hdev,
 			 "fixing up magicmouse battery report descriptor\n");
 		*rsize = *rsize - 1;
-		rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
-		if (!rdesc)
-			return NULL;
+		rdesc = rdesc + 1;
 
 		rdesc[0] = 0x05;
 		rdesc[1] = 0x01;
-- 
2.53.0.335.g19a08e0c02-goog


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup()
  2026-02-17 16:01 [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Günther Noack
  2026-02-17 16:01 ` [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup() Günther Noack
  2026-02-17 16:01 ` [PATCH 2/3] HID: magicmouse: avoid memory leak in magicmouse_report_fixup() Günther Noack
@ 2026-02-17 16:01 ` Günther Noack
  2026-02-17 18:31   ` Benjamin Tissoires
  2026-02-17 18:36 ` [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Benjamin Tissoires
  3 siblings, 1 reply; 12+ messages in thread
From: Günther Noack @ 2026-02-17 16:01 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Benjamin Tissoires, linux-input, linux-kernel, Günther Noack

The asus_report_fixup() function was allocating a new buffer with kmemdup()
when growing the report descriptor but never freeing it.  Switch to
devm_kzalloc() to ensure the memory is managed and freed automatically when
the device is removed.

Also fix a harmless out-of-bounds read by copying only the original
descriptor size.

Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Günther Noack <gnoack@google.com>
---
 drivers/hid/hid-asus.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 8ffcd12038e8..7a08e964b9cc 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1399,14 +1399,21 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		 */
 		if (*rsize == rsize_orig &&
 			rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
-			*rsize = rsize_orig + 1;
-			rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
-			if (!rdesc)
-				return NULL;
+			__u8 *new_rdesc;
+
+			new_rdesc = devm_kzalloc(&hdev->dev, rsize_orig + 1,
+						 GFP_KERNEL);
+			if (!new_rdesc)
+				return rdesc;
 
 			hid_info(hdev, "Fixing up %s keyb report descriptor\n",
 				drvdata->quirks & QUIRK_T100CHI ?
 				"T100CHI" : "T90CHI");
+
+			memcpy(new_rdesc, rdesc, rsize_orig);
+			*rsize = rsize_orig + 1;
+			rdesc = new_rdesc;
+
 			memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
 			rdesc[offs] = 0x19;
 			rdesc[offs + 1] = 0x00;
-- 
2.53.0.335.g19a08e0c02-goog


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
  2026-02-17 16:01 ` [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup() Günther Noack
@ 2026-02-17 18:22   ` Benjamin Tissoires
  2026-02-17 19:42     ` Günther Noack
  0 siblings, 1 reply; 12+ messages in thread
From: Benjamin Tissoires @ 2026-02-17 18:22 UTC (permalink / raw)
  To: Günther Noack; +Cc: Jiri Kosina, linux-input, linux-kernel

On Feb 17 2026, Günther Noack wrote:
> The apple_report_fixup() function was allocating a new buffer with
> kmemdup() but never freeing it. Since the caller of report_fixup() already
> provides a writable buffer and allows returning a pointer within that
> buffer, we can just modify the descriptor in-place and return the adjusted
> pointer.
> 
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Günther Noack <gnoack@google.com>
> ---
>  drivers/hid/hid-apple.c | 4 +---
>  1 file changed, 1 insertion(+), 3 deletions(-)
> 
> diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> index 233e367cce1d..894adc23367b 100644
> --- a/drivers/hid/hid-apple.c
> +++ b/drivers/hid/hid-apple.c
> @@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
>  		hid_info(hdev,
>  			 "fixing up Magic Keyboard battery report descriptor\n");
>  		*rsize = *rsize - 1;
> -		rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
> -		if (!rdesc)
> -			return NULL;
> +		rdesc = rdesc + 1;

I might be wrong, but later we call free(dev->rdesc) on device removal.
AFAICT, incrementing the pointer is undefined behavior.

What we should do instead is probably a krealloc instead of a kmemdup.

Same for all 3 patches.

Cheers,
Benjamin

>  
>  		rdesc[0] = 0x05;
>  		rdesc[1] = 0x01;
> -- 
> 2.53.0.335.g19a08e0c02-goog
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup()
  2026-02-17 16:01 ` [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup() Günther Noack
@ 2026-02-17 18:31   ` Benjamin Tissoires
  2026-02-17 19:51     ` Günther Noack
  0 siblings, 1 reply; 12+ messages in thread
From: Benjamin Tissoires @ 2026-02-17 18:31 UTC (permalink / raw)
  To: Günther Noack; +Cc: Jiri Kosina, linux-input, linux-kernel

On Feb 17 2026, Günther Noack wrote:
> The asus_report_fixup() function was allocating a new buffer with kmemdup()
> when growing the report descriptor but never freeing it.  Switch to
> devm_kzalloc() to ensure the memory is managed and freed automatically when
> the device is removed.

Actually this one is even worse: you can't use devm_kzalloc because
hid-core.c will later call kfree(dev->rdesc) if dev->rdesc is different
from the one provided by the low level driver. So we are going to have
a double free.

I really wonder if this was ever tested.

Cheers,
Benjamin

> 
> Also fix a harmless out-of-bounds read by copying only the original
> descriptor size.
> 
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Günther Noack <gnoack@google.com>
> ---
>  drivers/hid/hid-asus.c | 15 +++++++++++----
>  1 file changed, 11 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 8ffcd12038e8..7a08e964b9cc 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -1399,14 +1399,21 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
>  		 */
>  		if (*rsize == rsize_orig &&
>  			rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
> -			*rsize = rsize_orig + 1;
> -			rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
> -			if (!rdesc)
> -				return NULL;
> +			__u8 *new_rdesc;
> +
> +			new_rdesc = devm_kzalloc(&hdev->dev, rsize_orig + 1,
> +						 GFP_KERNEL);
> +			if (!new_rdesc)
> +				return rdesc;
>  
>  			hid_info(hdev, "Fixing up %s keyb report descriptor\n",
>  				drvdata->quirks & QUIRK_T100CHI ?
>  				"T100CHI" : "T90CHI");
> +
> +			memcpy(new_rdesc, rdesc, rsize_orig);
> +			*rsize = rsize_orig + 1;
> +			rdesc = new_rdesc;
> +
>  			memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
>  			rdesc[offs] = 0x19;
>  			rdesc[offs + 1] = 0x00;
> -- 
> 2.53.0.335.g19a08e0c02-goog
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/3] HID: Fix some memory leaks in drivers/hid
  2026-02-17 16:01 [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Günther Noack
                   ` (2 preceding siblings ...)
  2026-02-17 16:01 ` [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup() Günther Noack
@ 2026-02-17 18:36 ` Benjamin Tissoires
  2026-02-17 20:08   ` Günther Noack
  3 siblings, 1 reply; 12+ messages in thread
From: Benjamin Tissoires @ 2026-02-17 18:36 UTC (permalink / raw)
  To: Günther Noack; +Cc: Jiri Kosina, linux-input, linux-kernel

On Feb 17 2026, Günther Noack wrote:
> Hello!
> 
> These patches fix a few memory leaks in HID report descriptor fixups.
> 
> FWIW, a good ad-hoc way to look for usages of allocation functions in
> these is:
> 
>   awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c \
>     | grep -E '(malloc|kzalloc|kcalloc|kmemdup)'
> 
> The devm_* variants are safe in this context, because they tie the
> allocated memory to the lifetime of the driver.

No. Look at hid_close_report() in drivers/hid/hid-core.c.

HID still hasn't fully migrated to devm, so as a rule of thumb, if you
change a kzalloc into a devm_kzalloc, you are getting into troubles
unless you fix the all the kfree path.

> 
> For transparency, I generated these commits with Gemini-CLI,
> starting with this prompt:
> 
>     We are working in the Linux kernel. In the HID drivers in
>     `drivers/hid/hid-*.c`, the `report_fixup` driver hook is a function
>     that gets a byte buffer (with size) as input and that may modify that
>     byte buffer, and optionally return a pointer to a new byte buffer and
>     update the size.  The returned value is *not* memory-managed by the
>     caller though and will not be freed subsequently.  When the

If the memory is *not* managed, why would gemini converts kzalloc into
devm variants without changing the kfree paths????

>     `report_fixup` implementation allocates a new buffer and returns that,
>     that will not get freed by the caller.  

This is wrong. See hid_close_report(): if the new rdesc (after fixup)
differs from the one initially set, there is an explicit call to
kfree().

-> there is no memleak AFAICT, and your prompt is wrong.

Cheers,
Benjamin

> Validate this assessment and
>     fix up all HID drivers where that mistake is made.
> 
> (and then a little bit of additional nudging for the details).
> 
> —Günther
> 
> 
> Günther Noack (3):
>   HID: apple: avoid memory leak in apple_report_fixup()
>   HID: magicmouse: avoid memory leak in magicmouse_report_fixup()
>   HID: asus: avoid memory leak in asus_report_fixup()
> 
>  drivers/hid/hid-apple.c      |  4 +---
>  drivers/hid/hid-asus.c       | 15 +++++++++++----
>  drivers/hid/hid-magicmouse.c |  4 +---
>  3 files changed, 13 insertions(+), 10 deletions(-)
> 
> -- 
> 2.53.0.335.g19a08e0c02-goog
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
  2026-02-17 18:22   ` Benjamin Tissoires
@ 2026-02-17 19:42     ` Günther Noack
  2026-02-18 19:04       ` Benjamin Tissoires
  0 siblings, 1 reply; 12+ messages in thread
From: Günther Noack @ 2026-02-17 19:42 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel

Hello Benjamin!

On Tue, Feb 17, 2026 at 07:22:20PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > The apple_report_fixup() function was allocating a new buffer with
> > kmemdup() but never freeing it. Since the caller of report_fixup() already
> > provides a writable buffer and allows returning a pointer within that
> > buffer, we can just modify the descriptor in-place and return the adjusted
> > pointer.
> > 
> > Assisted-by: Gemini-CLI:Google Gemini 3
> > Signed-off-by: Günther Noack <gnoack@google.com>
> > ---
> >  drivers/hid/hid-apple.c | 4 +---
> >  1 file changed, 1 insertion(+), 3 deletions(-)
> > 
> > diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> > index 233e367cce1d..894adc23367b 100644
> > --- a/drivers/hid/hid-apple.c
> > +++ b/drivers/hid/hid-apple.c
> > @@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> >  		hid_info(hdev,
> >  			 "fixing up Magic Keyboard battery report descriptor\n");
> >  		*rsize = *rsize - 1;
> > -		rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
> > -		if (!rdesc)
> > -			return NULL;
> > +		rdesc = rdesc + 1;
> 
> I might be wrong, but later we call free(dev->rdesc) on device removal.
> AFAICT, incrementing the pointer is undefined behavior.
> 
> What we should do instead is probably a krealloc instead of a kmemdup.
> 
> Same for all 3 patches.

Thanks for the review.

Let me try to address your three responses in one reply.

I am happy to accept it if I am wrong about this, but I don't
understand your reasoning.  (This should go without saying, but maybe
is worth reiterating, I would not have sent this if I had not
convinced myself independently of LLM-assisted reasoning.)

Let me explain my reasoning based on the place where .report_fixup()
is called from, which is hid_open_report() in hid-core.c:


	start = device->bpf_rdesc;                         /* (1) */
	if (WARN_ON(!start))
		return -ENODEV;
	size = device->bpf_rsize;

	if (device->driver->report_fixup) {
		/*
		 * device->driver->report_fixup() needs to work
		 * on a copy of our report descriptor so it can
		 * change it.
		 */
		__u8 *buf = kmemdup(start, size, GFP_KERNEL);   /* (2) */

		if (buf == NULL)
			return -ENOMEM;

		start = device->driver->report_fixup(device, buf, &size);  /* (3) */

		/*
		 * The second kmemdup is required in case report_fixup() returns
		 * a static read-only memory, but we have no idea if that memory
		 * needs to be cleaned up or not at the end.
		 */
		start = kmemdup(start, size, GFP_KERNEL);  /* (4) */
		kfree(buf);                                /* (5) */
		if (start == NULL)
			return -ENOMEM;
	}

	device->rdesc = start;
	device->rsize = size;


This function uses a slightly elaborate scheme to call .report_fixup:

(1) start is assigned to the original device->bpf_rdesc
(2) buf is assigned to a copy of the 'start' buffer (deallocated in (5)).
 .  It is done because buf is meant to be mutated by .report_fixup()
 .  (3) start = ...->report_fixup(..., buf, ...)
 .  (4) start = kmemdup(start, ...)
(5) deallocate buf

Importantly:

(a) The buffer buf passed to report_fixup() is a copy of the report
    descriptor whose lifetime spans only from (2) to (5).
(b) The result of .report_fixup(), start, is immediately discarded in
    (4) and reassigned to the start variable again.

From (b), we can see that the result of .report_fixup() does *not* get
deallocated by the caller, and thus, when the driver wants to return a
newly allocated array, is must also hold a reference to it so that it
can deallocate it later.

From (a), we can see that the report_fixup hook is free to manipulate
the contents of the buffer that it receives, but if we were to
*reallocate* it within report_fixup, as you are suggesting above, it
could become a double free:

* During realloc, the allocator would potentially have to move the
  buffer to a place where there is enough space, freeing up the old
  place and allocating a new place. [1]
* In (5), we would then pass the original (now deallocated) buf
  pointer to kfree, leading to a double free.

If I were to describe the current interface of the hook
.report_fixup(dev, rdesc, rsize), it would be:

* report_fixup may modify rdesc[0] to rdesc[rsize-1]
* report_fixup may *not* deallocate rdesc
  (ownership of rdesc stays with the caller)
  * specifically, it may also not reallocate rdesc
    (because that may imply a deallocation)
* report_fixup returns a pointer to a buffer for which it can
  guarantee that it lives long enough for the kmemdup in (4), but
  which will *not* be deallocated by the caller (see (b) above).  The
  three techniques I have found for that are:
  * returning a subsection of the rdesc that it received
  * returning a pointer into a statically allocated array
    (e.g. motion_fixup() and ps3remote_fixup() in hid-sony.c)
  * allocating it with a devm_*() function.  My understanding was
    that this ties it to the lifetime of the device.  (e.g. the
    QUIRK_G752_KEYBOARD case in hid-asus.c)

Honestly, I still think that this reasoning holds, but I am happy to
be convinced otherwise.  Please let me know what you think.

—Günther

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup()
  2026-02-17 18:31   ` Benjamin Tissoires
@ 2026-02-17 19:51     ` Günther Noack
  0 siblings, 0 replies; 12+ messages in thread
From: Günther Noack @ 2026-02-17 19:51 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel

Hello!

On Tue, Feb 17, 2026 at 07:31:23PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > The asus_report_fixup() function was allocating a new buffer with kmemdup()
> > when growing the report descriptor but never freeing it.  Switch to
> > devm_kzalloc() to ensure the memory is managed and freed automatically when
> > the device is removed.
> 
> Actually this one is even worse: you can't use devm_kzalloc because
> hid-core.c will later call kfree(dev->rdesc) if dev->rdesc is different
> from the one provided by the low level driver. So we are going to have
> a double free.

The buffer returned by report_fixup() is duplicated first before
hid-core stores it in dev->rdesc.  The pointer that report_fixup()
returns is not managed by the caller.

I elaborated in the response to the other patch in [1].  You can see
it in the source code in the position marked with (4).

[1] https://lore.kernel.org/all/aZTEnPEHcWEkoTJR@google.com/


> I really wonder if this was ever tested.

I only convinced myself by staring at the code, because I do not
happen to have the matching USB devices here.  What it your usual
approach to verifying such changes?  raw-gadget?

—Günther

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/3] HID: Fix some memory leaks in drivers/hid
  2026-02-17 18:36 ` [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Benjamin Tissoires
@ 2026-02-17 20:08   ` Günther Noack
  0 siblings, 0 replies; 12+ messages in thread
From: Günther Noack @ 2026-02-17 20:08 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel

Hello!

On Tue, Feb 17, 2026 at 07:36:46PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > These patches fix a few memory leaks in HID report descriptor fixups.
> > 
> > FWIW, a good ad-hoc way to look for usages of allocation functions in
> > these is:
> > 
> >   awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c \
> >     | grep -E '(malloc|kzalloc|kcalloc|kmemdup)'
> > 
> > The devm_* variants are safe in this context, because they tie the
> > allocated memory to the lifetime of the driver.
> 
> No. Look at hid_close_report() in drivers/hid/hid-core.c.
> 
> HID still hasn't fully migrated to devm, so as a rule of thumb, if you
> change a kzalloc into a devm_kzalloc, you are getting into troubles
> unless you fix the all the kfree path.

OK, I have not verified where the devm-allocated objects get freed up.
If devm_*() is not possible here, then the drivers hid-asus.c and
hid-gembird.c have two additional memory leaks, because they do that.

$ awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c | grep -E 'alloc'
drivers/hid/hid-asus.c          new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
drivers/hid/hid-gembird.c               new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);

(That is without my threee patches)


> > For transparency, I generated these commits with Gemini-CLI,
> > starting with this prompt:
> > 
> >     We are working in the Linux kernel. In the HID drivers in
> >     `drivers/hid/hid-*.c`, the `report_fixup` driver hook is a function
> >     that gets a byte buffer (with size) as input and that may modify that
> >     byte buffer, and optionally return a pointer to a new byte buffer and
> >     update the size.  The returned value is *not* memory-managed by the
> >     caller though and will not be freed subsequently.  When the
> 
> If the memory is *not* managed, why would gemini converts kzalloc into
> devm variants without changing the kfree paths????

I'm not sure I understand the question, it's not clear to me what you
mean by the "kfree paths".

I have seen usages of devm in other HID drivers and I was under the
impression that devm_* allocations would work in the HID subsystem to
allocate objects which are then freed automatically at a later point
when the device gets removed.  Is that inaccurate?


> >     `report_fixup` implementation allocates a new buffer and returns that,
> >     that will not get freed by the caller.  
> 
> This is wrong. See hid_close_report(): if the new rdesc (after fixup)
> differs from the one initially set, there is an explicit call to
> kfree().
> 
> -> there is no memleak AFAICT, and your prompt is wrong.

See my discussion in [1].  The pointer returned by report_fixup() is
immediately discarded in the position marked with (4).  This is still
in the hid_open_report() function where the leak happens.

Let me know whether this makes sense.  I'm happy to be corrected, but
so far, I still have the feeling that my reasoning is sound.

—Günther

[1] https://lore.kernel.org/all/aZTEnPEHcWEkoTJR@google.com/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
  2026-02-17 19:42     ` Günther Noack
@ 2026-02-18 19:04       ` Benjamin Tissoires
  2026-02-19 15:47         ` Günther Noack
  0 siblings, 1 reply; 12+ messages in thread
From: Benjamin Tissoires @ 2026-02-18 19:04 UTC (permalink / raw)
  To: Günther Noack; +Cc: Jiri Kosina, linux-input, linux-kernel

On Feb 17 2026, Günther Noack wrote:
> Hello Benjamin!
> 
> On Tue, Feb 17, 2026 at 07:22:20PM +0100, Benjamin Tissoires wrote:
> > On Feb 17 2026, Günther Noack wrote:
> > > The apple_report_fixup() function was allocating a new buffer with
> > > kmemdup() but never freeing it. Since the caller of report_fixup() already
> > > provides a writable buffer and allows returning a pointer within that
> > > buffer, we can just modify the descriptor in-place and return the adjusted
> > > pointer.
> > > 
> > > Assisted-by: Gemini-CLI:Google Gemini 3
> > > Signed-off-by: Günther Noack <gnoack@google.com>
> > > ---
> > >  drivers/hid/hid-apple.c | 4 +---
> > >  1 file changed, 1 insertion(+), 3 deletions(-)
> > > 
> > > diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> > > index 233e367cce1d..894adc23367b 100644
> > > --- a/drivers/hid/hid-apple.c
> > > +++ b/drivers/hid/hid-apple.c
> > > @@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> > >  		hid_info(hdev,
> > >  			 "fixing up Magic Keyboard battery report descriptor\n");
> > >  		*rsize = *rsize - 1;
> > > -		rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
> > > -		if (!rdesc)
> > > -			return NULL;
> > > +		rdesc = rdesc + 1;
> > 
> > I might be wrong, but later we call free(dev->rdesc) on device removal.
> > AFAICT, incrementing the pointer is undefined behavior.
> > 
> > What we should do instead is probably a krealloc instead of a kmemdup.
> > 
> > Same for all 3 patches.
> 
> Thanks for the review.
> 
> Let me try to address your three responses in one reply.
> 
> I am happy to accept it if I am wrong about this, but I don't
> understand your reasoning.  (This should go without saying, but maybe
> is worth reiterating, I would not have sent this if I had not
> convinced myself independently of LLM-assisted reasoning.)
> 
> Let me explain my reasoning based on the place where .report_fixup()
> is called from, which is hid_open_report() in hid-core.c:
> 
> 
> 	start = device->bpf_rdesc;                         /* (1) */
> 	if (WARN_ON(!start))
> 		return -ENODEV;
> 	size = device->bpf_rsize;
> 
> 	if (device->driver->report_fixup) {
> 		/*
> 		 * device->driver->report_fixup() needs to work
> 		 * on a copy of our report descriptor so it can
> 		 * change it.
> 		 */
> 		__u8 *buf = kmemdup(start, size, GFP_KERNEL);   /* (2) */
> 
> 		if (buf == NULL)
> 			return -ENOMEM;
> 
> 		start = device->driver->report_fixup(device, buf, &size);  /* (3) */
> 
> 		/*
> 		 * The second kmemdup is required in case report_fixup() returns
> 		 * a static read-only memory, but we have no idea if that memory
> 		 * needs to be cleaned up or not at the end.
> 		 */
> 		start = kmemdup(start, size, GFP_KERNEL);  /* (4) */
> 		kfree(buf);                                /* (5) */
> 		if (start == NULL)
> 			return -ENOMEM;
> 	}
> 
> 	device->rdesc = start;
> 	device->rsize = size;
> 
> 
> This function uses a slightly elaborate scheme to call .report_fixup:
> 
> (1) start is assigned to the original device->bpf_rdesc
> (2) buf is assigned to a copy of the 'start' buffer (deallocated in (5)).
>  .  It is done because buf is meant to be mutated by .report_fixup()
>  .  (3) start = ...->report_fixup(..., buf, ...)
>  .  (4) start = kmemdup(start, ...)
> (5) deallocate buf

Ouch. Yeah, sorry. I wrote that code and it seemed I completely paged
it out. Your code is actually correct (all three) but it would be nice
to have a longer commit message explaining this above.

The main point of this alloc before calling fixup is because some
drivers are using a static array as the new report descriptor. So we can
not free it later on. Working on a known copy allows to handle the kfree
correctly.

So yes, sorry, returning rdesc+1 in 1/3 and 2/3 is correct, and using a
devm_kzalloc is too in 3/3.

Cheers,
Benjamin

> 
> Importantly:
> 
> (a) The buffer buf passed to report_fixup() is a copy of the report
>     descriptor whose lifetime spans only from (2) to (5).
> (b) The result of .report_fixup(), start, is immediately discarded in
>     (4) and reassigned to the start variable again.
> 
> From (b), we can see that the result of .report_fixup() does *not* get
> deallocated by the caller, and thus, when the driver wants to return a
> newly allocated array, is must also hold a reference to it so that it
> can deallocate it later.
> 
> From (a), we can see that the report_fixup hook is free to manipulate
> the contents of the buffer that it receives, but if we were to
> *reallocate* it within report_fixup, as you are suggesting above, it
> could become a double free:
> 
> * During realloc, the allocator would potentially have to move the
>   buffer to a place where there is enough space, freeing up the old
>   place and allocating a new place. [1]
> * In (5), we would then pass the original (now deallocated) buf
>   pointer to kfree, leading to a double free.
> 
> If I were to describe the current interface of the hook
> .report_fixup(dev, rdesc, rsize), it would be:
> 
> * report_fixup may modify rdesc[0] to rdesc[rsize-1]
> * report_fixup may *not* deallocate rdesc
>   (ownership of rdesc stays with the caller)
>   * specifically, it may also not reallocate rdesc
>     (because that may imply a deallocation)
> * report_fixup returns a pointer to a buffer for which it can
>   guarantee that it lives long enough for the kmemdup in (4), but
>   which will *not* be deallocated by the caller (see (b) above).  The
>   three techniques I have found for that are:
>   * returning a subsection of the rdesc that it received
>   * returning a pointer into a statically allocated array
>     (e.g. motion_fixup() and ps3remote_fixup() in hid-sony.c)
>   * allocating it with a devm_*() function.  My understanding was
>     that this ties it to the lifetime of the device.  (e.g. the
>     QUIRK_G752_KEYBOARD case in hid-asus.c)
> 
> Honestly, I still think that this reasoning holds, but I am happy to
> be convinced otherwise.  Please let me know what you think.
> 
> —Günther

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
  2026-02-18 19:04       ` Benjamin Tissoires
@ 2026-02-19 15:47         ` Günther Noack
  0 siblings, 0 replies; 12+ messages in thread
From: Günther Noack @ 2026-02-19 15:47 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel

On Wed, Feb 18, 2026 at 08:04:52PM +0100, Benjamin Tissoires wrote:
> Ouch. Yeah, sorry. I wrote that code and it seemed I completely paged
> it out. Your code is actually correct (all three) but it would be nice
> to have a longer commit message explaining this above.
> 
> The main point of this alloc before calling fixup is because some
> drivers are using a static array as the new report descriptor. So we can
> not free it later on. Working on a known copy allows to handle the kfree
> correctly.
> 
> So yes, sorry, returning rdesc+1 in 1/3 and 2/3 is correct, and using a
> devm_kzalloc is too in 3/3.

Ah OK, thanks for the review!  I sent you an updated version where I
am trying to express it more clearly in the commit messages.

I also added a commit that documents the expected allocation
properties in hid.h where the .report_fixup() field is defined (feel
free to ignore this one, if it feels like it's too much).

Link to V2:
https://lore.kernel.org/all/20260219154338.786625-2-gnoack@google.com/

—Günther

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-02-19 15:47 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-17 16:01 [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Günther Noack
2026-02-17 16:01 ` [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup() Günther Noack
2026-02-17 18:22   ` Benjamin Tissoires
2026-02-17 19:42     ` Günther Noack
2026-02-18 19:04       ` Benjamin Tissoires
2026-02-19 15:47         ` Günther Noack
2026-02-17 16:01 ` [PATCH 2/3] HID: magicmouse: avoid memory leak in magicmouse_report_fixup() Günther Noack
2026-02-17 16:01 ` [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup() Günther Noack
2026-02-17 18:31   ` Benjamin Tissoires
2026-02-17 19:51     ` Günther Noack
2026-02-17 18:36 ` [PATCH 0/3] HID: Fix some memory leaks in drivers/hid Benjamin Tissoires
2026-02-17 20:08   ` Günther Noack

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox