fbcmap: integer overflow bug

There 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.

Signed-off-by: Dan Carpenter <error27@gmail.com>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>

authored by Dan Carpenter and committed by Paul Mundt 1e7c7804 c353103d

+21 -8
+20 -8
drivers/video/fbcmap.c
··· 88 88 * 89 89 */ 90 90 91 - int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) 91 + int fb_alloc_cmap_gfp(struct fb_cmap *cmap, int len, int transp, gfp_t flags) 92 92 { 93 93 int size = len * sizeof(u16); 94 + int ret = -ENOMEM; 94 95 95 96 if (cmap->len != len) { 96 97 fb_dealloc_cmap(cmap); 97 98 if (!len) 98 99 return 0; 99 100 100 - cmap->red = kmalloc(size, GFP_ATOMIC); 101 + cmap->red = kmalloc(size, flags); 101 102 if (!cmap->red) 102 103 goto fail; 103 - cmap->green = kmalloc(size, GFP_ATOMIC); 104 + cmap->green = kmalloc(size, flags); 104 105 if (!cmap->green) 105 106 goto fail; 106 - cmap->blue = kmalloc(size, GFP_ATOMIC); 107 + cmap->blue = kmalloc(size, flags); 107 108 if (!cmap->blue) 108 109 goto fail; 109 110 if (transp) { 110 - cmap->transp = kmalloc(size, GFP_ATOMIC); 111 + cmap->transp = kmalloc(size, flags); 111 112 if (!cmap->transp) 112 113 goto fail; 113 114 } else { ··· 117 116 } 118 117 cmap->start = 0; 119 118 cmap->len = len; 120 - fb_copy_cmap(fb_default_cmap(len), cmap); 119 + ret = fb_copy_cmap(fb_default_cmap(len), cmap); 120 + if (ret) 121 + goto fail; 121 122 return 0; 122 123 123 124 fail: 124 125 fb_dealloc_cmap(cmap); 125 - return -ENOMEM; 126 + return ret; 127 + } 128 + 129 + int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) 130 + { 131 + return fb_alloc_cmap_gfp(cmap, len, transp, GFP_ATOMIC); 126 132 } 127 133 128 134 /** ··· 264 256 int rc, size = cmap->len * sizeof(u16); 265 257 struct fb_cmap umap; 266 258 259 + if (size < 0 || size < cmap->len) 260 + return -E2BIG; 261 + 267 262 memset(&umap, 0, sizeof(struct fb_cmap)); 268 - rc = fb_alloc_cmap(&umap, cmap->len, cmap->transp != NULL); 263 + rc = fb_alloc_cmap_gfp(&umap, cmap->len, cmap->transp != NULL, 264 + GFP_KERNEL); 269 265 if (rc) 270 266 return rc; 271 267 if (copy_from_user(umap.red, cmap->red, size) ||
+1
include/linux/fb.h
··· 1122 1122 1123 1123 /* drivers/video/fbcmap.c */ 1124 1124 extern int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp); 1125 + extern int fb_alloc_cmap_gfp(struct fb_cmap *cmap, int len, int transp, gfp_t flags); 1125 1126 extern void fb_dealloc_cmap(struct fb_cmap *cmap); 1126 1127 extern int fb_copy_cmap(const struct fb_cmap *from, struct fb_cmap *to); 1127 1128 extern int fb_cmap_to_user(const struct fb_cmap *from, struct fb_cmap_user *to);