From ff917923f4fb9c83717ba135ee47d7e4c1567bb7 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 28 Jul 2026 17:54:55 +0500 Subject: staging: rtl8723bs: fix OOB read in rtw_action_frame_parse() rtw_action_frame_parse() takes a frame_len parameter but never actually checks it before indexing into the frame body: const u8 *frame_body = frame + sizeof(struct ieee80211_hdr_3addr); ... c = frame_body[0]; ... a = frame_body[1]; frame_body already points 24 bytes (sizeof(struct ieee80211_hdr_3addr)) into frame, so reading frame_body[0] and frame_body[1] requires frame_len >= 26. A management action frame shorter than that (e.g. exactly 24 bytes, the minimum a malicious peer can send) causes a 1-2 byte out-of-bounds read. This is reachable from rtw_cfg80211_monitor_if_xmit_entry() and cfg80211_rtw_mgmt_tx() in ioctl_cfg80211.c, both of which pass attacker/user-influenced frame buffers and lengths straight through. Add the missing length check before frame_body is dereferenced. Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260728125456.32359-3-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- lib/kunit/backtrace-suppression-test.c | 198 +++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 lib/kunit/backtrace-suppression-test.c (limited to 'lib/kunit/backtrace-suppression-test.c') diff --git a/lib/kunit/backtrace-suppression-test.c b/lib/kunit/backtrace-suppression-test.c new file mode 100644 index 000000000..7a2a59c6a --- /dev/null +++ b/lib/kunit/backtrace-suppression-test.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for suppressing warning tracebacks. + * + * Copyright (C) 2024, Guenter Roeck + * Author: Guenter Roeck + */ + +#include +#include +#include +#include + +static void backtrace_suppression_test_warn_direct(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + kunit_warning_suppress(test) { + WARN(1, "This backtrace should be suppressed"); + /* + * Count must be checked inside the scope; the handle + * is not accessible after the block exits. + */ + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning()); +} + +static noinline void trigger_backtrace_warn(void) +{ + WARN(1, "This backtrace should be suppressed"); +} + +static void backtrace_suppression_test_warn_indirect(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + kunit_warning_suppress(test) { + trigger_backtrace_warn(); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } +} + +static void backtrace_suppression_test_warn_multi(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + kunit_warning_suppress(test) { + WARN(1, "This backtrace should be suppressed"); + trigger_backtrace_warn(); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2); + } +} + +static void backtrace_suppression_test_warn_on_direct(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE) && !IS_ENABLED(CONFIG_KALLSYMS)) + kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE or CONFIG_KALLSYMS"); + + kunit_warning_suppress(test) { + WARN_ON(1); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } +} + +static noinline void trigger_backtrace_warn_on(void) +{ + WARN_ON(1); +} + +static void backtrace_suppression_test_warn_on_indirect(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE)) + kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE"); + + kunit_warning_suppress(test) { + trigger_backtrace_warn_on(); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } +} + +static void backtrace_suppression_test_count(struct kunit *test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + kunit_warning_suppress(test) { + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0); + + WARN(1, "suppressed"); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + + WARN(1, "suppressed again"); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2); + } +} + +static void backtrace_suppression_test_active_state(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning()); + + kunit_warning_suppress(test) { + KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning()); + } + + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning()); + + kunit_warning_suppress(test) { + KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning()); + } + + KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning()); +} + +static void backtrace_suppression_test_multi_scope(struct kunit *test) +{ + struct kunit_suppressed_warning *sw1, *sw2; + + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE)) + kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE"); + + sw1 = kunit_start_suppress_warning(test); + trigger_backtrace_warn_on(); + WARN(1, "suppressed by sw1"); + kunit_end_suppress_warning(test, sw1); + + sw2 = kunit_start_suppress_warning(test); + WARN(1, "suppressed by sw2"); + kunit_end_suppress_warning(test, sw2); + + KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw1), 2); + KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw2), 1); +} + +struct cross_kthread_data { + bool was_active; + struct completion done; +}; + +static int cross_kthread_fn(void *data) +{ + struct cross_kthread_data *d = data; + + d->was_active = kunit_has_active_suppress_warning(); + complete(&d->done); + while (!kthread_should_stop()) + schedule(); + return 0; +} + +static void backtrace_suppression_test_cross_kthread(struct kunit *test) +{ + struct cross_kthread_data data; + struct task_struct *task; + + data.was_active = false; + init_completion(&data.done); + + kunit_warning_suppress(test) { + task = kthread_run(cross_kthread_fn, &data, "kunit-cross-test"); + KUNIT_ASSERT_FALSE(test, IS_ERR(task)); + wait_for_completion(&data.done); + kthread_stop(task); + } + + KUNIT_EXPECT_FALSE(test, data.was_active); +} + +static struct kunit_case backtrace_suppression_test_cases[] = { + KUNIT_CASE(backtrace_suppression_test_warn_direct), + KUNIT_CASE(backtrace_suppression_test_warn_indirect), + KUNIT_CASE(backtrace_suppression_test_warn_multi), + KUNIT_CASE(backtrace_suppression_test_warn_on_direct), + KUNIT_CASE(backtrace_suppression_test_warn_on_indirect), + KUNIT_CASE(backtrace_suppression_test_count), + KUNIT_CASE(backtrace_suppression_test_active_state), + KUNIT_CASE(backtrace_suppression_test_multi_scope), + KUNIT_CASE(backtrace_suppression_test_cross_kthread), + {} +}; + +static struct kunit_suite backtrace_suppression_test_suite = { + .name = "backtrace-suppression-test", + .test_cases = backtrace_suppression_test_cases, +}; +kunit_test_suites(&backtrace_suppression_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit test to verify warning backtrace suppression"); -- cgit v1.3.1