Message ID | 20230406015315.31505-1-mirsad.todorovac@alu.unizg.hr |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp727085vqo; Wed, 5 Apr 2023 19:38:19 -0700 (PDT) X-Google-Smtp-Source: AKy350bxAuYfVO1SfGCtyIUEQayoUuKgiiYo/eov/De9ogLsnR6WgZ0UMkBcjpGqdqHojEVQURj1 X-Received: by 2002:a17:907:1c13:b0:947:bf71:a548 with SMTP id nc19-20020a1709071c1300b00947bf71a548mr5934447ejc.4.1680748699260; Wed, 05 Apr 2023 19:38:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680748699; cv=none; d=google.com; s=arc-20160816; b=er8T355gz3IxGV1E6V2+wJKVtgVWk830QB/necKYLTEMBzOyGkYWkWAPgBNeJmpY/u NNCv5wkDJfIVXvGfr5p3JRQIvJjaj/K4jbcfcwlKXerrfpsfgGOmQECZC1LeGEaLC9ry 6tvk//UTWj09EQ0EiDteKLJKfcOrKVh2Z292WH3gCDwXRnZ4fJMkzEoHL6veAOG0RXLB f2mmQQJbnJ+69pGvkmaUEriC/RMidrKxwffGfpscHvSz636zdp4Z97RiFRdaqRd2cZkZ Jy37qwzxZYp/m0fEyqSz5iFOizPGRNA2qBJQDELqCcjTZZJpL2KdDGWTSVltswR61WyY 0sTg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from:dkim-signature:dkim-signature; bh=pRFv2Hy5rKG8S0IckUBNV9qtKgpB+vXSKM1Yi5fsDqs=; b=cub9LCHYc1jvFWpCTB7/NAoPTvBjBrutxOoA5tFGN4Ukc51DjIo1zSUkhjjduErWO7 e8VgxIr2XQLmQvu1ZrE51GsaBCfRt59DXr7ZN+s0hr81rm8J87FpSdjzXHbqDZ1o19PY WvQgh/tXeGgEPlqsnDq5U+DP0gWCvR9g1RvKnW+ratxh7/PI2Glkr22VMNdAF9lMxH41 wVDc2KnCmJrFKiYOCOfR52BrwjyJSgeZJOtUhvdA7V30AaskLuUivQIudUqd4VqofmFT n4Pm2Zma3suoihs3eEhFzAOCOZoNw/CH8WomMGoAbdruIpuJFePb26H/9oRJoiIGnqqy OXEQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=fail header.i=@alu.unizg.hr header.s=mail header.b=m8PruuXO; dkim=fail header.i=@alu.unizg.hr header.s=mail header.b=Wc6uKvlQ; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=alu.unizg.hr Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id zg19-20020a170907249300b008b17dfa77fdsi298831ejb.127.2023.04.05.19.37.35; Wed, 05 Apr 2023 19:38:19 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=fail header.i=@alu.unizg.hr header.s=mail header.b=m8PruuXO; dkim=fail header.i=@alu.unizg.hr header.s=mail header.b=Wc6uKvlQ; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=alu.unizg.hr Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234914AbjDFCgm (ORCPT <rfc822;a1648639935@gmail.com> + 99 others); Wed, 5 Apr 2023 22:36:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57264 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234923AbjDFCfE (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Wed, 5 Apr 2023 22:35:04 -0400 Received: from domac.alu.hr (domac.alu.unizg.hr [161.53.235.3]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3E07883D0 for <linux-kernel@vger.kernel.org>; Wed, 5 Apr 2023 19:35:02 -0700 (PDT) Received: from localhost (localhost [127.0.0.1]) by domac.alu.hr (Postfix) with ESMTP id 90AFB604ED; Thu, 6 Apr 2023 04:34:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=alu.unizg.hr; s=mail; t=1680748500; bh=moyjB0vCzLhLbgsmt8YV49FqUNROO5qsChsvEK2tqEw=; h=From:To:Cc:Subject:Date:From; b=m8PruuXOs0gkDxPuepXn7L3Sv+xfM980nDlvUrdRZOxqCSEWBIbCWaiWeVjGMpx1y xzMrv2Pv2RAKG6g8HMbq+/J6rmCFSYr2ZfO6uHlq7UDvCb39JzgWBlQtX6lqZtqrVG nfpn+t0SLSwM4yR9ONdJml0SmTWInXBTQxNSEi9XRf3+lpDRPXBuNXM6bfvEG1SQsF HXBLadOWaNAVbSg/j+fO1EOs1BOusk6OOvu9ikptL1aeoeY0UI2210z8Ipo2I2ElYl mhS+dxljAvfbGy3glKQ1Yh+FOeOutmXvIdhI6itZu9dGyi2hsS1m5Am9+78pCApyf5 l8+cw13kMmtJQ== X-Virus-Scanned: Debian amavisd-new at domac.alu.hr Received: from domac.alu.hr ([127.0.0.1]) by localhost (domac.alu.hr [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Jx_nZKwZw-ez; Thu, 6 Apr 2023 04:34:57 +0200 (CEST) Received: by domac.alu.hr (Postfix, from userid 1014) id 31915604EF; Thu, 6 Apr 2023 04:34:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=alu.unizg.hr; s=mail; t=1680748497; bh=moyjB0vCzLhLbgsmt8YV49FqUNROO5qsChsvEK2tqEw=; h=From:To:Cc:Subject:Date:From; b=Wc6uKvlQ6W+whn6zqWuHrfayS68UrU/JRL5RRNrPm/RX11WI7HyCnrZkmoi15HTWo n3ETlKN9S1Xhv9EfZn+eDzy3VOdV+SJvm3uvqW7wDtay6Wc2oYarPiscNf36DFqlNC k2V7/4BFgx+lzns4c8xg8h4tKELZelvy1D6ad0f0L18n/TsXD7ppDF6Htq6UJ5ZICM QAFbS/FBnIo0UpTN4uR+L0VtghXWuvkahSKpM3HLUa1y9tHsiAuAfAW62n6E0vHGYD u+he7wi/sOGCYB9nEIzSLM2KOArtmApr2Wkhyc1WtRGVbGSfqGWaH3TskSmY03rU0S 2GZ6kTnneTBrA== From: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr> To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Russ Weight <russell.h.weight@intel.com>, linux-kernel@vger.kernel.org Cc: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr>, Luis Chamberlain <mcgrof@kernel.org>, Tianfei zhang <tianfei.zhang@intel.com>, Christophe JAILLET <christophe.jaillet@wanadoo.fr>, Zhengchao Shao <shaozhengchao@huawei.com>, Colin Ian King <colin.i.king@gmail.com>, Takashi Iwai <tiwai@suse.de>, Dan Carpenter <error27@gmail.com> Subject: [PATCH v3 1/2] test_firmware: Fix some racing conditions in test_fw_config locking. Date: Thu, 6 Apr 2023 03:53:17 +0200 Message-Id: <20230406015315.31505-1-mirsad.todorovac@alu.unizg.hr> X-Mailer: git-send-email 2.30.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762392748257164369?= X-GMAIL-MSGID: =?utf-8?q?1762392748257164369?= |
Series |
[v3,1/2] test_firmware: Fix some racing conditions in test_fw_config locking.
|
|
Commit Message
Mirsad Todorovac
April 6, 2023, 1:53 a.m. UTC
Some functions were called both from locked and unlocked context, so the lock
was dropped prematurely, introducing a race condition when deadlock was avoided.
Having two locks wouldn't assure a race-proof mutual exclusion.
test_dev_config_update_bool_unlocked(), test_dev_config_update_u8_unlocked()
and test_dev_config_update_size_t_unlocked() versions of the functions were
introduced to be called from the locked contexts as a workaround without
releasing the main driver's lock and causing a race condition, much like putc()
and putc_unlocked() in stdio glibc library.
This should guarantee mutual exclusion and prevent any race conditions.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Luis Chamberlain <mcgrof@kernel.org>
Cc: Russ Weight <russell.h.weight@intel.com>
Cc: Tianfei zhang <tianfei.zhang@intel.com>
Cc: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Zhengchao Shao <shaozhengchao@huawei.com>
Cc: Colin Ian King <colin.i.king@gmail.com>
Cc: linux-kernel@vger.kernel.org
Cc: Takashi Iwai <tiwai@suse.de>
Suggested-by: Dan Carpenter <error27@gmail.com>
Signed-off-by: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr>
---
lib/test_firmware.c | 52 +++++++++++++++++++++++++++++++++------------
1 file changed, 38 insertions(+), 14 deletions(-)
Comments
On Thu, Apr 06, 2023 at 03:53:17AM +0200, Mirsad Goran Todorovac wrote: > Some functions were called both from locked and unlocked context, so the lock > was dropped prematurely, introducing a race condition when deadlock was avoided. > > Having two locks wouldn't assure a race-proof mutual exclusion. > > test_dev_config_update_bool_unlocked(), test_dev_config_update_u8_unlocked() > and test_dev_config_update_size_t_unlocked() versions of the functions were > introduced to be called from the locked contexts as a workaround without > releasing the main driver's lock and causing a race condition, much like putc() > and putc_unlocked() in stdio glibc library. > > This should guarantee mutual exclusion and prevent any race conditions. > Thanks for figuring this out! It seems like a good approach to me. However, I feel like PATCH 1/1 needs some style changes. The question you seem to be dealing with is how consistent to be and how much infrastructure to create. Don't think about that. Just fix the bug in the most minimal way possible and don't worry about being consistent. (Probably the best way to make this consistent is to change the test_dev_config_update_XXX functions into a single macro that calls the correct kstroXXX function. Then create a second macro that takes the lock and calls the first macro. But that is a clean up patch and unrelated to this bug.) > Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> > Cc: Luis Chamberlain <mcgrof@kernel.org> > Cc: Russ Weight <russell.h.weight@intel.com> > Cc: Tianfei zhang <tianfei.zhang@intel.com> > Cc: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr> > Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr> > Cc: Zhengchao Shao <shaozhengchao@huawei.com> > Cc: Colin Ian King <colin.i.king@gmail.com> > Cc: linux-kernel@vger.kernel.org > Cc: Takashi Iwai <tiwai@suse.de> > Suggested-by: Dan Carpenter <error27@gmail.com> > Signed-off-by: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr> > --- > lib/test_firmware.c | 52 +++++++++++++++++++++++++++++++++------------ > 1 file changed, 38 insertions(+), 14 deletions(-) > > diff --git a/lib/test_firmware.c b/lib/test_firmware.c > index 05ed84c2fc4c..272af8dc54b0 100644 > --- a/lib/test_firmware.c > +++ b/lib/test_firmware.c > @@ -353,16 +353,26 @@ static ssize_t config_test_show_str(char *dst, > return len; > } > > -static int test_dev_config_update_bool(const char *buf, size_t size, > +static inline int test_dev_config_update_bool_unlocked(const char *buf, size_t size, > bool *cfg) > { > int ret; > > - mutex_lock(&test_fw_mutex); > if (kstrtobool(buf, cfg) < 0) > ret = -EINVAL; > else > ret = size; > + > + return ret; > +} This change can be left out completely. > + > +static int test_dev_config_update_bool(const char *buf, size_t size, > + bool *cfg) > +{ > + int ret; > + > + mutex_lock(&test_fw_mutex); > + ret = test_dev_config_update_bool_unlocked(buf, size, cfg); > mutex_unlock(&test_fw_mutex); > > return ret; > @@ -373,7 +383,8 @@ static ssize_t test_dev_config_show_bool(char *buf, bool val) > return snprintf(buf, PAGE_SIZE, "%d\n", val); > } > > -static int test_dev_config_update_size_t(const char *buf, > +static int test_dev_config_update_size_t_unlocked( > + const char *buf, > size_t size, > size_t *cfg) > { Do not rename this function. Just add a comment that the mutext must be held. Or a WARN_ONCE(). WARN_ON_ONCE(!mutex_is_locked(&test_fw_mutex)); > @@ -384,9 +395,7 @@ static int test_dev_config_update_size_t(const char *buf, > if (ret) > return ret; > > - mutex_lock(&test_fw_mutex); > *(size_t *)cfg = new; > - mutex_unlock(&test_fw_mutex); > > /* Always return full write size even if we didn't consume all */ > return size; > @@ -402,6 +411,21 @@ static ssize_t test_dev_config_show_int(char *buf, int val) > return snprintf(buf, PAGE_SIZE, "%d\n", val); > } > > +static int test_dev_config_update_u8_unlocked(const char *buf, size_t size, u8 *cfg) > +{ > + u8 val; > + int ret; > + > + ret = kstrtou8(buf, 10, &val); > + if (ret) > + return ret; > + > + *(u8 *)cfg = val; > + > + /* Always return full write size even if we didn't consume all */ > + return size; > +} > + Just change the test_dev_config_update_u8() to not take the lock. Add the comment that the lock must be held. Change both callers to take the lock. Otherwise we end up creating too much duplicate code. > static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) > { > u8 val; regards, dan carpenter
On 6.4.2023. 16:04, Dan Carpenter wrote: > On Thu, Apr 06, 2023 at 03:53:17AM +0200, Mirsad Goran Todorovac wrote: >> Some functions were called both from locked and unlocked context, so the lock >> was dropped prematurely, introducing a race condition when deadlock was avoided. >> >> Having two locks wouldn't assure a race-proof mutual exclusion. >> >> test_dev_config_update_bool_unlocked(), test_dev_config_update_u8_unlocked() >> and test_dev_config_update_size_t_unlocked() versions of the functions were >> introduced to be called from the locked contexts as a workaround without >> releasing the main driver's lock and causing a race condition, much like putc() >> and putc_unlocked() in stdio glibc library. >> >> This should guarantee mutual exclusion and prevent any race conditions. >> > > Thanks for figuring this out! It seems like a good approach to me. > However, I feel like PATCH 1/1 needs some style changes. > > The question you seem to be dealing with is how consistent to be and how > much infrastructure to create. Don't think about that. Just fix the > bug in the most minimal way possible and don't worry about being > consistent. > > (Probably the best way to make this consistent is to change the > test_dev_config_update_XXX functions into a single macro that calls the > correct kstroXXX function. Then create a second macro that takes the > lock and calls the first macro. But that is a clean up patch and > unrelated to this bug.) > >> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> >> Cc: Luis Chamberlain <mcgrof@kernel.org> >> Cc: Russ Weight <russell.h.weight@intel.com> >> Cc: Tianfei zhang <tianfei.zhang@intel.com> >> Cc: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr> >> Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr> >> Cc: Zhengchao Shao <shaozhengchao@huawei.com> >> Cc: Colin Ian King <colin.i.king@gmail.com> >> Cc: linux-kernel@vger.kernel.org >> Cc: Takashi Iwai <tiwai@suse.de> >> Suggested-by: Dan Carpenter <error27@gmail.com> >> Signed-off-by: Mirsad Goran Todorovac <mirsad.todorovac@alu.unizg.hr> >> --- >> lib/test_firmware.c | 52 +++++++++++++++++++++++++++++++++------------ >> 1 file changed, 38 insertions(+), 14 deletions(-) >> >> diff --git a/lib/test_firmware.c b/lib/test_firmware.c >> index 05ed84c2fc4c..272af8dc54b0 100644 >> --- a/lib/test_firmware.c >> +++ b/lib/test_firmware.c >> @@ -353,16 +353,26 @@ static ssize_t config_test_show_str(char *dst, >> return len; >> } >> >> -static int test_dev_config_update_bool(const char *buf, size_t size, >> +static inline int test_dev_config_update_bool_unlocked(const char *buf, size_t size, >> bool *cfg) >> { >> int ret; >> >> - mutex_lock(&test_fw_mutex); >> if (kstrtobool(buf, cfg) < 0) >> ret = -EINVAL; >> else >> ret = size; >> + >> + return ret; >> +} > > This change can be left out completely. > >> + >> +static int test_dev_config_update_bool(const char *buf, size_t size, >> + bool *cfg) >> +{ >> + int ret; >> + >> + mutex_lock(&test_fw_mutex); >> + ret = test_dev_config_update_bool_unlocked(buf, size, cfg); >> mutex_unlock(&test_fw_mutex); >> >> return ret; >> @@ -373,7 +383,8 @@ static ssize_t test_dev_config_show_bool(char *buf, bool val) >> return snprintf(buf, PAGE_SIZE, "%d\n", val); >> } >> >> -static int test_dev_config_update_size_t(const char *buf, >> +static int test_dev_config_update_size_t_unlocked( >> + const char *buf, >> size_t size, >> size_t *cfg) >> { > > Do not rename this function. Just add a comment that the mutext must be > held. Or a WARN_ONCE(). > > WARN_ON_ONCE(!mutex_is_locked(&test_fw_mutex)); > > >> @@ -384,9 +395,7 @@ static int test_dev_config_update_size_t(const char *buf, >> if (ret) >> return ret; >> >> - mutex_lock(&test_fw_mutex); >> *(size_t *)cfg = new; >> - mutex_unlock(&test_fw_mutex); >> >> /* Always return full write size even if we didn't consume all */ >> return size; >> @@ -402,6 +411,21 @@ static ssize_t test_dev_config_show_int(char *buf, int val) >> return snprintf(buf, PAGE_SIZE, "%d\n", val); >> } >> >> +static int test_dev_config_update_u8_unlocked(const char *buf, size_t size, u8 *cfg) >> +{ >> + u8 val; >> + int ret; >> + >> + ret = kstrtou8(buf, 10, &val); >> + if (ret) >> + return ret; >> + >> + *(u8 *)cfg = val; >> + >> + /* Always return full write size even if we didn't consume all */ >> + return size; >> +} >> + > > Just change the test_dev_config_update_u8() to not take the lock. > Add the comment that the lock must be held. Change both callers to take > the lock. > > > Otherwise we end up creating too much duplicate code. > >> static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) >> { >> u8 val; > > regards, > dan carpenter Hi Mr. Carpenter, Thank you for your review. I will proceed according to your guidelines and issue the next version of the patch set. But I cannot promise it will be before the holidays - I do not want to make the gods angry either ;-) I cannot promise to try smart macros or inline functions with smart function parameters just yet. I would consider the real success if I hunt down the remaining leak and races in this driver. Despite being considered a less important one. As you have previously asserted, it is not a real security issue with a CVE, however, for completeness sake I would like to see these problems fixed. Best regards, Mirsad
On Fri, Apr 07, 2023 at 10:24:24AM +0200, Mirsad Goran Todorovac wrote: > > Hi Mr. Carpenter, > > Thank you for your review. > > I will proceed according to your guidelines and issue the next version of the > patch set. > > But I cannot promise it will be before the holidays - I do not want to make > the gods angry either ;-) > There is never a rush. > I cannot promise to try smart macros or inline functions with smart function > parameters just yet. > Don't worry about that. It just seemed like you were working towards a more general purpose infrastructure. It's just a clean up. > I would consider the real success if I hunt down the remaining leak and races > in this driver. Despite being considered a less important one. > > As you have previously asserted, it is not a real security issue with a CVE, > however, for completeness sake I would like to see these problems fixed. That's great. If you get bored and feel like giving up then just send PATCH 2/2 by itself because that one could be merged as is. regards, dan carpenter
On 07. 04. 2023. 11:03, Dan Carpenter wrote: > On Fri, Apr 07, 2023 at 10:24:24AM +0200, Mirsad Goran Todorovac wrote: >> >> Hi Mr. Carpenter, >> >> Thank you for your review. >> >> I will proceed according to your guidelines and issue the next version of the >> patch set. >> >> But I cannot promise it will be before the holidays - I do not want to make >> the gods angry either ;-) >> > > There is never a rush. > >> I cannot promise to try smart macros or inline functions with smart function >> parameters just yet. >> > > Don't worry about that. It just seemed like you were working towards > a more general purpose infrastructure. It's just a clean up. > >> I would consider the real success if I hunt down the remaining leak and races >> in this driver. Despite being considered a less important one. >> >> As you have previously asserted, it is not a real security issue with a CVE, >> however, for completeness sake I would like to see these problems fixed. > > That's great. If you get bored and feel like giving up then just send > PATCH 2/2 by itself because that one could be merged as is. Hi Mr. Carpenter, Actually, I do not like to give up easily :-) I seem to be onto something: In drivers/base/firmware_loader/main.c: 202 static void __free_fw_priv(struct kref *ref) 203 __releases(&fwc->lock) 204 { 205 struct fw_priv *fw_priv = to_fw_priv(ref); 206 struct firmware_cache *fwc = fw_priv->fwc; 207 208 pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n", 209 __func__, fw_priv->fw_name, fw_priv, fw_priv->data, 210 (unsigned int)fw_priv->size); 211 212 list_del(&fw_priv->list); 213 spin_unlock(&fwc->lock); 214 215 if (fw_is_paged_buf(fw_priv)) 216 fw_free_paged_buf(fw_priv); 217 else if (!fw_priv->allocated_size) 218 vfree(fw_priv->data); 219 220 kfree_const(fw_priv->fw_name); 221 kfree(fw_priv); 222 } This deallocates fw_priv->data only if fpriv->allocated_size == 0 217 else if (!fw_priv->allocated_size) 218 vfree(fw_priv->data); However, this function: 112 static struct fw_priv *__allocate_fw_priv(const char *fw_name, 113 struct firmware_cache *fwc, 114 void *dbuf, 115 size_t size, 116 size_t offset, 117 u32 opt_flags) 118 { 119 struct fw_priv *fw_priv; 120 121 /* For a partial read, the buffer must be preallocated. */ 122 if ((opt_flags & FW_OPT_PARTIAL) && !dbuf) 123 return NULL; 124 125 /* Only partial reads are allowed to use an offset. */ 126 if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL)) 127 return NULL; 128 129 fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); 130 if (!fw_priv) 131 return NULL; 132 133 fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); 134 if (!fw_priv->fw_name) { 135 kfree(fw_priv); 136 return NULL; 137 } 138 139 kref_init(&fw_priv->ref); 140 fw_priv->fwc = fwc; 141 fw_priv->data = dbuf; 142 fw_priv->allocated_size = size; 143 fw_priv->offset = offset; 144 fw_priv->opt_flags = opt_flags; 145 fw_state_init(fw_priv); 146 #ifdef CONFIG_FW_LOADER_USER_HELPER 147 INIT_LIST_HEAD(&fw_priv->pending_list); 148 #endif 149 150 pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); 151 152 return fw_priv; 153 } Has both set: 141 fw_priv->data = dbuf; 142 fw_priv->allocated_size = size; I suspect this could be the source of the leak? size in passed all the way down from request_firmware_into_buf(const struct firmware **firmware_p, const char *name, struct device *device, void *buf, size_t size) It is sized test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL); which is #define TEST_FIRMWARE_BUF_SIZE SZ_1K (the exact size of the leak: 1024 bytes) I did not dare to fix this, because in other contexts as xz_uncompress this test allocated_size is used with different semantics, and I am not sure what is the right way to fix this: 357 if (!fw_priv->allocated_size) 358 fw_priv->data = out_buf; so I would break this case. Possibly, the way of allocation and allocated_size could be separated? I did not expect the fix to go that deep into the kernel proper? Just to give you a progress report. I might even come up with a fix attempt, but not yet a formal patch I suppose. Best regards, Mirsad
On 07. 04. 2023. 23:08, Mirsad Goran Todorovac wrote: > On 07. 04. 2023. 11:03, Dan Carpenter wrote: >> On Fri, Apr 07, 2023 at 10:24:24AM +0200, Mirsad Goran Todorovac wrote: >>> >>> Hi Mr. Carpenter, >>> >>> Thank you for your review. >>> >>> I will proceed according to your guidelines and issue the next version of the >>> patch set. >>> >>> But I cannot promise it will be before the holidays - I do not want to make >>> the gods angry either ;-) >>> >> >> There is never a rush. >> >>> I cannot promise to try smart macros or inline functions with smart function >>> parameters just yet. >>> >> >> Don't worry about that. It just seemed like you were working towards >> a more general purpose infrastructure. It's just a clean up. >> >>> I would consider the real success if I hunt down the remaining leak and races >>> in this driver. Despite being considered a less important one. >>> >>> As you have previously asserted, it is not a real security issue with a CVE, >>> however, for completeness sake I would like to see these problems fixed. >> >> That's great. If you get bored and feel like giving up then just send >> PATCH 2/2 by itself because that one could be merged as is. > > Hi Mr. Carpenter, > > Actually, I do not like to give up easily :-) > > I seem to be onto something: > > In drivers/base/firmware_loader/main.c: > > 202 static void __free_fw_priv(struct kref *ref) > 203 __releases(&fwc->lock) > 204 { > 205 struct fw_priv *fw_priv = to_fw_priv(ref); > 206 struct firmware_cache *fwc = fw_priv->fwc; > 207 > 208 pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n", > 209 __func__, fw_priv->fw_name, fw_priv, fw_priv->data, > 210 (unsigned int)fw_priv->size); > 211 > 212 list_del(&fw_priv->list); > 213 spin_unlock(&fwc->lock); > 214 > 215 if (fw_is_paged_buf(fw_priv)) > 216 fw_free_paged_buf(fw_priv); > 217 else if (!fw_priv->allocated_size) > 218 vfree(fw_priv->data); > 219 > 220 kfree_const(fw_priv->fw_name); > 221 kfree(fw_priv); > 222 } > > This deallocates fw_priv->data only if fpriv->allocated_size == 0 > > 217 else if (!fw_priv->allocated_size) > 218 vfree(fw_priv->data); > > However, this function: > > 112 static struct fw_priv *__allocate_fw_priv(const char *fw_name, > 113 struct firmware_cache *fwc, > 114 void *dbuf, > 115 size_t size, > 116 size_t offset, > 117 u32 opt_flags) > 118 { > 119 struct fw_priv *fw_priv; > 120 > 121 /* For a partial read, the buffer must be preallocated. */ > 122 if ((opt_flags & FW_OPT_PARTIAL) && !dbuf) > 123 return NULL; > 124 > 125 /* Only partial reads are allowed to use an offset. */ > 126 if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL)) > 127 return NULL; > 128 > 129 fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); > 130 if (!fw_priv) > 131 return NULL; > 132 > 133 fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); > 134 if (!fw_priv->fw_name) { > 135 kfree(fw_priv); > 136 return NULL; > 137 } > 138 > 139 kref_init(&fw_priv->ref); > 140 fw_priv->fwc = fwc; > 141 fw_priv->data = dbuf; > 142 fw_priv->allocated_size = size; > 143 fw_priv->offset = offset; > 144 fw_priv->opt_flags = opt_flags; > 145 fw_state_init(fw_priv); > 146 #ifdef CONFIG_FW_LOADER_USER_HELPER > 147 INIT_LIST_HEAD(&fw_priv->pending_list); > 148 #endif > 149 > 150 pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); > 151 > 152 return fw_priv; > 153 } > > Has both set: > > 141 fw_priv->data = dbuf; > 142 fw_priv->allocated_size = size; > > I suspect this could be the source of the leak? > > size in passed all the way down from > > request_firmware_into_buf(const struct firmware **firmware_p, const char *name, > struct device *device, void *buf, size_t size) > > It is sized test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL); which is > > #define TEST_FIRMWARE_BUF_SIZE SZ_1K > > (the exact size of the leak: 1024 bytes) > > I did not dare to fix this, because in other contexts as xz_uncompress this > test allocated_size is used with different semantics, and I am not sure what > is the right way to fix this: > > 357 if (!fw_priv->allocated_size) > 358 fw_priv->data = out_buf; > > so I would break this case. > > Possibly, the way of allocation and allocated_size could be separated? > > I did not expect the fix to go that deep into the kernel proper? > > Just to give you a progress report. > > I might even come up with a fix attempt, but not yet a formal patch I suppose. P.S. Apparently, AFAICS, in this context: lib/test_firmware.c:lines 842-858: if (test_fw_config->partial) req->rc = request_partial_firmware_into_buf (&req->fw, req->name, req->dev, test_buf, test_fw_config->buf_size, test_fw_config->file_offset); else req->rc = request_firmware_into_buf (&req->fw, req->name, req->dev, test_buf, test_fw_config->buf_size); if (!req->fw) kfree(test_buf); we call request_firmware_into_buf(&req->fw, req->name, req->dev, test_buf, test_fw_config->buf_size); that calls drivers/base/firmware_loader/main.c:1035-1036: ret = _request_firmware(firmware_p, name, device, buf, size, 0, FW_OPT_UEVENT | FW_OPT_NOCACHE); which calls line 814-815: ret = _request_firmware_prepare(&fw, name, device, buf, size, offset, opt_flags); which calls line 748-749: ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size, offset, opt_flags); which calls line 189: tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, offset, opt_flags); which does line 112-153: static struct fw_priv *__allocate_fw_priv(const char *fw_name, struct firmware_cache *fwc, void *dbuf, size_t size, size_t offset, u32 opt_flags) { struct fw_priv *fw_priv; /* For a partial read, the buffer must be preallocated. */ if ((opt_flags & FW_OPT_PARTIAL) && !dbuf) return NULL; /* Only partial reads are allowed to use an offset. */ if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL)) return NULL; fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); if (!fw_priv) return NULL; fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); if (!fw_priv->fw_name) { kfree(fw_priv); return NULL; } kref_init(&fw_priv->ref); fw_priv->fwc = fwc; fw_priv->data = dbuf; fw_priv->allocated_size = size; fw_priv->offset = offset; fw_priv->opt_flags = opt_flags; fw_state_init(fw_priv); #ifdef CONFIG_FW_LOADER_USER_HELPER INIT_LIST_HEAD(&fw_priv->pending_list); #endif pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); return fw_priv; } Now, fw_priv->data is set to test_buf, fw_priv->allocated_size is set to test_fw_config->buf_size. When release_firmware(fw) is called, it calls in line drivers/base/firmware_loader/main.c:1081: firmware_free_data(fw) which calls lines 582-591: /* firmware holds the ownership of pages */ static void firmware_free_data(const struct firmware *fw) { /* Loaded directly? */ if (!fw->priv) { vfree(fw->data); return; } free_fw_priv(fw->priv); } fw_priv is allocated in line 129 of drivers/base/firmware_loader/main.c: fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); so vfree(fw->data) is not called. free_fw_priv(fw->priv) is in lines 224-230: void free_fw_priv(struct fw_priv *fw_priv) { struct firmware_cache *fwc = fw_priv->fwc; spin_lock(&fwc->lock); if (!kref_put(&fw_priv->ref, __free_fw_priv)) spin_unlock(&fwc->lock); } which calls __free_fw_priv(struct kref *ref), lines 202-222: static void __free_fw_priv(struct kref *ref) __releases(&fwc->lock) { struct fw_priv *fw_priv = to_fw_priv(ref); struct firmware_cache *fwc = fw_priv->fwc; pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n", __func__, fw_priv->fw_name, fw_priv, fw_priv->data, (unsigned int)fw_priv->size); list_del(&fw_priv->list); spin_unlock(&fwc->lock); if (fw_is_paged_buf(fw_priv)) fw_free_paged_buf(fw_priv); else if (!fw_priv->allocated_size) vfree(fw_priv->data); kfree_const(fw_priv->fw_name); kfree(fw_priv); } This has this construct: 215 if (fw_is_paged_buf(fw_priv)) 216 fw_free_paged_buf(fw_priv); 217 else if (!fw_priv->allocated_size) 218 vfree(fw_priv->data); but as fw->priv was set to test_fw_config->buf_size with the line 141 fw_priv->data = dbuf; 142 fw_priv->allocated_size = size; apparently vfree(fw_priv->data) is never being called for the firmware loaded with req-rc = request_firmware_into_buf(&req->fw, req->name, req->dev, test_buf, test_fw_config->buf_size); so IMHO we need to release it on the side of the caller, for this is what causes the leaks sized test_fw_config->buf_size = TEST_FIRMWARE_BUF_SIZE where TEST_FIRMWARE_BUF_SIZE is #define TEST_FIRMWARE_BUF_SIZE SZ_1K that is, the actual size of the memleaks: unreferenced object 0xffff9e011c39f000 (size 1024): comm "test_firmware-2", pid 27646, jiffies 4302559254 (age 466.216s) hex dump (first 32 bytes): 45 46 47 48 34 35 36 37 0a 00 00 00 00 00 00 00 EFGH4567........ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace: [<ffffffff9390d59c>] slab_post_alloc_hook+0x8c/0x4f0 [<ffffffff93914a69>] __kmem_cache_alloc_node+0x1d9/0x2a0 [<ffffffff93883a9e>] kmalloc_trace+0x2e/0xc0 [<ffffffff93cd1760>] test_fw_run_batch_request+0x90/0x170 [<ffffffff935d901a>] kthread+0x11a/0x150 [<ffffffff93402fc9>] ret_from_fork+0x29/0x50 (71-73 of them per run of tools/testing/selftest/firmware/fw_filesystem.sh) I hope this helps. Best regards, Mirsad
On 08. 04. 2023. 11:33, Mirsad Goran Todorovac wrote: > On 07. 04. 2023. 23:08, Mirsad Goran Todorovac wrote: >> On 07. 04. 2023. 11:03, Dan Carpenter wrote: >>> On Fri, Apr 07, 2023 at 10:24:24AM +0200, Mirsad Goran Todorovac wrote: >>>> >>>> Hi Mr. Carpenter, >>>> >>>> Thank you for your review. >>>> >>>> I will proceed according to your guidelines and issue the next version of the >>>> patch set. >>>> >>>> But I cannot promise it will be before the holidays - I do not want to make >>>> the gods angry either ;-) >>>> >>> >>> There is never a rush. >>> >>>> I cannot promise to try smart macros or inline functions with smart function >>>> parameters just yet. >>>> >>> >>> Don't worry about that. It just seemed like you were working towards >>> a more general purpose infrastructure. It's just a clean up. >>> >>>> I would consider the real success if I hunt down the remaining leak and races >>>> in this driver. Despite being considered a less important one. >>>> >>>> As you have previously asserted, it is not a real security issue with a CVE, >>>> however, for completeness sake I would like to see these problems fixed. >>> >>> That's great. If you get bored and feel like giving up then just send >>> PATCH 2/2 by itself because that one could be merged as is. >> >> Hi Mr. Carpenter, >> >> Actually, I do not like to give up easily :-) >> >> I seem to be onto something: >> >> In drivers/base/firmware_loader/main.c: >> >> 202 static void __free_fw_priv(struct kref *ref) >> 203 __releases(&fwc->lock) >> 204 { >> 205 struct fw_priv *fw_priv = to_fw_priv(ref); >> 206 struct firmware_cache *fwc = fw_priv->fwc; >> 207 >> 208 pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n", >> 209 __func__, fw_priv->fw_name, fw_priv, fw_priv->data, >> 210 (unsigned int)fw_priv->size); >> 211 >> 212 list_del(&fw_priv->list); >> 213 spin_unlock(&fwc->lock); >> 214 >> 215 if (fw_is_paged_buf(fw_priv)) >> 216 fw_free_paged_buf(fw_priv); >> 217 else if (!fw_priv->allocated_size) >> 218 vfree(fw_priv->data); >> 219 >> 220 kfree_const(fw_priv->fw_name); >> 221 kfree(fw_priv); >> 222 } >> >> This deallocates fw_priv->data only if fpriv->allocated_size == 0 >> >> 217 else if (!fw_priv->allocated_size) >> 218 vfree(fw_priv->data); >> >> However, this function: >> >> 112 static struct fw_priv *__allocate_fw_priv(const char *fw_name, >> 113 struct firmware_cache *fwc, >> 114 void *dbuf, >> 115 size_t size, >> 116 size_t offset, >> 117 u32 opt_flags) >> 118 { >> 119 struct fw_priv *fw_priv; >> 120 >> 121 /* For a partial read, the buffer must be preallocated. */ >> 122 if ((opt_flags & FW_OPT_PARTIAL) && !dbuf) >> 123 return NULL; >> 124 >> 125 /* Only partial reads are allowed to use an offset. */ >> 126 if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL)) >> 127 return NULL; >> 128 >> 129 fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); >> 130 if (!fw_priv) >> 131 return NULL; >> 132 >> 133 fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); >> 134 if (!fw_priv->fw_name) { >> 135 kfree(fw_priv); >> 136 return NULL; >> 137 } >> 138 >> 139 kref_init(&fw_priv->ref); >> 140 fw_priv->fwc = fwc; >> 141 fw_priv->data = dbuf; >> 142 fw_priv->allocated_size = size; >> 143 fw_priv->offset = offset; >> 144 fw_priv->opt_flags = opt_flags; >> 145 fw_state_init(fw_priv); >> 146 #ifdef CONFIG_FW_LOADER_USER_HELPER >> 147 INIT_LIST_HEAD(&fw_priv->pending_list); >> 148 #endif >> 149 >> 150 pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); >> 151 >> 152 return fw_priv; >> 153 } >> >> Has both set: >> >> 141 fw_priv->data = dbuf; >> 142 fw_priv->allocated_size = size; >> >> I suspect this could be the source of the leak? >> >> size in passed all the way down from >> >> request_firmware_into_buf(const struct firmware **firmware_p, const char *name, >> struct device *device, void *buf, size_t size) >> >> It is sized test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL); which is >> >> #define TEST_FIRMWARE_BUF_SIZE SZ_1K >> >> (the exact size of the leak: 1024 bytes) >> >> I did not dare to fix this, because in other contexts as xz_uncompress this >> test allocated_size is used with different semantics, and I am not sure what >> is the right way to fix this: >> >> 357 if (!fw_priv->allocated_size) >> 358 fw_priv->data = out_buf; >> >> so I would break this case. >> >> Possibly, the way of allocation and allocated_size could be separated? >> >> I did not expect the fix to go that deep into the kernel proper? >> >> Just to give you a progress report. >> >> I might even come up with a fix attempt, but not yet a formal patch I suppose. > > P.S. > > Apparently, AFAICS, in this context: > > lib/test_firmware.c:lines 842-858: > if (test_fw_config->partial) > req->rc = request_partial_firmware_into_buf > (&req->fw, > req->name, > req->dev, > test_buf, > test_fw_config->buf_size, > test_fw_config->file_offset); > else > req->rc = request_firmware_into_buf > (&req->fw, > req->name, > req->dev, > test_buf, > test_fw_config->buf_size); > if (!req->fw) > kfree(test_buf); > > we call > > request_firmware_into_buf(&req->fw, req->name, req->dev, test_buf, test_fw_config->buf_size); > > that calls drivers/base/firmware_loader/main.c:1035-1036: > > ret = _request_firmware(firmware_p, name, device, buf, size, 0, > FW_OPT_UEVENT | FW_OPT_NOCACHE); > > which calls line 814-815: > > ret = _request_firmware_prepare(&fw, name, device, buf, size, > offset, opt_flags); > > which calls line 748-749: > > ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size, > offset, opt_flags); > > which calls line 189: > > tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, offset, opt_flags); > > which does line 112-153: > > static struct fw_priv *__allocate_fw_priv(const char *fw_name, > struct firmware_cache *fwc, > void *dbuf, > size_t size, > size_t offset, > u32 opt_flags) > { > struct fw_priv *fw_priv; > > /* For a partial read, the buffer must be preallocated. */ > if ((opt_flags & FW_OPT_PARTIAL) && !dbuf) > return NULL; > > /* Only partial reads are allowed to use an offset. */ > if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL)) > return NULL; > > fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); > if (!fw_priv) > return NULL; > > fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC); > if (!fw_priv->fw_name) { > kfree(fw_priv); > return NULL; > } > > kref_init(&fw_priv->ref); > fw_priv->fwc = fwc; > fw_priv->data = dbuf; > fw_priv->allocated_size = size; > fw_priv->offset = offset; > fw_priv->opt_flags = opt_flags; > fw_state_init(fw_priv); > #ifdef CONFIG_FW_LOADER_USER_HELPER > INIT_LIST_HEAD(&fw_priv->pending_list); > #endif > > pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); > > return fw_priv; > } > > Now, fw_priv->data is set to test_buf, fw_priv->allocated_size is set to test_fw_config->buf_size. > > When release_firmware(fw) is called, it calls in line drivers/base/firmware_loader/main.c:1081: > > firmware_free_data(fw) which calls lines 582-591: > > /* firmware holds the ownership of pages */ > static void firmware_free_data(const struct firmware *fw) > { > /* Loaded directly? */ > if (!fw->priv) { > vfree(fw->data); > return; > } > free_fw_priv(fw->priv); > } > > fw_priv is allocated in line 129 of drivers/base/firmware_loader/main.c: > > fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); > > so vfree(fw->data) is not called. > > free_fw_priv(fw->priv) is in lines 224-230: > > void free_fw_priv(struct fw_priv *fw_priv) > { > struct firmware_cache *fwc = fw_priv->fwc; > spin_lock(&fwc->lock); > if (!kref_put(&fw_priv->ref, __free_fw_priv)) > spin_unlock(&fwc->lock); > } > > which calls __free_fw_priv(struct kref *ref), lines 202-222: > > static void __free_fw_priv(struct kref *ref) > __releases(&fwc->lock) > { > struct fw_priv *fw_priv = to_fw_priv(ref); > struct firmware_cache *fwc = fw_priv->fwc; > > pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n", > __func__, fw_priv->fw_name, fw_priv, fw_priv->data, > (unsigned int)fw_priv->size); > > list_del(&fw_priv->list); > spin_unlock(&fwc->lock); > > if (fw_is_paged_buf(fw_priv)) > fw_free_paged_buf(fw_priv); > else if (!fw_priv->allocated_size) > vfree(fw_priv->data); > > kfree_const(fw_priv->fw_name); > kfree(fw_priv); > } > > This has this construct: > > 215 if (fw_is_paged_buf(fw_priv)) > 216 fw_free_paged_buf(fw_priv); > 217 else if (!fw_priv->allocated_size) > 218 vfree(fw_priv->data); > > but as fw->priv was set to test_fw_config->buf_size with the line > > 141 fw_priv->data = dbuf; > 142 fw_priv->allocated_size = size; > > apparently vfree(fw_priv->data) is never being called for the firmware loaded > with > > req-rc = request_firmware_into_buf(&req->fw, req->name, req->dev, test_buf, > test_fw_config->buf_size); > > so IMHO we need to release it on the side of the caller, for this is what causes > the leaks sized test_fw_config->buf_size = TEST_FIRMWARE_BUF_SIZE where > > TEST_FIRMWARE_BUF_SIZE is > > #define TEST_FIRMWARE_BUF_SIZE SZ_1K > > that is, the actual size of the memleaks: > > unreferenced object 0xffff9e011c39f000 (size 1024): > comm "test_firmware-2", pid 27646, jiffies 4302559254 (age 466.216s) > hex dump (first 32 bytes): > 45 46 47 48 34 35 36 37 0a 00 00 00 00 00 00 00 EFGH4567........ > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ > backtrace: > [<ffffffff9390d59c>] slab_post_alloc_hook+0x8c/0x4f0 > [<ffffffff93914a69>] __kmem_cache_alloc_node+0x1d9/0x2a0 > [<ffffffff93883a9e>] kmalloc_trace+0x2e/0xc0 > [<ffffffff93cd1760>] test_fw_run_batch_request+0x90/0x170 > [<ffffffff935d901a>] kthread+0x11a/0x150 > [<ffffffff93402fc9>] ret_from_fork+0x29/0x50 > > (71-73 of them per run of tools/testing/selftest/firmware/fw_filesystem.sh) > > I hope this helps. This analysis really helped, and while waiting for the reply I have actually came upon a fix: (This v4 probably needs some style changes, as much as I tried to blend in the present code ...). --- diff --git a/lib/test_firmware.c b/lib/test_firmware.c index 05ed84c2fc4c..1d7d480b8eeb 100644 --- a/lib/test_firmware.c +++ b/lib/test_firmware.c @@ -45,6 +45,7 @@ struct test_batched_req { bool sent; const struct firmware *fw; const char *name; + const char *fw_buf; struct completion completion; struct task_struct *task; struct device *dev; @@ -175,8 +176,14 @@ static void __test_release_all_firmware(void) for (i = 0; i < test_fw_config->num_requests; i++) { req = &test_fw_config->reqs[i]; - if (req->fw) + if (req->fw) { + if (req->fw_buf) { + kfree_const(req->fw_buf); + req->fw_buf = NULL; + } release_firmware(req->fw); + req->fw = NULL; + } } vfree(test_fw_config->reqs); @@ -353,16 +360,26 @@ static ssize_t config_test_show_str(char *dst, return len; } -static int test_dev_config_update_bool(const char *buf, size_t size, +static inline int __test_dev_config_update_bool(const char *buf, size_t size, bool *cfg) { int ret; - mutex_lock(&test_fw_mutex); if (kstrtobool(buf, cfg) < 0) ret = -EINVAL; else ret = size; + + return ret; +} + +static int test_dev_config_update_bool(const char *buf, size_t size, + bool *cfg) +{ + int ret; + + mutex_lock(&test_fw_mutex); + ret = __test_dev_config_update_bool(buf, size, cfg); mutex_unlock(&test_fw_mutex); return ret; @@ -373,7 +390,8 @@ static ssize_t test_dev_config_show_bool(char *buf, bool val) return snprintf(buf, PAGE_SIZE, "%d\n", val); } -static int test_dev_config_update_size_t(const char *buf, +static int __test_dev_config_update_size_t( + const char *buf, size_t size, size_t *cfg) { @@ -384,9 +402,7 @@ static int test_dev_config_update_size_t(const char *buf, if (ret) return ret; - mutex_lock(&test_fw_mutex); *(size_t *)cfg = new; - mutex_unlock(&test_fw_mutex); /* Always return full write size even if we didn't consume all */ return size; @@ -402,7 +418,7 @@ static ssize_t test_dev_config_show_int(char *buf, int val) return snprintf(buf, PAGE_SIZE, "%d\n", val); } -static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) +static int __test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) { u8 val; int ret; @@ -411,14 +427,23 @@ static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) if (ret) return ret; - mutex_lock(&test_fw_mutex); *(u8 *)cfg = val; - mutex_unlock(&test_fw_mutex); /* Always return full write size even if we didn't consume all */ return size; } +static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) +{ + int ret; + + mutex_lock(&test_fw_mutex); + ret = __test_dev_config_update_u8(buf, size, cfg); + mutex_unlock(&test_fw_mutex); + + return ret; +} + static ssize_t test_dev_config_show_u8(char *buf, u8 val) { return snprintf(buf, PAGE_SIZE, "%u\n", val); @@ -471,10 +496,10 @@ static ssize_t config_num_requests_store(struct device *dev, mutex_unlock(&test_fw_mutex); goto out; } - mutex_unlock(&test_fw_mutex); - rc = test_dev_config_update_u8(buf, count, - &test_fw_config->num_requests); + rc = __test_dev_config_update_u8(buf, count, + &test_fw_config->num_requests); + mutex_unlock(&test_fw_mutex); out: return rc; @@ -518,10 +543,10 @@ static ssize_t config_buf_size_store(struct device *dev, mutex_unlock(&test_fw_mutex); goto out; } - mutex_unlock(&test_fw_mutex); - rc = test_dev_config_update_size_t(buf, count, - &test_fw_config->buf_size); + rc = __test_dev_config_update_size_t(buf, count, + &test_fw_config->buf_size); + mutex_unlock(&test_fw_mutex); out: return rc; @@ -548,10 +573,10 @@ static ssize_t config_file_offset_store(struct device *dev, mutex_unlock(&test_fw_mutex); goto out; } - mutex_unlock(&test_fw_mutex); - rc = test_dev_config_update_size_t(buf, count, - &test_fw_config->file_offset); + rc = __test_dev_config_update_size_t(buf, count, + &test_fw_config->file_offset); + mutex_unlock(&test_fw_mutex); out: return rc; @@ -652,6 +677,8 @@ static ssize_t trigger_request_store(struct device *dev, mutex_lock(&test_fw_mutex); release_firmware(test_firmware); + if (test_fw_config->reqs) + __test_release_all_firmware(); test_firmware = NULL; rc = request_firmware(&test_firmware, name, dev); if (rc) { @@ -752,6 +779,8 @@ static ssize_t trigger_async_request_store(struct device *dev, mutex_lock(&test_fw_mutex); release_firmware(test_firmware); test_firmware = NULL; + if (test_fw_config->reqs) + __test_release_all_firmware(); rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL, NULL, trigger_async_request_cb); if (rc) { @@ -794,6 +823,8 @@ static ssize_t trigger_custom_fallback_store(struct device *dev, mutex_lock(&test_fw_mutex); release_firmware(test_firmware); + if (test_fw_config->reqs) + __test_release_all_firmware(); test_firmware = NULL; rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOUEVENT, name, dev, GFP_KERNEL, NULL, @@ -856,6 +887,8 @@ static int test_fw_run_batch_request(void *data) test_fw_config->buf_size); if (!req->fw) kfree(test_buf); + else + req->fw_buf = test_buf; } else { req->rc = test_fw_config->req_firmware(&req->fw, req->name, @@ -895,6 +928,11 @@ static ssize_t trigger_batched_requests_store(struct device *dev, mutex_lock(&test_fw_mutex); + if (test_fw_config->reqs) { + rc = -EBUSY; + goto out_bail; + } + test_fw_config->reqs = vzalloc(array3_size(sizeof(struct test_batched_req), test_fw_config->num_requests, 2)); @@ -911,6 +949,7 @@ static ssize_t trigger_batched_requests_store(struct device *dev, req->fw = NULL; req->idx = i; req->name = test_fw_config->name; + req->fw_buf = NULL; req->dev = dev; init_completion(&req->completion); req->task = kthread_run(test_fw_run_batch_request, req, @@ -993,6 +1032,11 @@ ssize_t trigger_batched_requests_async_store(struct device *dev, mutex_lock(&test_fw_mutex); + if (test_fw_config->reqs) { + rc = -EBUSY; + goto out_bail; + } + test_fw_config->reqs = vzalloc(array3_size(sizeof(struct test_batched_req), test_fw_config->num_requests, 2)); @@ -1010,6 +1054,7 @@ ssize_t trigger_batched_requests_async_store(struct device *dev, for (i = 0; i < test_fw_config->num_requests; i++) { req = &test_fw_config->reqs[i]; req->name = test_fw_config->name; + req->fw_buf = NULL; req->fw = NULL; req->idx = i; init_completion(&req->completion); -- Best regards, Mirsad
diff --git a/lib/test_firmware.c b/lib/test_firmware.c index 05ed84c2fc4c..272af8dc54b0 100644 --- a/lib/test_firmware.c +++ b/lib/test_firmware.c @@ -353,16 +353,26 @@ static ssize_t config_test_show_str(char *dst, return len; } -static int test_dev_config_update_bool(const char *buf, size_t size, +static inline int test_dev_config_update_bool_unlocked(const char *buf, size_t size, bool *cfg) { int ret; - mutex_lock(&test_fw_mutex); if (kstrtobool(buf, cfg) < 0) ret = -EINVAL; else ret = size; + + return ret; +} + +static int test_dev_config_update_bool(const char *buf, size_t size, + bool *cfg) +{ + int ret; + + mutex_lock(&test_fw_mutex); + ret = test_dev_config_update_bool_unlocked(buf, size, cfg); mutex_unlock(&test_fw_mutex); return ret; @@ -373,7 +383,8 @@ static ssize_t test_dev_config_show_bool(char *buf, bool val) return snprintf(buf, PAGE_SIZE, "%d\n", val); } -static int test_dev_config_update_size_t(const char *buf, +static int test_dev_config_update_size_t_unlocked( + const char *buf, size_t size, size_t *cfg) { @@ -384,9 +395,7 @@ static int test_dev_config_update_size_t(const char *buf, if (ret) return ret; - mutex_lock(&test_fw_mutex); *(size_t *)cfg = new; - mutex_unlock(&test_fw_mutex); /* Always return full write size even if we didn't consume all */ return size; @@ -402,6 +411,21 @@ static ssize_t test_dev_config_show_int(char *buf, int val) return snprintf(buf, PAGE_SIZE, "%d\n", val); } +static int test_dev_config_update_u8_unlocked(const char *buf, size_t size, u8 *cfg) +{ + u8 val; + int ret; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + *(u8 *)cfg = val; + + /* Always return full write size even if we didn't consume all */ + return size; +} + static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) { u8 val; @@ -471,10 +495,10 @@ static ssize_t config_num_requests_store(struct device *dev, mutex_unlock(&test_fw_mutex); goto out; } - mutex_unlock(&test_fw_mutex); - rc = test_dev_config_update_u8(buf, count, - &test_fw_config->num_requests); + rc = test_dev_config_update_u8_unlocked(buf, count, + &test_fw_config->num_requests); + mutex_unlock(&test_fw_mutex); out: return rc; @@ -518,10 +542,10 @@ static ssize_t config_buf_size_store(struct device *dev, mutex_unlock(&test_fw_mutex); goto out; } - mutex_unlock(&test_fw_mutex); - rc = test_dev_config_update_size_t(buf, count, - &test_fw_config->buf_size); + rc = test_dev_config_update_size_t_unlocked(buf, count, + &test_fw_config->buf_size); + mutex_unlock(&test_fw_mutex); out: return rc; @@ -548,10 +572,10 @@ static ssize_t config_file_offset_store(struct device *dev, mutex_unlock(&test_fw_mutex); goto out; } - mutex_unlock(&test_fw_mutex); - rc = test_dev_config_update_size_t(buf, count, - &test_fw_config->file_offset); + rc = test_dev_config_update_size_t_unlocked(buf, count, + &test_fw_config->file_offset); + mutex_unlock(&test_fw_mutex); out: return rc;