diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-05-11 17:49:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-05-12 10:37:51 -0400 |
commit | 698b368275c3fa98261159253cfc79653f9dffc6 (patch) | |
tree | b92c921fe6522ece33fbbde33cc173c9dd32d9a2 | |
parent | 9f381a61f58bb6487c93ce2233bb9992f8ea9211 (diff) |
fbcon: add lifetime refcount to opened frame buffers
This just adds the refcount and the new registration lock logic. It
does not (for example) actually change the read/write/ioctl routines to
actually use the frame buffer that was opened: those function still end
up alway susing whatever the current frame buffer is at the time of the
call.
Without this, if something holds the frame buffer open over a
framebuffer switch, the close() operation after the switch will access a
fb_info that has been free'd by the unregistering of the old frame
buffer.
(The read/write/ioctl operations will normally not cause problems,
because they will - illogically - pick up the new fbcon instead. But a
switch that happens just as one of those is going on might see problems
too, the window is just much smaller: one individual op rather than the
whole open-close sequence.)
This use-after-free is apparently fairly easily triggered by the Ubuntu
11.04 boot sequence.
Acked-by: Tim Gardner <tim.gardner@canonical.com>
Tested-by: Daniel J Blueman <daniel.blueman@gmail.com>
Tested-by: Anca Emanuel <anca.emanuel@gmail.com>
Cc: Bruno Prémont <bonbons@linux-vserver.org>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Paul Mundt <lethal@linux-sh.org>
Cc: Dave Airlie <airlied@redhat.com>
Cc: Andy Whitcroft <andy.whitcroft@canonical.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/video/fbmem.c | 56 | ||||
-rw-r--r-- | include/linux/fb.h | 1 |
2 files changed, 47 insertions, 10 deletions
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c index e0c2284924b6..eec14d2ca1c7 100644 --- a/drivers/video/fbmem.c +++ b/drivers/video/fbmem.c | |||
@@ -42,9 +42,34 @@ | |||
42 | 42 | ||
43 | #define FBPIXMAPSIZE (1024 * 8) | 43 | #define FBPIXMAPSIZE (1024 * 8) |
44 | 44 | ||
45 | static DEFINE_MUTEX(registration_lock); | ||
45 | struct fb_info *registered_fb[FB_MAX] __read_mostly; | 46 | struct fb_info *registered_fb[FB_MAX] __read_mostly; |
46 | int num_registered_fb __read_mostly; | 47 | int num_registered_fb __read_mostly; |
47 | 48 | ||
49 | static struct fb_info *get_fb_info(unsigned int idx) | ||
50 | { | ||
51 | struct fb_info *fb_info; | ||
52 | |||
53 | if (idx >= FB_MAX) | ||
54 | return ERR_PTR(-ENODEV); | ||
55 | |||
56 | mutex_lock(®istration_lock); | ||
57 | fb_info = registered_fb[idx]; | ||
58 | if (fb_info) | ||
59 | atomic_inc(&fb_info->count); | ||
60 | mutex_unlock(®istration_lock); | ||
61 | |||
62 | return fb_info; | ||
63 | } | ||
64 | |||
65 | static void put_fb_info(struct fb_info *fb_info) | ||
66 | { | ||
67 | if (!atomic_dec_and_test(&fb_info->count)) | ||
68 | return; | ||
69 | if (fb_info->fbops->fb_destroy) | ||
70 | fb_info->fbops->fb_destroy(fb_info); | ||
71 | } | ||
72 | |||
48 | int lock_fb_info(struct fb_info *info) | 73 | int lock_fb_info(struct fb_info *info) |
49 | { | 74 | { |
50 | mutex_lock(&info->lock); | 75 | mutex_lock(&info->lock); |
@@ -647,6 +672,7 @@ int fb_show_logo(struct fb_info *info, int rotate) { return 0; } | |||
647 | 672 | ||
648 | static void *fb_seq_start(struct seq_file *m, loff_t *pos) | 673 | static void *fb_seq_start(struct seq_file *m, loff_t *pos) |
649 | { | 674 | { |
675 | mutex_lock(®istration_lock); | ||
650 | return (*pos < FB_MAX) ? pos : NULL; | 676 | return (*pos < FB_MAX) ? pos : NULL; |
651 | } | 677 | } |
652 | 678 | ||
@@ -658,6 +684,7 @@ static void *fb_seq_next(struct seq_file *m, void *v, loff_t *pos) | |||
658 | 684 | ||
659 | static void fb_seq_stop(struct seq_file *m, void *v) | 685 | static void fb_seq_stop(struct seq_file *m, void *v) |
660 | { | 686 | { |
687 | mutex_unlock(®istration_lock); | ||
661 | } | 688 | } |
662 | 689 | ||
663 | static int fb_seq_show(struct seq_file *m, void *v) | 690 | static int fb_seq_show(struct seq_file *m, void *v) |
@@ -1361,14 +1388,16 @@ __releases(&info->lock) | |||
1361 | struct fb_info *info; | 1388 | struct fb_info *info; |
1362 | int res = 0; | 1389 | int res = 0; |
1363 | 1390 | ||
1364 | if (fbidx >= FB_MAX) | 1391 | info = get_fb_info(fbidx); |
1365 | return -ENODEV; | 1392 | if (!info) { |
1366 | info = registered_fb[fbidx]; | ||
1367 | if (!info) | ||
1368 | request_module("fb%d", fbidx); | 1393 | request_module("fb%d", fbidx); |
1369 | info = registered_fb[fbidx]; | 1394 | info = get_fb_info(fbidx); |
1370 | if (!info) | 1395 | if (!info) |
1371 | return -ENODEV; | 1396 | return -ENODEV; |
1397 | } | ||
1398 | if (IS_ERR(info)) | ||
1399 | return PTR_ERR(info); | ||
1400 | |||
1372 | mutex_lock(&info->lock); | 1401 | mutex_lock(&info->lock); |
1373 | if (!try_module_get(info->fbops->owner)) { | 1402 | if (!try_module_get(info->fbops->owner)) { |
1374 | res = -ENODEV; | 1403 | res = -ENODEV; |
@@ -1386,6 +1415,8 @@ __releases(&info->lock) | |||
1386 | #endif | 1415 | #endif |
1387 | out: | 1416 | out: |
1388 | mutex_unlock(&info->lock); | 1417 | mutex_unlock(&info->lock); |
1418 | if (res) | ||
1419 | put_fb_info(info); | ||
1389 | return res; | 1420 | return res; |
1390 | } | 1421 | } |
1391 | 1422 | ||
@@ -1401,6 +1432,7 @@ __releases(&info->lock) | |||
1401 | info->fbops->fb_release(info,1); | 1432 | info->fbops->fb_release(info,1); |
1402 | module_put(info->fbops->owner); | 1433 | module_put(info->fbops->owner); |
1403 | mutex_unlock(&info->lock); | 1434 | mutex_unlock(&info->lock); |
1435 | put_fb_info(info); | ||
1404 | return 0; | 1436 | return 0; |
1405 | } | 1437 | } |
1406 | 1438 | ||
@@ -1542,11 +1574,13 @@ register_framebuffer(struct fb_info *fb_info) | |||
1542 | remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, | 1574 | remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, |
1543 | fb_is_primary_device(fb_info)); | 1575 | fb_is_primary_device(fb_info)); |
1544 | 1576 | ||
1577 | mutex_lock(®istration_lock); | ||
1545 | num_registered_fb++; | 1578 | num_registered_fb++; |
1546 | for (i = 0 ; i < FB_MAX; i++) | 1579 | for (i = 0 ; i < FB_MAX; i++) |
1547 | if (!registered_fb[i]) | 1580 | if (!registered_fb[i]) |
1548 | break; | 1581 | break; |
1549 | fb_info->node = i; | 1582 | fb_info->node = i; |
1583 | atomic_set(&fb_info->count, 1); | ||
1550 | mutex_init(&fb_info->lock); | 1584 | mutex_init(&fb_info->lock); |
1551 | mutex_init(&fb_info->mm_lock); | 1585 | mutex_init(&fb_info->mm_lock); |
1552 | 1586 | ||
@@ -1583,6 +1617,7 @@ register_framebuffer(struct fb_info *fb_info) | |||
1583 | fb_var_to_videomode(&mode, &fb_info->var); | 1617 | fb_var_to_videomode(&mode, &fb_info->var); |
1584 | fb_add_videomode(&mode, &fb_info->modelist); | 1618 | fb_add_videomode(&mode, &fb_info->modelist); |
1585 | registered_fb[i] = fb_info; | 1619 | registered_fb[i] = fb_info; |
1620 | mutex_unlock(®istration_lock); | ||
1586 | 1621 | ||
1587 | event.info = fb_info; | 1622 | event.info = fb_info; |
1588 | if (!lock_fb_info(fb_info)) | 1623 | if (!lock_fb_info(fb_info)) |
@@ -1616,6 +1651,7 @@ unregister_framebuffer(struct fb_info *fb_info) | |||
1616 | struct fb_event event; | 1651 | struct fb_event event; |
1617 | int i, ret = 0; | 1652 | int i, ret = 0; |
1618 | 1653 | ||
1654 | mutex_lock(®istration_lock); | ||
1619 | i = fb_info->node; | 1655 | i = fb_info->node; |
1620 | if (!registered_fb[i]) { | 1656 | if (!registered_fb[i]) { |
1621 | ret = -EINVAL; | 1657 | ret = -EINVAL; |
@@ -1638,7 +1674,7 @@ unregister_framebuffer(struct fb_info *fb_info) | |||
1638 | (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) | 1674 | (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) |
1639 | kfree(fb_info->pixmap.addr); | 1675 | kfree(fb_info->pixmap.addr); |
1640 | fb_destroy_modelist(&fb_info->modelist); | 1676 | fb_destroy_modelist(&fb_info->modelist); |
1641 | registered_fb[i]=NULL; | 1677 | registered_fb[i] = NULL; |
1642 | num_registered_fb--; | 1678 | num_registered_fb--; |
1643 | fb_cleanup_device(fb_info); | 1679 | fb_cleanup_device(fb_info); |
1644 | device_destroy(fb_class, MKDEV(FB_MAJOR, i)); | 1680 | device_destroy(fb_class, MKDEV(FB_MAJOR, i)); |
@@ -1646,9 +1682,9 @@ unregister_framebuffer(struct fb_info *fb_info) | |||
1646 | fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); | 1682 | fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); |
1647 | 1683 | ||
1648 | /* this may free fb info */ | 1684 | /* this may free fb info */ |
1649 | if (fb_info->fbops->fb_destroy) | 1685 | put_fb_info(fb_info); |
1650 | fb_info->fbops->fb_destroy(fb_info); | ||
1651 | done: | 1686 | done: |
1687 | mutex_unlock(®istration_lock); | ||
1652 | return ret; | 1688 | return ret; |
1653 | } | 1689 | } |
1654 | 1690 | ||
diff --git a/include/linux/fb.h b/include/linux/fb.h index df728c1c29ed..6a8274877171 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h | |||
@@ -832,6 +832,7 @@ struct fb_tile_ops { | |||
832 | #define FBINFO_CAN_FORCE_OUTPUT 0x200000 | 832 | #define FBINFO_CAN_FORCE_OUTPUT 0x200000 |
833 | 833 | ||
834 | struct fb_info { | 834 | struct fb_info { |
835 | atomic_t count; | ||
835 | int node; | 836 | int node; |
836 | int flags; | 837 | int flags; |
837 | struct mutex lock; /* Lock for open/release/ioctl funcs */ | 838 | struct mutex lock; /* Lock for open/release/ioctl funcs */ |