forked from rubenslte/android_kernel_samsung_msm8226
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:
committed by
Francescodario Cuzzocrea
parent
d5a0cfb8bb
commit
f6509830af
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user