Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752989AbdFGXrR (ORCPT ); Wed, 7 Jun 2017 19:47:17 -0400 Received: from wtarreau.pck.nerim.net ([62.212.114.60]:50486 "EHLO 1wt.eu" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752847AbdFGXEN (ORCPT ); Wed, 7 Jun 2017 19:04:13 -0400 From: Willy Tarreau To: linux-kernel@vger.kernel.org, stable@vger.kernel.org, linux@roeck-us.net Cc: Takashi Iwai , Mauro Carvalho Chehab , Willy Tarreau Subject: [PATCH 3.10 235/250] xc2028: Fix use-after-free bug properly Date: Thu, 8 Jun 2017 01:00:21 +0200 Message-Id: <1496876436-32402-236-git-send-email-w@1wt.eu> X-Mailer: git-send-email 2.8.0.rc2.1.gbe9624a In-Reply-To: <1496876436-32402-1-git-send-email-w@1wt.eu> References: <1496876436-32402-1-git-send-email-w@1wt.eu> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 3858 Lines: 127 From: Takashi Iwai commit 22a1e7783e173ab3d86018eb590107d68df46c11 upstream. 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') Signed-off-by: Takashi Iwai Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Willy Tarreau --- drivers/media/tuners/tuner-xc2028.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/media/tuners/tuner-xc2028.c b/drivers/media/tuners/tuner-xc2028.c index ab0bfc46..3a615e4 100644 --- a/drivers/media/tuners/tuner-xc2028.c +++ b/drivers/media/tuners/tuner-xc2028.c @@ -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; @@ -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, @@ -890,9 +895,9 @@ read_not_reliable: 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++; @@ -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); @@ -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 @@ -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, -- 2.8.0.rc2.1.gbe9624a