USB: f_qc_rndis: Prevent use-after-free for _rndis_qc

Assume that there are two threads, thread1 is setting
value of _rndis_qc variable in rndis_qc_bind_config_vendor
function. Thread2 jumps in and get the value of _rndis_qc
in rndis_qc_open_dev function before it is freed in
rndis_qc_bind_config_vendor function, since rndis_ipa_init
or usb_add_function failed. Use-after-free will happen as
Thread2 is referencing freed objects. To prevent this
spinlock is used where ever it is needed to protect
_rndis_qc variable.

Bug: 35136547
Change-Id: Ib45ae14281821eeaf79419e8d177cb5d51b94df8
CVE-2017-9684
Signed-off-by: Kevin F. Haggerty <haggertk@lineageos.org>
This commit is contained in:
Lena Salman
2019-08-08 12:10:20 +02:00
committed by Francescodario Cuzzocrea
parent d5a0cfb8bb
commit f6509830af
+78 -27
View File
@@ -80,7 +80,7 @@
*/
struct f_rndis_qc {
struct qc_gether port;
struct qc_gether port;
u8 ctrl_id, data_id;
u8 ethaddr[ETH_ALEN];
u32 vendorID;
@@ -88,8 +88,8 @@ struct f_rndis_qc {
u32 max_pkt_size;
const char *manufacturer;
int config;
atomic_t ioctl_excl;
atomic_t open_excl;
atomic_t ioctl_excl;
atomic_t open_excl;
struct usb_ep *notify;
struct usb_request *notify_req;
@@ -97,6 +97,8 @@ struct f_rndis_qc {
struct data_port bam_port;
};
static spinlock_t rndis_lock;
static inline struct f_rndis_qc *func_to_rndis_qc(struct usb_function *f)
{
return container_of(f, struct f_rndis_qc, port.func);
@@ -529,7 +531,7 @@ static void rndis_qc_response_available(void *_rndis)
}
static void rndis_qc_response_complete(struct usb_ep *ep,
struct usb_request *req)
struct usb_request *req)
{
struct f_rndis_qc *rndis = req->context;
int status = req->status;
@@ -672,7 +674,7 @@ invalid:
static int rndis_qc_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
struct f_rndis_qc *rndis = func_to_rndis_qc(f);
struct f_rndis_qc *rndis = func_to_rndis_qc(f);
struct usb_composite_dev *cdev = f->config->cdev;
/* we know alt == 0 */
@@ -982,6 +984,7 @@ static void
rndis_qc_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_rndis_qc *rndis = func_to_rndis_qc(f);
unsigned long flags;
pr_debug("rndis_qc_unbind: free");
bam_data_destroy(0);
@@ -995,7 +998,10 @@ rndis_qc_unbind(struct usb_configuration *c, struct usb_function *f)
kfree(rndis->notify_req->buf);
usb_ep_free_request(rndis->notify, rndis->notify_req);
spin_lock_irqsave(&rndis_lock, flags);
kfree(rndis);
_rndis_qc = NULL;
spin_unlock_irqrestore(&rndis_lock, flags);
}
/* Some controllers can't support RNDIS ... */
@@ -1103,90 +1109,133 @@ rndis_qc_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
rndis->port.func.suspend = rndis_qc_suspend;
rndis->port.func.resume = rndis_qc_resume;
_rndis_qc = rndis;
status = usb_add_function(c, &rndis->port.func);
if (status) {
goto fail;
}
_rndis_qc = rndis;
return status;
fail:
kfree(rndis);
_rndis_qc = NULL;
kfree(rndis);
_rndis_qc = NULL;
rndis_exit();
return status;
}
static int rndis_qc_open_dev(struct inode *ip, struct file *fp)
{
int ret = 0;
unsigned long flags;
pr_info("Open rndis QC driver\n");
spin_lock_irqsave(&rndis_lock, flags);
if (!_rndis_qc) {
pr_err("rndis_qc_dev not created yet\n");
return -ENODEV;
ret = -ENODEV;
goto fail;
}
if (rndis_qc_lock(&_rndis_qc->open_excl)) {
pr_err("Already opened\n");
return -EBUSY;
ret = -EBUSY;
goto fail;
}
fp->private_data = _rndis_qc;
pr_info("rndis QC file opened\n");
fail:
spin_unlock_irqrestore(&rndis_lock, flags);
return 0;
if (!ret)
pr_info("rndis QC file opened\n");
return ret;
}
static int rndis_qc_release_dev(struct inode *ip, struct file *fp)
{
struct f_rndis_qc *rndis = fp->private_data;
unsigned long flags;
pr_info("Close rndis QC file");
rndis_qc_unlock(&rndis->open_excl);
spin_lock_irqsave(&rndis_lock, flags);
if (!_rndis_qc) {
pr_err("rndis_qc_dev not present\n");
spin_unlock_irqrestore(&rndis_lock, flags);
return -ENODEV;
}
rndis_qc_unlock(&_rndis_qc->open_excl);
spin_unlock_irqrestore(&rndis_lock, flags);
return 0;
}
static long rndis_qc_ioctl(struct file *fp, unsigned cmd, unsigned long arg)
{
struct f_rndis_qc *rndis = fp->private_data;
u8 qc_max_pkt_per_xfer = 0;
u32 qc_max_pkt_size = 0;
int ret = 0;
unsigned long flags;
pr_info("Received command %d", cmd);
spin_lock_irqsave(&rndis_lock, flags);
if (!_rndis_qc) {
pr_err("rndis_qc_dev not present\n");
ret = -ENODEV;
goto fail;
}
if (rndis_qc_lock(&rndis->ioctl_excl))
return -EBUSY;
qc_max_pkt_per_xfer = _rndis_qc->max_pkt_per_xfer;
qc_max_pkt_size = _rndis_qc->max_pkt_size;
if (rndis_qc_lock(&_rndis_qc->ioctl_excl)) {
ret = -EBUSY;
goto fail;
}
spin_unlock_irqrestore(&rndis_lock, flags);
pr_info("Received command %d\n", cmd);
switch (cmd) {
case RNDIS_QC_GET_MAX_PKT_PER_XFER:
ret = copy_to_user((void __user *)arg,
&rndis->max_pkt_per_xfer,
sizeof(rndis->max_pkt_per_xfer));
&qc_max_pkt_per_xfer,
sizeof(qc_max_pkt_per_xfer));
if (ret) {
pr_err("copying to user space failed");
ret = -EFAULT;
}
pr_info("Sent max packets per xfer %d",
rndis->max_pkt_per_xfer);
qc_max_pkt_per_xfer);
break;
case RNDIS_QC_GET_MAX_PKT_SIZE:
ret = copy_to_user((void __user *)arg,
&rndis->max_pkt_size,
sizeof(rndis->max_pkt_size));
&qc_max_pkt_size,
sizeof(qc_max_pkt_size));
if (ret) {
pr_err("copying to user space failed");
ret = -EFAULT;
}
pr_debug("Sent max packet size %d",
rndis->max_pkt_size);
qc_max_pkt_size);
break;
default:
pr_err("Unsupported IOCTL");
ret = -EINVAL;
}
rndis_qc_unlock(&rndis->ioctl_excl);
spin_lock_irqsave(&rndis_lock, flags);
if (!_rndis_qc) {
pr_err("rndis_qc_dev not present\n");
ret = -ENODEV;
goto fail;
}
rndis_qc_unlock(&_rndis_qc->ioctl_excl);
fail:
spin_unlock_irqrestore(&rndis_lock, flags);
return ret;
}
@@ -1209,6 +1258,8 @@ static int rndis_qc_init(void)
pr_info("initialize rndis QC instance\n");
spin_lock_init(&rndis_lock);
ret = misc_register(&rndis_qc_device);
if (ret)
pr_err("rndis QC driver failed to register");