* [PATCH 1/2] fb: avoid oops when fb is removed by remove_conflicting_framebuffers
@ 2011-01-18 23:09 Herton Ronaldo Krzesinski
2011-01-18 23:09 ` [PATCH 2/2] fb: avoid possible deadlock caused by fb_set_suspend Herton Ronaldo Krzesinski
0 siblings, 1 reply; 2+ messages in thread
From: Herton Ronaldo Krzesinski @ 2011-01-18 23:09 UTC (permalink / raw)
To: linux-fbdev; +Cc: linux-kernel, Herton Ronaldo Krzesinski
Firmware framebuffers (which have the flag FBINFO_MISC_FIRMWARE) can be
removed and replaced by another native framebuffer, like what happens
when you boot with a vesa framebuffer and later loads a drm module with
modesetting enabled on x86.
When framebuffer which is going to replace the firmware framebuffer is
registered, unregister_framebuffer is called for the old firmware
framebuffer automatically inside remove_conflicting_framebuffers
function. The problem is that while this happens, the old framebuffer
can still be in use.
The first issue in this is that unregister_framebuffer can free/destroy
struct fb_info when calling fb_info->fbops->fb_destroy. The same struct
fb_info is given to file->private_data inside fb_open function, so if
there is an application that have old framebuffer open, and it closes it
after the framebuffer was replaced, fb_release will still get the old
struct fb_info from file->private_data. As it was freed, the kernel will
most likely oops accessing an already freed location inside fb_release.
To fix this, add a reference count to struct fb_info and use it, so the
struct will be only destroyed after the last user closes the framebuffer.
The second issue is that the file_operations framebuffer functions
reference registered_fb array without locking. This can cause a race and
oops if application using the old firmware framebuffer calls a
read/write/mmap/ioctl function while unregister_framebuffer is running,
in the window inside it where registered_fb for the old framebuffer is
set to NULL. To avoid this situation, do not get the framebuffer from
registered_fb array, instead get it from file->private_data which is set
on fb_open. While at it, also add a new fb_info state which is set when
the framebuffer is being unregistered, so that file_operations functions
can check and prevent access to framebuffer when possible, returning an
error if the framebuffer is being or already unregistered.
This addresses kernel.org bug #26232. A testcase is provided in the
ticket which triggers the problem on current kernels.
References: https://bugzilla.kernel.org/show_bug.cgi?id&232
Signed-off-by: Herton Ronaldo Krzesinski <herton@mandriva.com.br>
---
drivers/video/fbmem.c | 170 ++++++++++++++++++++++++++++++++++-------------
drivers/video/fbsysfs.c | 2 +
include/linux/fb.h | 2 +
3 files changed, 127 insertions(+), 47 deletions(-)
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c
index 4ac1201..05d09ac 100644
--- a/drivers/video/fbmem.c
+++ b/drivers/video/fbmem.c
@@ -48,7 +48,7 @@ int num_registered_fb __read_mostly;
int lock_fb_info(struct fb_info *info)
{
mutex_lock(&info->lock);
- if (!info->fbops) {
+ if (unlikely(!info->fbops || info->state = FBINFO_STATE_EXITING)) {
mutex_unlock(&info->lock);
return 0;
}
@@ -56,6 +56,72 @@ int lock_fb_info(struct fb_info *info)
}
EXPORT_SYMBOL(lock_fb_info);
+static int fbops_lock_fb_info(struct fb_info *info)
+{
+ int err;
+
+ if (!info->screen_base)
+ return -ENODEV;
+
+ if (info->flags = FBINFO_MISC_FIRMWARE) {
+ err = lock_fb_info(info);
+ if (err) {
+ if (info->state = FBINFO_STATE_SUSPENDED)
+ err = -EPERM;
+ if (err < 1)
+ unlock_fb_info(info);
+ else
+ err = 0;
+ } else
+ err = -ENODEV;
+ return err;
+ }
+
+ if (info->state != FBINFO_STATE_RUNNING)
+ return -EPERM;
+
+ return 0;
+}
+
+static void fbops_unlock_fb_info(struct fb_info *info)
+{
+ if (info->flags = FBINFO_MISC_FIRMWARE)
+ unlock_fb_info(info);
+}
+
+static void fb_kref_init(struct fb_info *info)
+{
+ if (info->flags = FBINFO_MISC_FIRMWARE)
+ kref_init(&info->fwfb_rfcnt);
+}
+
+static void fb_kref_get(struct fb_info *info)
+{
+ if (info->flags = FBINFO_MISC_FIRMWARE)
+ kref_get(&info->fwfb_rfcnt);
+}
+
+static void fb_kref_release(struct kref *ref)
+{
+ struct fb_info *info = container_of(ref, struct fb_info, fwfb_rfcnt);
+
+ if (info->fbops->fb_destroy)
+ info->fbops->fb_destroy(info);
+}
+
+static int fb_kref_put(struct fb_info *info, bool in_unregister)
+{
+ if (info->flags = FBINFO_MISC_FIRMWARE)
+ return kref_put(&info->fwfb_rfcnt, fb_kref_release);
+
+ if (in_unregister && info->fbops->fb_destroy) {
+ info->fbops->fb_destroy(info);
+ return 1;
+ }
+
+ return 0;
+}
+
/*
* Helpers
*/
@@ -694,30 +760,30 @@ static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx];
+ struct fb_info *info = file->private_data;
u8 *buffer, *dst;
u8 __iomem *src;
- int c, cnt = 0, err = 0;
+ int c, cnt = 0, err;
unsigned long total_size;
- if (!info || ! info->screen_base)
- return -ENODEV;
+ err = fbops_lock_fb_info(info);
+ if (err)
+ return err;
- if (info->state != FBINFO_STATE_RUNNING)
- return -EPERM;
+ if (info->fbops->fb_read) {
+ err = info->fbops->fb_read(info, buf, count, ppos);
+ goto fb_read_exit;
+ }
- if (info->fbops->fb_read)
- return info->fbops->fb_read(info, buf, count, ppos);
-
total_size = info->screen_size;
if (total_size = 0)
total_size = info->fix.smem_len;
- if (p >= total_size)
- return 0;
+ if (p >= total_size) {
+ err = 0;
+ goto fb_read_exit;
+ }
if (count >= total_size)
count = total_size;
@@ -727,8 +793,10 @@ fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
- if (!buffer)
- return -ENOMEM;
+ if (!buffer) {
+ err = -ENOMEM;
+ goto fb_read_exit;
+ }
src = (u8 __iomem *) (info->screen_base + p);
@@ -754,37 +822,42 @@ fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
kfree(buffer);
- return (err) ? err : cnt;
+ if (!err)
+ err = cnt;
+
+fb_read_exit:
+ fbops_unlock_fb_info(info);
+ return err;
}
static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx];
+ struct fb_info *info = file->private_data;
u8 *buffer, *src;
u8 __iomem *dst;
- int c, cnt = 0, err = 0;
+ int c, cnt = 0, err;
unsigned long total_size;
- if (!info || !info->screen_base)
- return -ENODEV;
+ err = fbops_lock_fb_info(info);
+ if (err)
+ return err;
- if (info->state != FBINFO_STATE_RUNNING)
- return -EPERM;
+ if (info->fbops->fb_write) {
+ err = info->fbops->fb_write(info, buf, count, ppos);
+ goto fb_write_exit;
+ }
- if (info->fbops->fb_write)
- return info->fbops->fb_write(info, buf, count, ppos);
-
total_size = info->screen_size;
if (total_size = 0)
total_size = info->fix.smem_len;
- if (p > total_size)
- return -EFBIG;
+ if (p > total_size) {
+ err = -EFBIG;
+ goto fb_write_exit;
+ }
if (count > total_size) {
err = -EFBIG;
@@ -800,8 +873,10 @@ fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
- if (!buffer)
- return -ENOMEM;
+ if (!buffer) {
+ err = -ENOMEM;
+ goto fb_write_exit;
+ }
dst = (u8 __iomem *) (info->screen_base + p);
@@ -828,7 +903,12 @@ fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
kfree(buffer);
- return (cnt) ? cnt : err;
+ if (cnt)
+ err = cnt;
+
+fb_write_exit:
+ fbops_unlock_fb_info(info);
+ return err;
}
int
@@ -1141,9 +1221,7 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx];
+ struct fb_info *info = file->private_data;
return do_fb_ioctl(info, cmd, arg);
}
@@ -1265,9 +1343,7 @@ static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,
static long fb_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx];
+ struct fb_info *info = file->private_data;
struct fb_ops *fb = info->fbops;
long ret = -ENOIOCTLCMD;
@@ -1303,8 +1379,7 @@ static long fb_compat_ioctl(struct file *file, unsigned int cmd,
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
- int fbidx = iminor(file->f_path.dentry->d_inode);
- struct fb_info *info = registered_fb[fbidx];
+ struct fb_info *info = file->private_data;
struct fb_ops *fb = info->fbops;
unsigned long off;
unsigned long start;
@@ -1380,6 +1455,8 @@ __releases(&info->lock)
if (res)
module_put(info->fbops->owner);
}
+ if (!res)
+ fb_kref_get(info);
#ifdef CONFIG_FB_DEFERRED_IO
if (info->fbdefio)
fb_deferred_io_open(info, inode, file);
@@ -1401,6 +1478,7 @@ __releases(&info->lock)
info->fbops->fb_release(info,1);
module_put(info->fbops->owner);
mutex_unlock(&info->lock);
+ fb_kref_put(info, false);
return 0;
}
@@ -1549,6 +1627,7 @@ register_framebuffer(struct fb_info *fb_info)
fb_info->node = i;
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
+ fb_kref_init(fb_info);
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
@@ -1622,9 +1701,9 @@ unregister_framebuffer(struct fb_info *fb_info)
goto done;
}
-
if (!lock_fb_info(fb_info))
return -ENODEV;
+ fb_info->state = FBINFO_STATE_EXITING;
event.info = fb_info;
ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event);
unlock_fb_info(fb_info);
@@ -1644,10 +1723,7 @@ unregister_framebuffer(struct fb_info *fb_info)
device_destroy(fb_class, MKDEV(FB_MAJOR, i));
event.info = 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);
+ fb_kref_put(fb_info, true);
done:
return ret;
}
@@ -1655,7 +1731,7 @@ done:
/**
* fb_set_suspend - low level driver signals suspend
* @info: framebuffer affected
- * @state: 0 = resuming, !=0 = suspending
+ * @state: 0 = resuming, 1 = suspending
*
* This is meant to be used by low level drivers to
* signal suspend/resume to the core & clients.
diff --git a/drivers/video/fbsysfs.c b/drivers/video/fbsysfs.c
index 0a08f13..be361b5 100644
--- a/drivers/video/fbsysfs.c
+++ b/drivers/video/fbsysfs.c
@@ -398,6 +398,8 @@ static ssize_t store_fbstate(struct device *device,
char *last = NULL;
state = simple_strtoul(buf, &last, 0);
+ if (state && state > 1)
+ return -EINVAL;
acquire_console_sem();
fb_set_suspend(fb_info, (int)state);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index d1631d3..836f605 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -871,6 +871,7 @@ struct fb_info {
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
+#define FBINFO_STATE_EXITING 2
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
@@ -885,6 +886,7 @@ struct fb_info {
resource_size_t size;
} ranges[0];
} *apertures;
+ struct kref fwfb_rfcnt;
};
static inline struct apertures_struct *alloc_apertures(unsigned int max_num) {
--
1.7.3.4
^ permalink raw reply related [flat|nested] 2+ messages in thread* [PATCH 2/2] fb: avoid possible deadlock caused by fb_set_suspend
2011-01-18 23:09 [PATCH 1/2] fb: avoid oops when fb is removed by remove_conflicting_framebuffers Herton Ronaldo Krzesinski
@ 2011-01-18 23:09 ` Herton Ronaldo Krzesinski
0 siblings, 0 replies; 2+ messages in thread
From: Herton Ronaldo Krzesinski @ 2011-01-18 23:09 UTC (permalink / raw)
To: linux-fbdev; +Cc: linux-kernel, Herton Ronaldo Krzesinski
A lock ordering issue can cause deadlocks: in framebuffer/console code,
all needed struct fb_info locks are taken before acquire_console_sem(),
in places which need to take console semaphore.
But fb_set_suspend is always called with console semaphore held, and
inside it we call lock_fb_info which gets the fb_info lock, inverse
locking order of what the rest of the code does. This causes a real
deadlock issue, when we write to state fb sysfs attribute (which calls
fb_set_suspend) while a framebuffer is being unregistered by
remove_conflicting_framebuffers, as can be shown by following show
blocked state trace on a test program which loads i915 and runs another
forked processes writing to state attribute:
Test process with semaphore held and trying to get fb_info lock:
...
fb-test2 D 0000000000000000 0 237 228 0x00000000
ffff8800774f3d68 0000000000000082 00000000000135c0 00000000000135c0
ffff880000000000 ffff8800774f3fd8 ffff8800774f3fd8 ffff880076ee4530
00000000000135c0 ffff8800774f3fd8 ffff8800774f2000 00000000000135c0
Call Trace:
[<ffffffff8141287a>] __mutex_lock_slowpath+0x11a/0x1e0
[<ffffffff814142f2>] ? _raw_spin_lock_irq+0x22/0x40
[<ffffffff814123d3>] mutex_lock+0x23/0x50
[<ffffffff8125dfc5>] lock_fb_info+0x25/0x60
[<ffffffff8125e3f0>] fb_set_suspend+0x20/0x80
[<ffffffff81263e2f>] store_fbstate+0x4f/0x70
[<ffffffff812e7f70>] dev_attr_store+0x20/0x30
[<ffffffff811c46b4>] sysfs_write_file+0xd4/0x160
[<ffffffff81155a26>] vfs_write+0xc6/0x190
[<ffffffff81155d51>] sys_write+0x51/0x90
[<ffffffff8100c012>] system_call_fastpath+0x16/0x1b
...
modprobe process stalled because has the fb_info lock (got inside
unregister_framebuffer) but waiting for the semaphore held by the
test process which is waiting to get the fb_info lock:
...
modprobe D 0000000000000000 0 230 218 0x00000000
ffff880077a4d618 0000000000000082 0000000000000001 0000000000000001
ffff880000000000 ffff880077a4dfd8 ffff880077a4dfd8 ffff8800775a2e20
00000000000135c0 ffff880077a4dfd8 ffff880077a4c000 00000000000135c0
Call Trace:
[<ffffffff81411fe5>] schedule_timeout+0x215/0x310
[<ffffffff81058051>] ? get_parent_ip+0x11/0x50
[<ffffffff814130dd>] __down+0x6d/0xb0
[<ffffffff81089f71>] down+0x41/0x50
[<ffffffff810629ac>] acquire_console_sem+0x2c/0x50
[<ffffffff812ca53d>] unbind_con_driver+0xad/0x2d0
[<ffffffff8126f5f7>] fbcon_event_notify+0x457/0x890
[<ffffffff814144ff>] ? _raw_spin_unlock_irqrestore+0x1f/0x50
[<ffffffff81058051>] ? get_parent_ip+0x11/0x50
[<ffffffff8141836d>] notifier_call_chain+0x4d/0x70
[<ffffffff8108a3b8>] __blocking_notifier_call_chain+0x58/0x80
[<ffffffff8108a3f6>] blocking_notifier_call_chain+0x16/0x20
[<ffffffff8125dabb>] fb_notifier_call_chain+0x1b/0x20
[<ffffffff8125e6ac>] unregister_framebuffer+0x7c/0x130
[<ffffffff8125e8b3>] remove_conflicting_framebuffers+0x153/0x180
[<ffffffff8125eef3>] register_framebuffer+0x93/0x2c0
[<ffffffffa0331112>] drm_fb_helper_single_fb_probe+0x252/0x2f0 [drm_kms_helper]
[<ffffffffa03314a3>] drm_fb_helper_initial_config+0x2f3/0x6d0 [drm_kms_helper]
[<ffffffffa03318dd>] ? drm_fb_helper_single_add_all_connectors+0x5d/0x1c0 [drm_kms_helper]
[<ffffffffa037b588>] intel_fbdev_init+0xa8/0x160 [i915]
[<ffffffffa0343d74>] i915_driver_load+0x854/0x12b0 [i915]
[<ffffffffa02f0e7e>] drm_get_pci_dev+0x19e/0x360 [drm]
[<ffffffff8141821d>] ? sub_preempt_count+0x9d/0xd0
[<ffffffffa0386f91>] i915_pci_probe+0x15/0x17 [i915]
[<ffffffff8124481f>] local_pci_probe+0x5f/0xd0
[<ffffffff81244f89>] pci_device_probe+0x119/0x120
[<ffffffff812eccaa>] ? driver_sysfs_add+0x7a/0xb0
[<ffffffff812ed003>] driver_probe_device+0xa3/0x290
[<ffffffff812ed1f0>] ? __driver_attach+0x0/0xb0
[<ffffffff812ed29b>] __driver_attach+0xab/0xb0
[<ffffffff812ed1f0>] ? __driver_attach+0x0/0xb0
[<ffffffff812ebd3e>] bus_for_each_dev+0x5e/0x90
[<ffffffff812ecc2e>] driver_attach+0x1e/0x20
[<ffffffff812ec6f2>] bus_add_driver+0xe2/0x320
[<ffffffffa03aa000>] ? i915_init+0x0/0x96 [i915]
[<ffffffff812ed536>] driver_register+0x76/0x140
[<ffffffffa03aa000>] ? i915_init+0x0/0x96 [i915]
[<ffffffff81245216>] __pci_register_driver+0x56/0xd0
[<ffffffffa02f1264>] drm_pci_init+0xe4/0xf0 [drm]
[<ffffffffa03aa000>] ? i915_init+0x0/0x96 [i915]
[<ffffffffa02e84a8>] drm_init+0x58/0x70 [drm]
[<ffffffffa03aa094>] i915_init+0x94/0x96 [i915]
[<ffffffff81002194>] do_one_initcall+0x44/0x190
[<ffffffff810a066b>] sys_init_module+0xcb/0x210
[<ffffffff8100c012>] system_call_fastpath+0x16/0x1b
...
fb-test2 which reproduces above is available on kernel.org bug #26232.
To solve this issue, avoid calling lock_fb_info inside fb_set_suspend,
and move it out to where needed (callers of fb_set_suspend must call
lock_fb_info before if needed). So far, the only place which needs to
call lock_fb_info is store_fbstate, all other places which calls
fb_set_suspend are suspend/resume hooks that should not need the lock as
they should be run only when processes are already frozen in
suspend/resume.
References: https://bugzilla.kernel.org/show_bug.cgi?id&232
Signed-off-by: Herton Ronaldo Krzesinski <herton@mandriva.com.br>
---
drivers/video/fbmem.c | 4 +---
drivers/video/fbsysfs.c | 3 +++
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c
index 05d09ac..6747e06 100644
--- a/drivers/video/fbmem.c
+++ b/drivers/video/fbmem.c
@@ -1741,8 +1741,7 @@ void fb_set_suspend(struct fb_info *info, int state)
{
struct fb_event event;
- if (!lock_fb_info(info))
- return;
+ WARN_ON(info->state = FBINFO_STATE_EXITING);
event.info = info;
if (state) {
fb_notifier_call_chain(FB_EVENT_SUSPEND, &event);
@@ -1751,7 +1750,6 @@ void fb_set_suspend(struct fb_info *info, int state)
info->state = FBINFO_STATE_RUNNING;
fb_notifier_call_chain(FB_EVENT_RESUME, &event);
}
- unlock_fb_info(info);
}
/**
diff --git a/drivers/video/fbsysfs.c b/drivers/video/fbsysfs.c
index be361b5..2ba5370 100644
--- a/drivers/video/fbsysfs.c
+++ b/drivers/video/fbsysfs.c
@@ -401,9 +401,12 @@ static ssize_t store_fbstate(struct device *device,
if (state && state > 1)
return -EINVAL;
+ if (!lock_fb_info(fb_info))
+ return -ENODEV;
acquire_console_sem();
fb_set_suspend(fb_info, (int)state);
release_console_sem();
+ unlock_fb_info(fb_info);
return count;
}
--
1.7.3.4
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2011-01-18 23:09 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-01-18 23:09 [PATCH 1/2] fb: avoid oops when fb is removed by remove_conflicting_framebuffers Herton Ronaldo Krzesinski
2011-01-18 23:09 ` [PATCH 2/2] fb: avoid possible deadlock caused by fb_set_suspend Herton Ronaldo Krzesinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox