fbcmap: integer overflow bug
Subsystems (2)
RFC PATCH 1/1 fbcmap: integer overflow bug
There is an integer overflow bug in the FBIOPUTCMAP ioctl if cmap->len * 2 overflows.
It's harmless, except that it messes up the cmap until someone types reset. It could have been caught by checking the return from fb_copy_cmap().
Or it could have been caught by limiting the size of the cmaps to one page. The cmaps are allocated with GFP_ATOMIC and it makes sense to limit them.
Different drivers use different sizes of cmaps. There are about 150 drivers. I've checked a bunch (50) of them and the larges cmap.len I've found is gxt4500 which maxes out at 1024 so PAGE_SIZE is about twice that length. For some of the 50 I wasn't sure on the limit.
Is PAGE_SIZE a reasonable limit? Does anyone know or do I have to audit all 150 drivers?
drivers/video/fbcmap.c +6 -1
@@ -110,7 +110,9 @@ int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) } cmap->start =0 ; cmap->len = len; -fb_copy_cmap (fb_default_cmap (len), cmap); +if (fb_copy_cmap (fb_default_cmap (len), cmap)) + goto fail; +return 0 ; fail:
@@ -250,6 +252,9 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)int rc, size = cmap->len *sizeof (u16);struct fb_cmap umap; +if (cmap->len >PAGE_SIZE || cmap->len *sizeof (u16) >PAGE_SIZE ) +return -EINVAL ; +memset (&umap,0 ,sizeof (struct fb_cmap )); rc =fb_alloc_cmap (&umap, cmap->len, cmap->transp !=NULL );if (rc)
Quoted 11 lines · On Wed, 27 Oct 2010 11:37:16 +0200 Dan Carpenter <[email protected]> wrote:
There is an integer overflow bug in the FBIOPUTCMAP ioctl if
cmap->len* 2 overflows.It's harmless, except that it messes up the cmap until someone types
reset. It could have been caught by checking the return fromfb_copy_cmap().Or it could have been caught by limiting the size of the cmaps to one page. The cmaps are allocated with
GFP_ATOMICand it makes sense to limit them.
Quoted 7 lines
Different drivers use different sizes of cmaps. There are about 150 drivers. I've checked a bunch (50) of them and the larges cmap.len I've found is gxt4500 which maxes out at 1024 so
PAGE_SIZEis about twice that length. For some of the 50 I wasn't sure on the limit.Is
PAGE_SIZEa reasonable limit? Does anyone know or do I have to audit all 150 drivers?
No signed-off-by:.
Quoted 16 lines
drivers/video/fbcmap.c +3
-1@@ -110,7 +110,9 @@ int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) } cmap->start =0 ; cmap->len = len; -fb_copy_cmap (fb_default_cmap (len), cmap); +if (fb_copy_cmap (fb_default_cmap (len), cmap)) + goto fail; +return 0 ; fail:
drivers/video/fbcmap.c int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) · line 260
Quoted 9 lines
drivers/video/fbcmap.c +3
-0@@ -250,6 +252,9 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)int rc, size = cmap->len *sizeof (u16);struct fb_cmap umap; +if (cmap->len >PAGE_SIZE || cmap->len *sizeof (u16) >PAGE_SIZE ) +return -EINVAL ; +memset (&umap,0 ,sizeof (struct fb_cmap )); rc =fb_alloc_cmap (&umap, cmap->len, cmap->transp !=NULL );if (rc)
A suitable way to fix this would be to detect the overflow only, and then just throw the passed-in length at kmalloc(), and let kmalloc() decide whether it is sane or not.
To do that, one would need to implement a new fb_alloc_cmap_not_stupid() which takes a gfp_t, and pass in GFP_KERNEL.
Feel free to sanitise the fb_alloc_cmap() indenting in [patch 1/2] as well ;)
PATCH 1/2 fbcmap: cleanup white space in fb_alloc_cmap()
checkpatch.pl and Andrew Morton both complained about the indenting in fb_alloc_cmap()
drivers/video/fbcmap.c +30 -24
@@ -90,32 +90,38 @@ static const struct fb_cmap default_16_colors = {int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp) { -int size = len*sizeof (u16); - -if (cmap->len != len) { -fb_dealloc_cmap (cmap); -if (!len) -return 0 ; -if (!(cmap->red =kmalloc (size,GFP_ATOMIC ))) - goto fail; -if (!(cmap->green =kmalloc (size,GFP_ATOMIC ))) - goto fail; -if (!(cmap->blue =kmalloc (size,GFP_ATOMIC ))) - goto fail; -if (transp) { -if (!(cmap->transp =kmalloc (size,GFP_ATOMIC ))) - goto fail; - }else - cmap->transp =NULL ; - } - cmap->start =0 ; - cmap->len = len; -fb_copy_cmap (fb_default_cmap (len), cmap); -return 0 ; +int size = len *sizeof (u16); + +if (cmap->len != len) { +fb_dealloc_cmap (cmap); +if (!len) +return 0 ; + + cmap->red =kmalloc (size,GFP_ATOMIC ); +if (!cmap->red) + goto fail; + cmap->green =kmalloc (size,GFP_ATOMIC ); +if (!cmap->green) + goto fail; + cmap->blue =kmalloc (size,GFP_ATOMIC ); +if (!cmap->blue) + goto fail; +if (transp) { + cmap->transp =kmalloc (size,GFP_ATOMIC ); +if (!cmap->transp) + goto fail; + }else { + cmap->transp =NULL ; + } + } + cmap->start =0 ; + cmap->len = len; +fb_copy_cmap (fb_default_cmap (len), cmap); +return 0 ; fail: -fb_dealloc_cmap (cmap); -return -ENOMEM ; +fb_dealloc_cmap (cmap); +return -ENOMEM ; }/**
PATCH 2/2 fbcmap: integer overflow bug
In reply to Andrew MortonThere is an integer overflow in fb_set_user_cmap() because cmap->len * 2 can wrap. It's basically harmless. Your terminal will be messed up until you type reset.
This patch does three things to fix the bug.
First, it checks the return value of fb_copy_cmap() in fb_alloc_cmap(). That is enough to fix address the overflow.
Second it checks for the integer overflow in fb_set_user_cmap().
Lastly I wanted to cap "cmap->len" in fb_set_user_cmap() much lower because it gets used to determine the size of allocation. Unfortunately no one knows what the limit should be. Instead what this patch does is makes the allocation happen with GFP_KERNEL instead of GFP_ATOMIC and lets the kmalloc() decide what values of cmap->len are reasonable. To do this, the patch introduces a function called fb_alloc_cmap_gfp() which is like fb_alloc_cmap() except that it takes a GFP flag.
include/linux/fb.h +1 -0
@@ -1122,6 +1122,7 @@ extern const struct fb_videomode *fb_find_best_display(const struct fb_monspecs/* drivers/video/fbcmap.c */ extern int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp); +extern int fb_alloc_cmap_gfp (struct fb_cmap *cmap,int len,int transp,gfp_t flags);extern void fb_dealloc_cmap (struct fb_cmap *cmap);extern int fb_copy_cmap (const struct fb_cmap *from,struct fb_cmap *to);extern int fb_cmap_to_user (const struct fb_cmap *from,struct fb_cmap_user *to);
drivers/video/fbcmap.c +20 -8
@@ -88,26 +88,27 @@ static const struct fb_cmap default_16_colors = {* */ -int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp) +int fb_alloc_cmap_gfp (struct fb_cmap *cmap,int len,int transp,gfp_t flags) {int size = len *sizeof (u16); +int ret = -ENOMEM ;if (cmap->len != len) {fb_dealloc_cmap (cmap);if (!len)return 0 ; - cmap->red =kmalloc (size,GFP_ATOMIC ); + cmap->red =kmalloc (size, flags);if (!cmap->red) goto fail; - cmap->green =kmalloc (size,GFP_ATOMIC ); + cmap->green =kmalloc (size, flags);if (!cmap->green) goto fail; - cmap->blue =kmalloc (size,GFP_ATOMIC ); + cmap->blue =kmalloc (size, flags);if (!cmap->blue) goto fail;if (transp) { - cmap->transp =kmalloc (size,GFP_ATOMIC ); + cmap->transp =kmalloc (size, flags);if (!cmap->transp) goto fail; }else {
@@ -116,12 +117,19 @@ int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) } cmap->start =0 ; cmap->len = len; -fb_copy_cmap (fb_default_cmap (len), cmap); + ret =fb_copy_cmap (fb_default_cmap (len), cmap); +if (ret) + goto fail;return 0 ; fail:fb_dealloc_cmap (cmap); -return -ENOMEM ; +return ret; +} + +int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp) +{ +return fb_alloc_cmap_gfp (cmap, len, transp,GFP_ATOMIC ); }/**
@@ -256,8 +264,12 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)int rc, size = cmap->len *sizeof (u16);struct fb_cmap umap; +if (cmap->len *2 >INT_MAX ) +return -EINVAL ; +memset (&umap,0 ,sizeof (struct fb_cmap )); - rc =fb_alloc_cmap (&umap, cmap->len, cmap->transp !=NULL ); + rc =fb_alloc_cmap_gfp (&umap, cmap->len, cmap->transp !=NULL , +GFP_KERNEL );if (rc)return rc; if (copy_from_user(umap.red, cmap->red, size) ||
Quoted 4 lines · Dan Carpenter
drivers/video/fbcmap.c +0
-0@@ -256,8 +264,12 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)int rc, size = cmap->len *sizeof (u16);struct fb_cmap umap;
drivers/video/fbcmap.c int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) · line 275
Quoted 10 lines
drivers/video/fbcmap.c +5
-1+if (cmap->len *2 >INT_MAX ) +return -EINVAL ; +memset (&umap,0 ,sizeof (struct fb_cmap )); - rc =fb_alloc_cmap (&umap, cmap->len, cmap->transp !=NULL ); + rc =fb_alloc_cmap_gfp (&umap, cmap->len, cmap->transp !=NULL , +GFP_KERNEL );if (rc)return rc; if (copy_from_user(umap.red, cmap->red, size) ||
This looks reasonable, but it probably makes more sense to use -E2BIG for the overflow case (as other cases are doing already), and also just to check size directly rather than open-coding the * 2.
Quoted 6 lines · Paul Mundt
drivers/video/fbcmap.c int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) · line 267
Quoted 5 lines · Dan Carpenter
drivers/video/fbcmap.c +1
-0@@ -256,8 +264,12 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) int rc, size = cmap->len * sizeof(u16); struct fb_cmap umap; + if (cmap->len * 2 > INT_MAX)
Isn't that another integer overflow? I.e. should be "if (cmap->len > INT_MAX / sizeof(u16))" instead?
Quoted 10 lines
Quoted 9 lines
drivers/video/fbcmap.c +4
-1+ return -EINVAL; + memset(&umap, 0, sizeof(struct fb_cmap)); - rc = fb_alloc_cmap(&umap, cmap->len, cmap->transp != NULL); + rc = fb_alloc_cmap_gfp(&umap, cmap->len, cmap->transp != NULL, + GFP_KERNEL); if (rc) return rc;if (
copy_from_user(umap.red, cmap->red, size)||
Quoted 3 lines
This looks reasonable, but it probably makes more sense to use
-E2BIGfor the overflow case (as other cases are doing already), and also just to check size directly rather than open-coding the * 2.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]
In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that. -- Linus Torvalds
Quoted 11 lines · Geert Uytterhoeven
Quoted 6 lines · Paul Mundt
drivers/video/fbcmap.c int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) · line 267
Quoted 5 lines · Dan Carpenter
drivers/video/fbcmap.c +1
-0@@ -256,8 +264,12 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) int rc, size = cmap->len * sizeof(u16); struct fb_cmap umap; + if (cmap->len * 2 > INT_MAX)Isn't that another integer overflow? I.e. should be "if (
cmap->len>INT_MAX/ sizeof(u16))" instead?
Yeah it is. :/
I'll change it to:
if (size < 0 || size < cmap->len)
like Paul asked.
regards,
dan carpenter
--
To unsubscribe from this list: send the line "unsubscribe kernel-janitors" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Am 15.11.2010 08:20, schrieb Dan Carpenter:
Quoted 13 lines · Geert Uytterhoeven
Quoted 11 lines · Paul Mundt
Quoted 6 lines · Dan Carpenter
drivers/video/fbcmap.c int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) · line 267
Quoted 5 lines
drivers/video/fbcmap.c +1
-0@@ -256,8 +264,12 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)int rc, size = cmap->len *sizeof (u16);struct fb_cmap umap; +if (cmap->len *2 >INT_MAX )Isn't that another integer overflow? I.e. should be "if (
cmap->len>INT_MAX/ sizeof(u16))" instead?
Quoted 6 lines
Yeah it is. :/
I'll change it to:
if (size < 0 || size < cmap->len)like Paul asked.
I do not see the rest of the code but it looks like cmap->len is size in int8_t. So the upper limit is something like INT_MAX/(sizeof(u16)*2). Perhaps we can call it a char ? is there ANY system that has a more than 256 colors in R|G|B ?
<spying for struct fb_cmap_user >
__u32 len;
__u16 __user *red;
So no need to check for <0.
hope that helps,
re,
wh
Quoted 5 lines · walter harms
I do not see the rest of the code but it looks like
cmap->lenis size inint8_t. So the upper limit is something like INT_MAX/(sizeof(u16)*2). Perhaps we can call it a char ? is there ANY system that has a more than 256 colors in R|G|B ?
Yeah. There are. I had a list of some but I've deleted it.
Quoted 5 lines
<spying for struct
fb_cmap_user>
__u32len;
__u16__user*red;So no need to check for <0.
I test size which is an int so that's why it's needed.
regards,
dan carpenter
PATCH v2 2/2 fbcmap: integer overflow bug
In reply to Paul MundtThere is an integer overflow in fb_set_user_cmap() because cmap->len * 2 can wrap. It's basically harmless. Your terminal will be messed up until you type reset.
This patch does three things to fix the bug.
First, it checks the return value of fb_copy_cmap() in fb_alloc_cmap(). That is enough to fix address the overflow.
Second it checks for the integer overflow in fb_set_user_cmap().
Lastly I wanted to cap "cmap->len" in fb_set_user_cmap() much lower because it gets used to determine the size of allocation. Unfortunately no one knows what the limit should be. Instead what this patch does is makes the allocation happen with GFP_KERNEL instead of GFP_ATOMIC and lets the kmalloc() decide what values of cmap->len are reasonable. To do this, the patch introduces a function called fb_alloc_cmap_gfp() which is like fb_alloc_cmap() except that it takes a GFP flag.
---
v2: Fix overflow check in fb_set_user_cmap(). Return -E2BIG on error.
include/linux/fb.h +1 -0
@@ -1122,6 +1122,7 @@ extern const struct fb_videomode *fb_find_best_display(const struct fb_monspecs/* drivers/video/fbcmap.c */ extern int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp); +extern int fb_alloc_cmap_gfp (struct fb_cmap *cmap,int len,int transp,gfp_t flags);extern void fb_dealloc_cmap (struct fb_cmap *cmap);extern int fb_copy_cmap (const struct fb_cmap *from,struct fb_cmap *to);extern int fb_cmap_to_user (const struct fb_cmap *from,struct fb_cmap_user *to);
drivers/video/fbcmap.c +20 -8
@@ -88,26 +88,27 @@ static const struct fb_cmap default_16_colors = {* */ -int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp) +int fb_alloc_cmap_gfp (struct fb_cmap *cmap,int len,int transp,gfp_t flags) {int size = len *sizeof (u16); +int ret = -ENOMEM ;if (cmap->len != len) {fb_dealloc_cmap (cmap);if (!len)return 0 ; - cmap->red =kmalloc (size,GFP_ATOMIC ); + cmap->red =kmalloc (size, flags);if (!cmap->red) goto fail; - cmap->green =kmalloc (size,GFP_ATOMIC ); + cmap->green =kmalloc (size, flags);if (!cmap->green) goto fail; - cmap->blue =kmalloc (size,GFP_ATOMIC ); + cmap->blue =kmalloc (size, flags);if (!cmap->blue) goto fail;if (transp) { - cmap->transp =kmalloc (size,GFP_ATOMIC ); + cmap->transp =kmalloc (size, flags);if (!cmap->transp) goto fail; }else {
@@ -116,12 +117,19 @@ int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) } cmap->start =0 ; cmap->len = len; -fb_copy_cmap (fb_default_cmap (len), cmap); + ret =fb_copy_cmap (fb_default_cmap (len), cmap); +if (ret) + goto fail;return 0 ; fail:fb_dealloc_cmap (cmap); -return -ENOMEM ; +return ret; +} + +int fb_alloc_cmap (struct fb_cmap *cmap,int len,int transp) +{ +return fb_alloc_cmap_gfp (cmap, len, transp,GFP_ATOMIC ); }/**
@@ -256,8 +264,12 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)int rc, size = cmap->len *sizeof (u16);struct fb_cmap umap; +if (size <0 || size < cmap->len) +return -E2BIG ; +memset (&umap,0 ,sizeof (struct fb_cmap )); - rc =fb_alloc_cmap (&umap, cmap->len, cmap->transp !=NULL ); + rc =fb_alloc_cmap_gfp (&umap, cmap->len, cmap->transp !=NULL , +GFP_KERNEL );if (rc)return rc; if (copy_from_user(umap.red, cmap->red, size) ||
Re: [patch 1/2] fbcmap: cleanup white space in fb_alloc_cmap()
Quoted 2 lines · Dan Carpenter
checkpatch.pl and Andrew Morton both complained about the indenting in
fb_alloc_cmap()
Quoted 11 lines · Dan Carpenter
There is an integer overflow in
fb_set_user_cmap()becausecmap->len* 2 can wrap. It's basically harmless. Your terminal will be messed up until you type reset.This patch does three things to fix the bug.
First, it checks the return value of
fb_copy_cmap()infb_alloc_cmap(). That is enough to fix address the overflow.Second it checks for the integer overflow in
fb_set_user_cmap().
Quoted 7 lines
Lastly I wanted to cap "
cmap->len" infb_set_user_cmap()much lower because it gets used to determine the size of allocation. Unfortunately no one knows what the limit should be. Instead what this patch does is makes the allocation happen withGFP_KERNELinstead ofGFP_ATOMICand lets thekmalloc()decide what values ofcmap->lenare reasonable. To do this, the patch introduces a function calledfb_alloc_cmap_gfp()which is likefb_alloc_cmap()except that it takes a GFP flag.
Both applied, thanks.