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>
#define FBPIXMAPSIZE (1024 * 8)
#define FBPIXMAPSIZE (1024 * 8)
+static DEFINE_MUTEX(registration_lock);
struct fb_info *registered_fb[FB_MAX] __read_mostly;
int num_registered_fb __read_mostly;
struct fb_info *registered_fb[FB_MAX] __read_mostly;
int num_registered_fb __read_mostly;
+static struct fb_info *get_fb_info(unsigned int idx)
+{
+ struct fb_info *fb_info;
+
+ if (idx >= FB_MAX)
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(®istration_lock);
+ fb_info = registered_fb[idx];
+ if (fb_info)
+ atomic_inc(&fb_info->count);
+ mutex_unlock(®istration_lock);
+
+ return fb_info;
+}
+
+static void put_fb_info(struct fb_info *fb_info)
+{
+ if (!atomic_dec_and_test(&fb_info->count))
+ return;
+ if (fb_info->fbops->fb_destroy)
+ fb_info->fbops->fb_destroy(fb_info);
+}
+
int lock_fb_info(struct fb_info *info)
{
mutex_lock(&info->lock);
int lock_fb_info(struct fb_info *info)
{
mutex_lock(&info->lock);
static void *fb_seq_start(struct seq_file *m, loff_t *pos)
{
static void *fb_seq_start(struct seq_file *m, loff_t *pos)
{
+ mutex_lock(®istration_lock);
return (*pos < FB_MAX) ? pos : NULL;
}
return (*pos < FB_MAX) ? pos : NULL;
}
static void fb_seq_stop(struct seq_file *m, void *v)
{
static void fb_seq_stop(struct seq_file *m, void *v)
{
+ mutex_unlock(®istration_lock);
}
static int fb_seq_show(struct seq_file *m, void *v)
}
static int fb_seq_show(struct seq_file *m, void *v)
struct fb_info *info;
int res = 0;
struct fb_info *info;
int res = 0;
- if (fbidx >= FB_MAX)
- return -ENODEV;
- info = registered_fb[fbidx];
- if (!info)
+ info = get_fb_info(fbidx);
+ if (!info) {
request_module("fb%d", fbidx);
request_module("fb%d", fbidx);
- info = registered_fb[fbidx];
- if (!info)
- return -ENODEV;
+ info = get_fb_info(fbidx);
+ if (!info)
+ return -ENODEV;
+ }
+ if (IS_ERR(info))
+ return PTR_ERR(info);
+
mutex_lock(&info->lock);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
mutex_lock(&info->lock);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
#endif
out:
mutex_unlock(&info->lock);
#endif
out:
mutex_unlock(&info->lock);
+ if (res)
+ put_fb_info(info);
info->fbops->fb_release(info,1);
module_put(info->fbops->owner);
mutex_unlock(&info->lock);
info->fbops->fb_release(info,1);
module_put(info->fbops->owner);
mutex_unlock(&info->lock);
remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
fb_is_primary_device(fb_info));
remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
fb_is_primary_device(fb_info));
+ mutex_lock(®istration_lock);
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
+ atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
+ mutex_unlock(®istration_lock);
event.info = fb_info;
if (!lock_fb_info(fb_info))
event.info = fb_info;
if (!lock_fb_info(fb_info))
struct fb_event event;
int i, ret = 0;
struct fb_event event;
int i, ret = 0;
+ mutex_lock(®istration_lock);
i = fb_info->node;
if (!registered_fb[i]) {
ret = -EINVAL;
i = fb_info->node;
if (!registered_fb[i]) {
ret = -EINVAL;
(fb_info->pixmap.flags & FB_PIXMAP_DEFAULT))
kfree(fb_info->pixmap.addr);
fb_destroy_modelist(&fb_info->modelist);
(fb_info->pixmap.flags & FB_PIXMAP_DEFAULT))
kfree(fb_info->pixmap.addr);
fb_destroy_modelist(&fb_info->modelist);
+ registered_fb[i] = NULL;
num_registered_fb--;
fb_cleanup_device(fb_info);
device_destroy(fb_class, MKDEV(FB_MAJOR, i));
num_registered_fb--;
fb_cleanup_device(fb_info);
device_destroy(fb_class, MKDEV(FB_MAJOR, i));
fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);
/* this may free fb info */
fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);
/* this may free fb info */
- if (fb_info->fbops->fb_destroy)
- fb_info->fbops->fb_destroy(fb_info);
+ mutex_unlock(®istration_lock);
#define FBINFO_CAN_FORCE_OUTPUT 0x200000
struct fb_info {
#define FBINFO_CAN_FORCE_OUTPUT 0x200000
struct fb_info {
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */