Re: [PATCH] HID: hidraw, fix a NULL pointer dereference in hidraw_ioctl

Previous thread: [Patch] Limit sysctl_tcp_mem and sysctl_udp_mem initializers to prevent integer overflows. by Robin Holt on Saturday, October 2, 2010 - 4:24 am. (11 messages)

Next thread: [Patch] Limit max_user_watches to prevent integer overflow. by Robin Holt on Saturday, October 2, 2010 - 4:40 am. (1 message)
From: Antonio Ospite
Date: Saturday, October 2, 2010 - 4:25 am

BUG: unable to handle kernel NULL pointer dereference at 0000000000000028
IP: [<ffffffffa02c66b4>] hidraw_ioctl+0xfc/0x32c [hid]
[...]

This is reproducible by disconnecting the device while userspace does ioctl in
a loop and doesn't check return values in order to exit the loop, like in the
following test program:

int main(int argc, char *argv[])
{
	int fd = -1;
	unsigned char name[256];
	int ret;

	if (argc != 2) {
		fprintf(stderr, "usage: %s </dev/hidrawX>\n",
				argv[0]);
		exit(1);
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror("hidraw open");
		exit(1);
	}

	while (1) {
		ret = ioctl(fd, HIDIOCGRAWNAME(256), name);
		printf("ret: %d name: %s\n", ret, name);
	}

	close(fd);
	exit(0);
}

Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
---
Should this be applied to older stable kernels too?

Alan, Jiri,

there is a similar problem when _writing_ to the device, but Alan's
changes in that area are shuffling the code a bit, should I send a patch
[to hidraw_send_report()] on top of Alan's work for that, or a fix for
current mainline [in hidraw_write()] on which Alan should rebase his
work would be better?

The same pattern of unchecked hidraw_table[minor] is also present in
hidraw_get_report but this function is called only after the NULL check
in hidraw_ioctl _for_now_, so that is currently safe.

Thanks,
   Antonio Ospite
   http://ao2.it

 drivers/hid/hidraw.c |    5 +++++
 1 files changed, 5 insertions(+), 0 deletions(-)

diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 47d70c5..9eaf6ae 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -244,6 +244,10 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
 
 	mutex_lock(&minors_lock);
 	dev = hidraw_table[minor];
+	if (!dev) {
+		ret = -ENODEV;
+		goto out;
+	}
 
 	switch (cmd) {
 		case HIDIOCGRDESCSIZE:
@@ -317,6 +321,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
 
 		ret = -ENOTTY;
 	}
+out:
 ...
From: Jiri Kosina
Date: Monday, October 4, 2010 - 6:50 am

Yes, I will be adding (or feel free to do so yourself with another respin) 

Please send me the fix for current mainline for now, i.e. respin with the 
write path covered as well. We are struggling to get feedback on Alan's 
patches from Bluetooth maintainer, so we'd rather have this race fixed in 
any case.

Thanks,

-- 
Jiri Kosina
SUSE Labs, Novell Inc.
--

From: Antonio Ospite
Date: Monday, October 4, 2010 - 7:11 am

On Mon, 4 Oct 2010 15:50:31 +0200 (CEST)


Ok, I hope having Alan to resend his changes again rebased on these
fixes will bring the discussion on that up again.

Regards,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
From: Antonio Ospite
Date: Tuesday, October 5, 2010 - 8:20 am

BUG: unable to handle kernel NULL pointer dereference at 0000000000000028
IP: [<ffffffffa02c66b4>] hidraw_ioctl+0xfc/0x32c [hid]
[...]

This is reproducible by disconnecting the device while userspace does
ioctl in a loop and doesn't check return values in order to exit the
loop, like in the following test program:

int main(int argc, char *argv[])
{
	int fd = -1;
	unsigned char name[256];
	int ret;

	if (argc != 2) {
		fprintf(stderr, "usage: %s </dev/hidrawX>\n", argv[0]);
		exit(1);
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror("hidraw open");
		exit(1);
	}

	while (1) {
		ret = ioctl(fd, HIDIOCGRAWNAME(256), name);
		printf("ret: %d name: %s\n", ret, name);
	}

	close(fd);
	exit(0);
}

Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
---
 drivers/hid/hidraw.c |    5 +++++
 1 files changed, 5 insertions(+), 0 deletions(-)

diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 47d70c5..9eaf6ae 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -244,6 +244,10 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
 
 	mutex_lock(&minors_lock);
 	dev = hidraw_table[minor];
+	if (!dev) {
+		ret = -ENODEV;
+		goto out;
+	}
 
 	switch (cmd) {
 		case HIDIOCGRDESCSIZE:
@@ -317,6 +321,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
 
 		ret = -ENOTTY;
 	}
+out:
 	mutex_unlock(&minors_lock);
 	return ret;
 }
-- 
1.7.1

--

From: Antonio Ospite
Date: Tuesday, October 5, 2010 - 8:20 am

BUG: unable to handle kernel NULL pointer dereference at 0000000000000028
IP: [<ffffffffa0f0a625>] hidraw_write+0x3b/0x116 [hid]
[...]

This is reproducible by disconnecting the device while userspace writes
to dev node in a loop and doesn't check return values in order to exit
the loop, like in the following test program:

int main(int argc, char *argv[])
{
	int fd = -1;
	unsigned char report[2] = {0x01, 0x00};
	int ret;

	if (argc != 2) {
		fprintf(stderr, "usage: %s </dev/hidrawX>\n", argv[0]);
		exit(1);
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror("hidraw open");
		exit(1);
	}

	while (1) {
		ret = write(fd, report, sizeof(report));
		printf("ret: %d\n", ret);
	}

	close(fd);
	exit(0);
}

Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
---
 drivers/hid/hidraw.c |    6 ++++++
 1 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 9eaf6ae..a3866b5 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -109,6 +109,12 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t
 	int ret = 0;
 
 	mutex_lock(&minors_lock);
+
+	if (!hidraw_table[minor]) {
+		ret = -ENODEV;
+		goto out;
+	}
+
 	dev = hidraw_table[minor]->hid;
 
 	if (!dev->hid_output_raw_report) {
-- 
1.7.1

--

From: Antonio Ospite
Date: Tuesday, October 5, 2010 - 8:20 am

Hi,

here are some fixes to hidraw.

Patches are against 2.6.36-rc6, but they should be ported to other
maintained stable kernels as well.

Antonio Ospite (2):
  HID: hidraw, fix a NULL pointer dereference in hidraw_ioctl
  HID: hidraw, fix a NULL pointer dereference in hidraw_write

 drivers/hid/hidraw.c |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)


The first one has been sent already but I am resending it with
stable@stable@kernel.org in the recipients list.


Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
--

From: Greg KH
Date: Tuesday, October 5, 2010 - 10:42 am

Please read Documentation/stable_kernel_rules.txt which shows you how to
properly notify the stable developer to pick up the patch (hint, cc:ing
them on the patch is not the correct way, you need to add the mark to
the signed-off-by: area.)

thanks,

greg k-h
--

From: Antonio Ospite
Date: Tuesday, October 5, 2010 - 1:16 pm

On Tue, 5 Oct 2010 10:42:00 -0700

I see now, thanks Greg.

Jiri, are you adding the Cc: mark to these when you apply them? I
misunderstood your previous statement about that and thought CCing like
in recipients was enough.

There is one thing which is not 100% clear to me from the doc (my
fault): are patches targeted to -stable to be sent _twice_? Once for
linus master branch (this is what "upstream" means in that context,
isn't it?) and once for -stable with the commit ID of the former? Is
the Cc mark meant just to automate this very process? It seemed to me
that items 1 and 2 of the "Procedure" in
Documentation/stable_kernel_rules.txt are alternative each other.

Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
From: Jiri Kosina
Date: Wednesday, October 6, 2010 - 2:31 am

Either you have to send it twice, or you can ad 'Cc: stable@kernel.org' to 
the patch meta-information, and it will be picked up for stable 
automatically once it lands in Linus' tree.

I have added the 'Cc:' markings now and applied both patches, thanks.

-- 
Jiri Kosina
SUSE Labs, Novell Inc.
--

From: Antonio Ospite
Date: Friday, October 15, 2010 - 12:44 am

On Wed, 6 Oct 2010 11:31:55 +0200 (CEST)

Jiri, these are not in 2.6.36-rc8, any chance we can make it for 2.6.36?

Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
From: Jiri Kosina
Date: Friday, October 15, 2010 - 2:10 am

Hi Antonio,

yes, it is in my queue. I am currently a bit overloaded with other things, 
so as this is technically not a regression, it wasn't on very top of my 
list. Stil, will be pushing it to Linus soon.

Thanks,

-- 
Jiri Kosina
SUSE Labs, Novell Inc.
--

From: Jiri Slaby
Date: Tuesday, October 5, 2010 - 2:12 pm

Hi, please fix also the window in hidraw_release.

thanks,
-- 
js
--

From: Antonio Ospite
Date: Wednesday, October 6, 2010 - 3:01 am

On Tue, 05 Oct 2010 23:12:00 +0200


Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
From: Jiri Slaby
Date: Wednesday, October 6, 2010 - 3:09 am

Sure. Look at the code:
if (!hidraw_table[minor])
        return -ENODEV;
...
dev = hidraw_table[minor];
if (!--dev->open) {
...

This is done without minors_lock, so you can easily have dev being NULL
even though the first if.

regards,
-- 
js
--

From: Antonio Ospite
Date: Saturday, October 9, 2010 - 5:40 am

On Wed, 06 Oct 2010 12:09:07 +0200

Jiri (Slaby) I think it makes more sense if you send a patch about this
as it is you who has raised the issue.

Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
From: Jiri Slaby
Date: Tuesday, October 19, 2010 - 2:24 am

There is a window between hidraw_table check and its dereference.
In that window, the device may be unplugged and removed form the
system and we will then dereference NULL.

Lock that place properly so that either we get NULL and jump out or we
can work with real pointer.

Signed-off-by: Jiri Slaby <jslaby@suse.cz>
---
 drivers/hid/hidraw.c |   14 ++++++++++----
 1 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 925992f..6d81be3 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -218,9 +218,13 @@ static int hidraw_release(struct inode * inode, struct file * file)
 	unsigned int minor = iminor(inode);
 	struct hidraw *dev;
 	struct hidraw_list *list = file->private_data;
+	int ret;
 
-	if (!hidraw_table[minor])
-		return -ENODEV;
+	mutex_lock(&minors_lock);
+	if (!hidraw_table[minor]) {
+		ret = -ENODEV;
+		goto unlock;
+	}
 
 	list_del(&list->node);
 	dev = hidraw_table[minor];
@@ -233,10 +237,12 @@ static int hidraw_release(struct inode * inode, struct file * file)
 			kfree(list->hidraw);
 		}
 	}
-
+	ret = 0;
+unlock:
+	mutex_unlock(&minors_lock);
 	kfree(list);
 
-	return 0;
+	return ret;
 }
 
 static long hidraw_ioctl(struct file *file, unsigned int cmd,
-- 
1.7.3.1

--

From: Jiri Slaby
Date: Tuesday, October 19, 2010 - 2:28 am

Actually the kfree cannot be here. The first process to exit would free
it and the others will try to free it again.

Was it supposed to leak memory in the !hidraw_table[minor] case?

regards,
-- 
js
suse labs
--

From: Jiri Slaby
Date: Tuesday, October 19, 2010 - 2:29 am

There is a window between hidraw_table check and its dereference.
In that window, the device may be unplugged and removed form the
system and we will then dereference NULL.

Lock that place properly so that either we get NULL and jump out or we
can work with real pointer.

Signed-off-by: Jiri Slaby <jslaby@suse.cz>
---
 drivers/hid/hidraw.c |   14 ++++++++++----
 1 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 925992f..8a4b32d 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -218,9 +218,13 @@ static int hidraw_release(struct inode * inode, struct file * file)
 	unsigned int minor = iminor(inode);
 	struct hidraw *dev;
 	struct hidraw_list *list = file->private_data;
+	int ret;
 
-	if (!hidraw_table[minor])
-		return -ENODEV;
+	mutex_lock(&minors_lock);
+	if (!hidraw_table[minor]) {
+		ret = -ENODEV;
+		goto unlock;
+	}
 
 	list_del(&list->node);
 	dev = hidraw_table[minor];
@@ -233,10 +237,12 @@ static int hidraw_release(struct inode * inode, struct file * file)
 			kfree(list->hidraw);
 		}
 	}
-
 	kfree(list);
+	ret = 0;
+unlock:
+	mutex_unlock(&minors_lock);
 
-	return 0;
+	return ret;
 }
 
 static long hidraw_ioctl(struct file *file, unsigned int cmd,
-- 
1.7.3.1

--

From: Jiri Kosina
Date: Wednesday, October 20, 2010 - 7:55 am

Good catch, applied. Thanks.

-- 
Jiri Kosina
SUSE Labs, Novell Inc.
--

From: Alan Ott
Date: Monday, October 4, 2010 - 6:54 am

This doesn't have anything to do with my patch really, and goes way 

This needs to go back into stable kernels as well, so a patch against 
mainline will be necessary for that. If you want to make a patch against 
mine, that's fine with me. If you want me to work it into my patch, 

I can stick a comment ahead of hidraw_send_report, similar to the one 
which already exists.

Alan.


--

From: Antonio Ospite
Date: Tuesday, October 5, 2010 - 1:29 pm

On Mon, 04 Oct 2010 09:54:21 -0400

Alan rebasing your patch on top of the hidraw_write() fix is trivial
so you do it in your v5, ok? It does not get rebased automatically just
because of the mutex_lock() call.

If it is OK to you I'd promote my Tested-by to a Signed-off-by in
the commit message so to take implicit credit for the fix. But this is

ACK.

Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

PGP public key ID: 0x4553B001

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
Previous thread: [Patch] Limit sysctl_tcp_mem and sysctl_udp_mem initializers to prevent integer overflows. by Robin Holt on Saturday, October 2, 2010 - 4:24 am. (11 messages)

Next thread: [Patch] Limit max_user_watches to prevent integer overflow. by Robin Holt on Saturday, October 2, 2010 - 4:40 am. (1 message)