From patchwork Mon Jul 31 16:50:19 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mirsad Todorovac X-Patchwork-Id: 13335043 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 097FDC001DC for ; Mon, 31 Jul 2023 16:51:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229946AbjGaQvB (ORCPT ); Mon, 31 Jul 2023 12:51:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:50938 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229998AbjGaQu6 (ORCPT ); Mon, 31 Jul 2023 12:50:58 -0400 Received: from domac.alu.hr (domac.alu.unizg.hr [IPv6:2001:b68:2:2800::3]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DEB7B1735; Mon, 31 Jul 2023 09:50:56 -0700 (PDT) Received: from localhost (localhost [127.0.0.1]) by domac.alu.hr (Postfix) with ESMTP id 96E846017F; Mon, 31 Jul 2023 18:50:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=alu.unizg.hr; s=mail; t=1690822254; bh=3rLIRVfb/scgMMCtJC4alGZSmKoc6l8OBKb3QkoXPIU=; h=From:To:Cc:Subject:Date:From; b=PUF/iVtmZIU08a2aDfPIN0skqEx1Txl+EozEp9JqGi4WmxruBNh8ImRILa6vWtwyo APkK0zoGghojPV51aB2KWgM9EOabdDyeSQS/5l/TF1jJswZCaUZznwbqpjqH6qEE3Q hP+cz+TyJJbC8nk5pwMjKUbVD/+ww2J1ZDOS+skYe/u7eAhXDlppLXtY/qU39NGCPB Yk5YgAM8VhEJbKw2rjtrpqLwlpaeLDOkPq4SX86w2Yodl1XqOKQbR1RDkid6DMUaBY 5CkE22lI4ihLBclT9F3Puvpo3Jj3YZocdV3/UFirtlfY0YTb8RIch2aU2E2uxIANi1 ebWNfyMm8IDQQ== 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 Zq6g0yf1KWso; Mon, 31 Jul 2023 18:50:51 +0200 (CEST) Received: from defiant.. (unknown [94.250.191.183]) by domac.alu.hr (Postfix) with ESMTPSA id 9A7F760173; Mon, 31 Jul 2023 18:50:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=alu.unizg.hr; s=mail; t=1690822251; bh=3rLIRVfb/scgMMCtJC4alGZSmKoc6l8OBKb3QkoXPIU=; h=From:To:Cc:Subject:Date:From; b=Aq8PU2IzGH+6MnwlG4HTD/jS0V30NVIxx7NOpGyV4QSccZ7F0D/+dCmVX4JWM9HZ3 8fkfic55NubFV6EtNIg5gMKP4d5VlvzP69r+bu42tMc7/onh2eZSuxUIT5O9e3LR10 Tpv8AZ6Eg1iXAaSMiKSNtfIOQeAiw+J+0SA+Kj9CuKub7DXrK91FUqzTUP2mGvXBuq 9lhmAlH6HZ+PszhSXID/qrgH/vbmNS2CZ//I9V6os2EEkU5Q8YaPwVeM6j+ISGUzmN mpdwJAFyhiZbFzZ9LgUB689UctG5FY6Wtt9OlfrP8GuEjG7cEI+bQdETYAxRCEraR4 6ljqhsz/x/eVg== From: Mirsad Todorovac To: Greg Kroah-Hartman , Mirsad Todorovac , linux-kernel@vger.kernel.org Cc: "Luis R . Rodriguez" , Russ Weight , Takashi Iwai , Tianfei Zhang , Shuah Khan , Colin Ian King , Randy Dunlap , linux-kselftest@vger.kernel.org, stable@vger.kernel.org, Dan Carpenter Subject: [PATCH v1 1/1] test_firmware: prevent race conditions by a correct implementation of locking Date: Mon, 31 Jul 2023 18:50:19 +0200 Message-Id: <20230731165018.8233-1-mirsad.todorovac@alu.unizg.hr> X-Mailer: git-send-email 2.34.1 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org NOTE: This patch is tested against 5.4 stable NOTE: This is a patch for the 5.4 stable branch, not for the torvalds tree. The torvalds tree, and stable tree 5.10, 5.15, 6.1 and 6.4 branches were fixed in the separate commit ID 4acfe3dfde68 ("test_firmware: prevent race conditions by a correct implementation of locking") which was incompatible with 5.4 Dan Carpenter spotted a race condition in a couple of situations like these in the test_firmware driver: static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) { u8 val; int ret; ret = kstrtou8(buf, 10, &val); 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 ssize_t config_num_requests_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int rc; mutex_lock(&test_fw_mutex); if (test_fw_config->reqs) { pr_err("Must call release_all_firmware prior to changing config\n"); rc = -EINVAL; mutex_unlock(&test_fw_mutex); goto out; } mutex_unlock(&test_fw_mutex); // NOTE: HERE is the race!!! Function can be preempted! // test_fw_config->reqs can change between the release of // the lock about and acquire of the lock in the // test_dev_config_update_u8() rc = test_dev_config_update_u8(buf, count, &test_fw_config->num_requests); out: return rc; } static ssize_t config_read_fw_idx_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return test_dev_config_update_u8(buf, count, &test_fw_config->read_fw_idx); } The function test_dev_config_update_u8() is called from both the locked and the unlocked context, function config_num_requests_store() and config_read_fw_idx_store() which can both be called asynchronously as they are driver's methods, while test_dev_config_update_u8() and siblings change their argument pointed to by u8 *cfg or similar pointer. To avoid deadlock on test_fw_mutex, the lock is dropped before calling test_dev_config_update_u8() and re-acquired within test_dev_config_update_u8() itself, but alas this creates a race condition. Having two locks wouldn't assure a race-proof mutual exclusion. This situation is best avoided by the introduction of a new, unlocked function __test_dev_config_update_u8() which can be called from the locked context and reducing test_dev_config_update_u8() to: 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; } doing the locking and calling the unlocked primitive, which enables both locked and unlocked versions without duplication of code. Fixes: c92316bf8e948 ("test_firmware: add batched firmware tests") Cc: Luis R. Rodriguez Cc: Greg Kroah-Hartman Cc: Russ Weight Cc: Takashi Iwai Cc: Tianfei Zhang Cc: Shuah Khan Cc: Colin Ian King Cc: Randy Dunlap Cc: linux-kselftest@vger.kernel.org Cc: stable@vger.kernel.org # v5.4 Suggested-by: Dan Carpenter Link: https://lore.kernel.org/r/20230509084746.48259-1-mirsad.todorovac@alu.unizg.hr Signed-off-by: Mirsad Todorovac --- lib/test_firmware.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/test_firmware.c b/lib/test_firmware.c index 38553944e967..92d7195d5b5b 100644 --- a/lib/test_firmware.c +++ b/lib/test_firmware.c @@ -301,16 +301,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, - bool *cfg) +static inline int __test_dev_config_update_bool(const char *buf, size_t size, + bool *cfg) { int ret; - mutex_lock(&test_fw_mutex); if (strtobool(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; @@ -340,7 +350,7 @@ static ssize_t test_dev_config_show_int(char *buf, int cfg) return snprintf(buf, PAGE_SIZE, "%d\n", val); } -static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) +static inline int __test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) { int ret; long new; @@ -352,14 +362,23 @@ static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) if (new > U8_MAX) return -EINVAL; - mutex_lock(&test_fw_mutex); *(u8 *)cfg = new; - 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 cfg) { u8 val; @@ -392,10 +411,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;