diff options
author | Takashi Iwai <tiwai@suse.de> | 2016-11-17 04:49:31 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@s-opensource.com> | 2016-11-23 18:04:26 -0500 |
commit | 22a1e7783e173ab3d86018eb590107d68df46c11 (patch) | |
tree | 3beaea52d877cfab058a52b97a2e2f4d6f170b4e | |
parent | 9c763584b7c8911106bb77af7e648bef09af9d80 (diff) |
xc2028: Fix use-after-free bug properly
The commit 8dfbcc4351a0 ("[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 8dfbcc4351a0 ('[media] xc2028: avoid use after free')
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
-rw-r--r-- | drivers/media/tuners/tuner-xc2028.c | 37 |
1 files changed, 16 insertions, 21 deletions
diff --git a/drivers/media/tuners/tuner-xc2028.c b/drivers/media/tuners/tuner-xc2028.c index 317ef63ee789..8d96a22647b3 100644 --- a/drivers/media/tuners/tuner-xc2028.c +++ b/drivers/media/tuners/tuner-xc2028.c | |||
@@ -281,6 +281,14 @@ static void free_firmware(struct xc2028_data *priv) | |||
281 | int i; | 281 | int i; |
282 | tuner_dbg("%s called\n", __func__); | 282 | tuner_dbg("%s called\n", __func__); |
283 | 283 | ||
284 | /* free allocated f/w string */ | ||
285 | if (priv->fname != firmware_name) | ||
286 | kfree(priv->fname); | ||
287 | priv->fname = NULL; | ||
288 | |||
289 | priv->state = XC2028_NO_FIRMWARE; | ||
290 | memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); | ||
291 | |||
284 | if (!priv->firm) | 292 | if (!priv->firm) |
285 | return; | 293 | return; |
286 | 294 | ||
@@ -291,9 +299,6 @@ static void free_firmware(struct xc2028_data *priv) | |||
291 | 299 | ||
292 | priv->firm = NULL; | 300 | priv->firm = NULL; |
293 | priv->firm_size = 0; | 301 | priv->firm_size = 0; |
294 | priv->state = XC2028_NO_FIRMWARE; | ||
295 | |||
296 | memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); | ||
297 | } | 302 | } |
298 | 303 | ||
299 | static int load_all_firmwares(struct dvb_frontend *fe, | 304 | static int load_all_firmwares(struct dvb_frontend *fe, |
@@ -884,9 +889,8 @@ read_not_reliable: | |||
884 | return 0; | 889 | return 0; |
885 | 890 | ||
886 | fail: | 891 | fail: |
887 | priv->state = XC2028_NO_FIRMWARE; | 892 | free_firmware(priv); |
888 | 893 | ||
889 | memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); | ||
890 | if (retry_count < 8) { | 894 | if (retry_count < 8) { |
891 | msleep(50); | 895 | msleep(50); |
892 | retry_count++; | 896 | retry_count++; |
@@ -1332,11 +1336,8 @@ static int xc2028_dvb_release(struct dvb_frontend *fe) | |||
1332 | mutex_lock(&xc2028_list_mutex); | 1336 | mutex_lock(&xc2028_list_mutex); |
1333 | 1337 | ||
1334 | /* only perform final cleanup if this is the last instance */ | 1338 | /* only perform final cleanup if this is the last instance */ |
1335 | if (hybrid_tuner_report_instance_count(priv) == 1) { | 1339 | if (hybrid_tuner_report_instance_count(priv) == 1) |
1336 | free_firmware(priv); | 1340 | free_firmware(priv); |
1337 | kfree(priv->ctrl.fname); | ||
1338 | priv->ctrl.fname = NULL; | ||
1339 | } | ||
1340 | 1341 | ||
1341 | if (priv) | 1342 | if (priv) |
1342 | hybrid_tuner_release_state(priv); | 1343 | hybrid_tuner_release_state(priv); |
@@ -1399,19 +1400,8 @@ static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg) | |||
1399 | 1400 | ||
1400 | /* | 1401 | /* |
1401 | * Copy the config data. | 1402 | * Copy the config data. |
1402 | * For the firmware name, keep a local copy of the string, | ||
1403 | * in order to avoid troubles during device release. | ||
1404 | */ | 1403 | */ |
1405 | kfree(priv->ctrl.fname); | ||
1406 | priv->ctrl.fname = NULL; | ||
1407 | memcpy(&priv->ctrl, p, sizeof(priv->ctrl)); | 1404 | memcpy(&priv->ctrl, p, sizeof(priv->ctrl)); |
1408 | if (p->fname) { | ||
1409 | priv->ctrl.fname = kstrdup(p->fname, GFP_KERNEL); | ||
1410 | if (priv->ctrl.fname == NULL) { | ||
1411 | rc = -ENOMEM; | ||
1412 | goto unlock; | ||
1413 | } | ||
1414 | } | ||
1415 | 1405 | ||
1416 | /* | 1406 | /* |
1417 | * If firmware name changed, frees firmware. As free_firmware will | 1407 | * If firmware name changed, frees firmware. As free_firmware will |
@@ -1426,10 +1416,15 @@ static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg) | |||
1426 | 1416 | ||
1427 | if (priv->state == XC2028_NO_FIRMWARE) { | 1417 | if (priv->state == XC2028_NO_FIRMWARE) { |
1428 | if (!firmware_name[0]) | 1418 | if (!firmware_name[0]) |
1429 | priv->fname = priv->ctrl.fname; | 1419 | priv->fname = kstrdup(p->fname, GFP_KERNEL); |
1430 | else | 1420 | else |
1431 | priv->fname = firmware_name; | 1421 | priv->fname = firmware_name; |
1432 | 1422 | ||
1423 | if (!priv->fname) { | ||
1424 | rc = -ENOMEM; | ||
1425 | goto unlock; | ||
1426 | } | ||
1427 | |||
1433 | rc = request_firmware_nowait(THIS_MODULE, 1, | 1428 | rc = request_firmware_nowait(THIS_MODULE, 1, |
1434 | priv->fname, | 1429 | priv->fname, |
1435 | priv->i2c_props.adap->dev.parent, | 1430 | priv->i2c_props.adap->dev.parent, |