fbmem: don't call copy_from/to_user() with mutex held

Avoid calling copy_from/to_user() with fb_info->lock mutex held in fbmem
ioctl().

fb_mmap() is called under mm->mmap_sem (A) held, that also acquires
fb_info->lock (B); fb_ioctl() takes fb_info->lock (B) and does
copy_from/to_user() that might acquire mm->mmap_sem (A), causing a
deadlock.

NOTE: it doesn't push down the fb_info->lock in each own driver's
fb_ioctl(), so there are still potential deadlocks elsewhere.

Signed-off-by: Andrea Righi <righi.andrea@gmail.com>
Cc: Dave Jones <davej@redhat.com>
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Cc: Johannes Weiner <hannes@saeurebad.de>
Cc: Krzysztof Helt <krzysztof.h1@wp.pl>
Cc: Harvey Harrison <harvey.harrison@gmail.com>
Cc: Stefan Richter <stefanr@s5r6.in-berlin.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Andrea Righi 2009-02-04 15:12:03 -08:00 committed by Linus Torvalds
parent afd8d0f940
commit 1f5e31d7e5
3 changed files with 98 additions and 72 deletions

View file

@ -250,10 +250,6 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)
int rc, size = cmap->len * sizeof(u16); int rc, size = cmap->len * sizeof(u16);
struct fb_cmap umap; struct fb_cmap umap;
if (cmap->start < 0 || (!info->fbops->fb_setcolreg &&
!info->fbops->fb_setcmap))
return -EINVAL;
memset(&umap, 0, sizeof(struct fb_cmap)); memset(&umap, 0, sizeof(struct fb_cmap));
rc = fb_alloc_cmap(&umap, cmap->len, cmap->transp != NULL); rc = fb_alloc_cmap(&umap, cmap->len, cmap->transp != NULL);
if (rc) if (rc)
@ -262,11 +258,23 @@ int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)
copy_from_user(umap.green, cmap->green, size) || copy_from_user(umap.green, cmap->green, size) ||
copy_from_user(umap.blue, cmap->blue, size) || copy_from_user(umap.blue, cmap->blue, size) ||
(cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) { (cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) {
fb_dealloc_cmap(&umap); rc = -EFAULT;
return -EFAULT; goto out;
} }
umap.start = cmap->start; umap.start = cmap->start;
if (!lock_fb_info(info)) {
rc = -ENODEV;
goto out;
}
if (cmap->start < 0 || (!info->fbops->fb_setcolreg &&
!info->fbops->fb_setcmap)) {
rc = -EINVAL;
goto out1;
}
rc = fb_set_cmap(&umap, info); rc = fb_set_cmap(&umap, info);
out1:
unlock_fb_info(info);
out:
fb_dealloc_cmap(&umap); fb_dealloc_cmap(&umap);
return rc; return rc;
} }

View file

@ -1013,132 +1013,139 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
struct fb_var_screeninfo var; struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix; struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb; struct fb_con2fbmap con2fb;
struct fb_cmap cmap_from;
struct fb_cmap_user cmap; struct fb_cmap_user cmap;
struct fb_event event; struct fb_event event;
void __user *argp = (void __user *)arg; void __user *argp = (void __user *)arg;
long ret = 0; long ret = 0;
fb = info->fbops;
if (!fb)
return -ENODEV;
switch (cmd) { switch (cmd) {
case FBIOGET_VSCREENINFO: case FBIOGET_VSCREENINFO:
ret = copy_to_user(argp, &info->var, if (!lock_fb_info(info))
sizeof(var)) ? -EFAULT : 0; return -ENODEV;
var = info->var;
unlock_fb_info(info);
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
break; break;
case FBIOPUT_VSCREENINFO: case FBIOPUT_VSCREENINFO:
if (copy_from_user(&var, argp, sizeof(var))) { if (copy_from_user(&var, argp, sizeof(var)))
ret = -EFAULT; return -EFAULT;
break; if (!lock_fb_info(info))
} return -ENODEV;
acquire_console_sem(); acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT; info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_set_var(info, &var); ret = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT; info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem(); release_console_sem();
if (ret == 0 && copy_to_user(argp, &var, sizeof(var))) unlock_fb_info(info);
if (!ret && copy_to_user(argp, &var, sizeof(var)))
ret = -EFAULT; ret = -EFAULT;
break; break;
case FBIOGET_FSCREENINFO: case FBIOGET_FSCREENINFO:
ret = copy_to_user(argp, &info->fix, if (!lock_fb_info(info))
sizeof(fix)) ? -EFAULT : 0; return -ENODEV;
fix = info->fix;
unlock_fb_info(info);
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
break; break;
case FBIOPUTCMAP: case FBIOPUTCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap))) if (copy_from_user(&cmap, argp, sizeof(cmap)))
ret = -EFAULT; return -EFAULT;
else ret = fb_set_user_cmap(&cmap, info);
ret = fb_set_user_cmap(&cmap, info);
break; break;
case FBIOGETCMAP: case FBIOGETCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap))) if (copy_from_user(&cmap, argp, sizeof(cmap)))
ret = -EFAULT; return -EFAULT;
else if (!lock_fb_info(info))
ret = fb_cmap_to_user(&info->cmap, &cmap); return -ENODEV;
cmap_from = info->cmap;
unlock_fb_info(info);
ret = fb_cmap_to_user(&cmap_from, &cmap);
break; break;
case FBIOPAN_DISPLAY: case FBIOPAN_DISPLAY:
if (copy_from_user(&var, argp, sizeof(var))) { if (copy_from_user(&var, argp, sizeof(var)))
ret = -EFAULT; return -EFAULT;
break; if (!lock_fb_info(info))
} return -ENODEV;
acquire_console_sem(); acquire_console_sem();
ret = fb_pan_display(info, &var); ret = fb_pan_display(info, &var);
release_console_sem(); release_console_sem();
unlock_fb_info(info);
if (ret == 0 && copy_to_user(argp, &var, sizeof(var))) if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
ret = -EFAULT; return -EFAULT;
break; break;
case FBIO_CURSOR: case FBIO_CURSOR:
ret = -EINVAL; ret = -EINVAL;
break; break;
case FBIOGET_CON2FBMAP: case FBIOGET_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb))) if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
ret = -EFAULT; return -EFAULT;
else if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
ret = -EINVAL; return -EINVAL;
else { con2fb.framebuffer = -1;
con2fb.framebuffer = -1; event.data = &con2fb;
event.info = info;
event.data = &con2fb; if (!lock_fb_info(info))
fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, return -ENODEV;
&event); event.info = info;
ret = copy_to_user(argp, &con2fb, fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
sizeof(con2fb)) ? -EFAULT : 0; unlock_fb_info(info);
}
ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
break; break;
case FBIOPUT_CON2FBMAP: case FBIOPUT_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb))) { if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
ret = -EFAULT; return -EFAULT;
break; if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
} return -EINVAL;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) { if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
ret = -EINVAL; return -EINVAL;
break;
}
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX) {
ret = -EINVAL;
break;
}
if (!registered_fb[con2fb.framebuffer]) if (!registered_fb[con2fb.framebuffer])
request_module("fb%d", con2fb.framebuffer); request_module("fb%d", con2fb.framebuffer);
if (!registered_fb[con2fb.framebuffer]) { if (!registered_fb[con2fb.framebuffer]) {
ret = -EINVAL; ret = -EINVAL;
break; break;
} }
event.info = info;
event.data = &con2fb; event.data = &con2fb;
if (!lock_fb_info(info))
return -ENODEV;
event.info = info;
ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP,
&event); &event);
unlock_fb_info(info);
break; break;
case FBIOBLANK: case FBIOBLANK:
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem(); acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT; info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_blank(info, arg); ret = fb_blank(info, arg);
info->flags &= ~FBINFO_MISC_USEREVENT; info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem(); release_console_sem();
break;; unlock_fb_info(info);
break;
default: default:
if (fb->fb_ioctl == NULL) if (!lock_fb_info(info))
ret = -ENOTTY; return -ENODEV;
else fb = info->fbops;
if (fb->fb_ioctl)
ret = fb->fb_ioctl(info, cmd, arg); ret = fb->fb_ioctl(info, cmd, arg);
else
ret = -ENOTTY;
unlock_fb_info(info);
} }
return ret; return ret;
} }
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
__acquires(&info->lock)
__releases(&info->lock)
{ {
struct inode *inode = file->f_path.dentry->d_inode; struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode); int fbidx = iminor(inode);
struct fb_info *info; struct fb_info *info = registered_fb[fbidx];
long ret;
info = registered_fb[fbidx]; return do_fb_ioctl(info, cmd, arg);
mutex_lock(&info->lock);
ret = do_fb_ioctl(info, cmd, arg);
mutex_unlock(&info->lock);
return ret;
} }
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
@ -1257,8 +1264,6 @@ static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,
static long fb_compat_ioctl(struct file *file, unsigned int cmd, static long fb_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg) unsigned long arg)
__acquires(&info->lock)
__releases(&info->lock)
{ {
struct inode *inode = file->f_path.dentry->d_inode; struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode); int fbidx = iminor(inode);
@ -1266,7 +1271,6 @@ __releases(&info->lock)
struct fb_ops *fb = info->fbops; struct fb_ops *fb = info->fbops;
long ret = -ENOIOCTLCMD; long ret = -ENOIOCTLCMD;
mutex_lock(&info->lock);
switch(cmd) { switch(cmd) {
case FBIOGET_VSCREENINFO: case FBIOGET_VSCREENINFO:
case FBIOPUT_VSCREENINFO: case FBIOPUT_VSCREENINFO:
@ -1292,7 +1296,6 @@ __releases(&info->lock)
ret = fb->fb_compat_ioctl(info, cmd, arg); ret = fb->fb_compat_ioctl(info, cmd, arg);
break; break;
} }
mutex_unlock(&info->lock);
return ret; return ret;
} }
#endif #endif

View file

@ -960,6 +960,21 @@ extern struct fb_info *registered_fb[FB_MAX];
extern int num_registered_fb; extern int num_registered_fb;
extern struct class *fb_class; extern struct class *fb_class;
static inline int lock_fb_info(struct fb_info *info)
{
mutex_lock(&info->lock);
if (!info->fbops) {
mutex_unlock(&info->lock);
return 0;
}
return 1;
}
static inline void unlock_fb_info(struct fb_info *info)
{
mutex_unlock(&info->lock);
}
static inline void __fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, static inline void __fb_pad_aligned_buffer(u8 *dst, u32 d_pitch,
u8 *src, u32 s_pitch, u32 height) u8 *src, u32 s_pitch, u32 height)
{ {