Skip to content

Commit cc67482

Browse files
V4belhdeller
authored andcommitted
fbdev: smscufx: Fix several use-after-free bugs
Several types of UAFs can occur when physically removing a USB device. Adds ufx_ops_destroy() function to .fb_destroy of fb_ops, and in this function, there is kref_put() that finally calls ufx_free(). This fix prevents multiple UAFs. Signed-off-by: Hyunwoo Kim <[email protected]> Link: https://lore.kernel.org/linux-fbdev/20221011153436.GA4446@ubuntu/ Cc: <[email protected]> Signed-off-by: Helge Deller <[email protected]>
1 parent 7028159 commit cc67482

File tree

1 file changed

+30
-25
lines changed

1 file changed

+30
-25
lines changed

drivers/video/fbdev/smscufx.c

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ struct ufx_data {
9797
struct kref kref;
9898
int fb_count;
9999
bool virtualized; /* true when physical usb device not present */
100-
struct delayed_work free_framebuffer_work;
101100
atomic_t usb_active; /* 0 = update virtual buffer, but no usb traffic */
102101
atomic_t lost_pixels; /* 1 = a render op failed. Need screen refresh */
103102
u8 *edid; /* null until we read edid from hw or get from sysfs */
@@ -1117,15 +1116,24 @@ static void ufx_free(struct kref *kref)
11171116
{
11181117
struct ufx_data *dev = container_of(kref, struct ufx_data, kref);
11191118

1120-
/* this function will wait for all in-flight urbs to complete */
1121-
if (dev->urbs.count > 0)
1122-
ufx_free_urb_list(dev);
1119+
kfree(dev);
1120+
}
11231121

1124-
pr_debug("freeing ufx_data %p", dev);
1122+
static void ufx_ops_destory(struct fb_info *info)
1123+
{
1124+
struct ufx_data *dev = info->par;
1125+
int node = info->node;
11251126

1126-
kfree(dev);
1127+
/* Assume info structure is freed after this point */
1128+
framebuffer_release(info);
1129+
1130+
pr_debug("fb_info for /dev/fb%d has been freed", node);
1131+
1132+
/* release reference taken by kref_init in probe() */
1133+
kref_put(&dev->kref, ufx_free);
11271134
}
11281135

1136+
11291137
static void ufx_release_urb_work(struct work_struct *work)
11301138
{
11311139
struct urb_node *unode = container_of(work, struct urb_node,
@@ -1134,14 +1142,9 @@ static void ufx_release_urb_work(struct work_struct *work)
11341142
up(&unode->dev->urbs.limit_sem);
11351143
}
11361144

1137-
static void ufx_free_framebuffer_work(struct work_struct *work)
1145+
static void ufx_free_framebuffer(struct ufx_data *dev)
11381146
{
1139-
struct ufx_data *dev = container_of(work, struct ufx_data,
1140-
free_framebuffer_work.work);
11411147
struct fb_info *info = dev->info;
1142-
int node = info->node;
1143-
1144-
unregister_framebuffer(info);
11451148

11461149
if (info->cmap.len != 0)
11471150
fb_dealloc_cmap(&info->cmap);
@@ -1153,11 +1156,6 @@ static void ufx_free_framebuffer_work(struct work_struct *work)
11531156

11541157
dev->info = NULL;
11551158

1156-
/* Assume info structure is freed after this point */
1157-
framebuffer_release(info);
1158-
1159-
pr_debug("fb_info for /dev/fb%d has been freed", node);
1160-
11611159
/* ref taken in probe() as part of registering framebfufer */
11621160
kref_put(&dev->kref, ufx_free);
11631161
}
@@ -1169,11 +1167,13 @@ static int ufx_ops_release(struct fb_info *info, int user)
11691167
{
11701168
struct ufx_data *dev = info->par;
11711169

1170+
mutex_lock(&disconnect_mutex);
1171+
11721172
dev->fb_count--;
11731173

11741174
/* We can't free fb_info here - fbmem will touch it when we return */
11751175
if (dev->virtualized && (dev->fb_count == 0))
1176-
schedule_delayed_work(&dev->free_framebuffer_work, HZ);
1176+
ufx_free_framebuffer(dev);
11771177

11781178
if ((dev->fb_count == 0) && (info->fbdefio)) {
11791179
fb_deferred_io_cleanup(info);
@@ -1186,6 +1186,8 @@ static int ufx_ops_release(struct fb_info *info, int user)
11861186

11871187
kref_put(&dev->kref, ufx_free);
11881188

1189+
mutex_unlock(&disconnect_mutex);
1190+
11891191
return 0;
11901192
}
11911193

@@ -1292,6 +1294,7 @@ static const struct fb_ops ufx_ops = {
12921294
.fb_blank = ufx_ops_blank,
12931295
.fb_check_var = ufx_ops_check_var,
12941296
.fb_set_par = ufx_ops_set_par,
1297+
.fb_destroy = ufx_ops_destory,
12951298
};
12961299

12971300
/* Assumes &info->lock held by caller
@@ -1673,9 +1676,6 @@ static int ufx_usb_probe(struct usb_interface *interface,
16731676
goto destroy_modedb;
16741677
}
16751678

1676-
INIT_DELAYED_WORK(&dev->free_framebuffer_work,
1677-
ufx_free_framebuffer_work);
1678-
16791679
retval = ufx_reg_read(dev, 0x3000, &id_rev);
16801680
check_warn_goto_error(retval, "error %d reading 0x3000 register from device", retval);
16811681
dev_dbg(dev->gdev, "ID_REV register value 0x%08x", id_rev);
@@ -1748,10 +1748,12 @@ static int ufx_usb_probe(struct usb_interface *interface,
17481748
static void ufx_usb_disconnect(struct usb_interface *interface)
17491749
{
17501750
struct ufx_data *dev;
1751+
struct fb_info *info;
17511752

17521753
mutex_lock(&disconnect_mutex);
17531754

17541755
dev = usb_get_intfdata(interface);
1756+
info = dev->info;
17551757

17561758
pr_debug("USB disconnect starting\n");
17571759

@@ -1765,12 +1767,15 @@ static void ufx_usb_disconnect(struct usb_interface *interface)
17651767

17661768
/* if clients still have us open, will be freed on last close */
17671769
if (dev->fb_count == 0)
1768-
schedule_delayed_work(&dev->free_framebuffer_work, 0);
1770+
ufx_free_framebuffer(dev);
17691771

1770-
/* release reference taken by kref_init in probe() */
1771-
kref_put(&dev->kref, ufx_free);
1772+
/* this function will wait for all in-flight urbs to complete */
1773+
if (dev->urbs.count > 0)
1774+
ufx_free_urb_list(dev);
17721775

1773-
/* consider ufx_data freed */
1776+
pr_debug("freeing ufx_data %p", dev);
1777+
1778+
unregister_framebuffer(info);
17741779

17751780
mutex_unlock(&disconnect_mutex);
17761781
}

0 commit comments

Comments
 (0)