Skip to content

Commit

Permalink
xc2028: Fix use-after-free bug properly
Browse files Browse the repository at this point in the history
commit 22a1e77 upstream.

The commit 8dfbcc4 ("[media] xc2028: avoid use after free") tried
to address the reported use-after-free by clearing the reference.

However, it's clearing the wrong pointer; it sets NULL to
priv->ctrl.fname, but it's anyway overwritten by the next line
memcpy(&priv->ctrl, p, sizeof(priv->ctrl)).

OTOH, the actual code accessing the freed string is the strcmp() call
with priv->fname:
	if (!firmware_name[0] && p->fname &&
	    priv->fname && strcmp(p->fname, priv->fname))
		free_firmware(priv);

where priv->fname points to the previous file name, and this was
already freed by kfree().

For fixing the bug properly, this patch does the following:

- Keep the copy of firmware file name in only priv->fname,
  priv->ctrl.fname isn't changed;
- The allocation is done only when the firmware gets loaded;
- The kfree() is called in free_firmware() commonly

Fixes: commit 8dfbcc4 ('[media] xc2028: avoid use after free')
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
  • Loading branch information
Takashi Iwai authored and Jiri Slaby committed Jan 27, 2017
1 parent 075030b commit f093600
Showing 1 changed file with 16 additions and 20 deletions.
36 changes: 16 additions & 20 deletions drivers/media/tuners/tuner-xc2028.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,14 @@ static void free_firmware(struct xc2028_data *priv)
int i;
tuner_dbg("%s called\n", __func__);

/* free allocated f/w string */
if (priv->fname != firmware_name)
kfree(priv->fname);
priv->fname = NULL;

priv->state = XC2028_NO_FIRMWARE;
memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));

if (!priv->firm)
return;

Expand All @@ -299,9 +307,6 @@ static void free_firmware(struct xc2028_data *priv)

priv->firm = NULL;
priv->firm_size = 0;
priv->state = XC2028_NO_FIRMWARE;

memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
}

static int load_all_firmwares(struct dvb_frontend *fe,
Expand Down Expand Up @@ -890,9 +895,9 @@ static int check_firmware(struct dvb_frontend *fe, unsigned int type,
return 0;

fail:
free_firmware(priv);
priv->state = XC2028_SLEEP;

memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
if (retry_count < 8) {
msleep(50);
retry_count++;
Expand Down Expand Up @@ -1314,11 +1319,8 @@ static int xc2028_dvb_release(struct dvb_frontend *fe)
mutex_lock(&xc2028_list_mutex);

/* only perform final cleanup if this is the last instance */
if (hybrid_tuner_report_instance_count(priv) == 1) {
if (hybrid_tuner_report_instance_count(priv) == 1)
free_firmware(priv);
kfree(priv->ctrl.fname);
priv->ctrl.fname = NULL;
}

if (priv)
hybrid_tuner_release_state(priv);
Expand Down Expand Up @@ -1381,19 +1383,8 @@ static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg)

/*
* Copy the config data.
* For the firmware name, keep a local copy of the string,
* in order to avoid troubles during device release.
*/
kfree(priv->ctrl.fname);
priv->ctrl.fname = NULL;
memcpy(&priv->ctrl, p, sizeof(priv->ctrl));
if (p->fname) {
priv->ctrl.fname = kstrdup(p->fname, GFP_KERNEL);
if (priv->ctrl.fname == NULL) {
rc = -ENOMEM;
goto unlock;
}
}

/*
* If firmware name changed, frees firmware. As free_firmware will
Expand All @@ -1408,10 +1399,15 @@ static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg)

if (priv->state == XC2028_NO_FIRMWARE) {
if (!firmware_name[0])
priv->fname = priv->ctrl.fname;
priv->fname = kstrdup(p->fname, GFP_KERNEL);
else
priv->fname = firmware_name;

if (!priv->fname) {
rc = -ENOMEM;
goto unlock;
}

rc = request_firmware_nowait(THIS_MODULE, 1,
priv->fname,
priv->i2c_props.adap->dev.parent,
Expand Down

0 comments on commit f093600

Please sign in to comment.