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:
Родитель
afd8d0f940
Коммит
1f5e31d7e5
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Загрузка…
Ссылка в новой задаче