From 9c2782af46306b458c08aaaacfcf47fd9419a084 Mon Sep 17 00:00:00 2001 From: Gao Rui Date: Tue, 21 Jul 2026 19:23:14 +0800 Subject: [PATCH 1/2] riscv: strnlen: fix ZBB path overflow and SIZE_MAX fallback driver inclusion category: bugfix bugzilla: https://github.com/RVCK-Project/rvck/issues/339 -------------------------------- This patch improves the RISC-V strnlen implementation: - Add explicit fallback to generic path when count == SIZE_MAX, avoiding minu instruction misbehavior. - Add overflow check for (s + count), ensuring safe fallback when address addition wraps around. - Refactor aligned boundary calculation to use (s + count - 1), preventing word loads beyond the valid range. - Add fast exit when all remaining bytes are within the first word. - Simplify generic path loop with clearer pointer/count handling. These changes fix potential off-by-one, overflow, and extreme input bugs, while keeping ZBB optimization for normal cases. Fixes: 5ba15d419fab ("riscv: lib: add strnlen() implementation") Signed-off-by: Gao Rui --- arch/riscv/lib/strnlen.S | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/arch/riscv/lib/strnlen.S b/arch/riscv/lib/strnlen.S index 53afa7b5b314..fc5d578bbffe 100644 --- a/arch/riscv/lib/strnlen.S +++ b/arch/riscv/lib/strnlen.S @@ -17,6 +17,7 @@ SYM_FUNC_START(strnlen) __ALTERNATIVE_CFG("nop", "j strnlen_zbb", 0, RISCV_ISA_EXT_ZBB, IS_ENABLED(CONFIG_RISCV_ISA_ZBB) && IS_ENABLED(CONFIG_TOOLCHAIN_HAS_ZBB)) +strnlen_generic: /* * Returns @@ -27,20 +28,24 @@ SYM_FUNC_START(strnlen) * a1 - Max length of string * * Clobbers - * t0, t1, t2 + * t0, t1 */ - addi t1, a0, -1 - add t2, a0, a1 + mv t1, a0 + 1: - addi t1, t1, 1 - beq t1, t2, 2f + beqz a1, 2f + addi a1, a1, -1 + lbu t0, 0(t1) - bnez t0, 1b + beqz t0, 2f + + addi t1, t1, 1 + j 1b + 2: sub a0, t1, a0 ret - /* * Variant of strnlen using the ZBB extension if available */ @@ -73,6 +78,16 @@ strnlen_zbb: /* If maxlen is 0, return 0. */ beqz a1, 3f + /* + * Fallback to generic implementation when count is large enough to + * cause address overflow in the ZBB optimized path, or when count + * equals SIZE_MAX where the minu instruction misbehaves. + */ + li t5, -1 + beq a1, t5, strnlen_generic /* count == SIZE_MAX */ + add t4, a0, a1 + bltu t4, a0, strnlen_generic /* a0 + a1 overflow */ + /* Number of irrelevant bytes in the first word. */ andi t2, a0, SZREG-1 @@ -83,8 +98,12 @@ strnlen_zbb: sub t3, t3, t2 slli t2, t2, 3 - /* Aligned boundary. */ - add t4, a0, a1 + /* + * Aligned boundary. Use the address of the last valid byte + * (s + count - 1) to avoid loading a word past the count + * boundary in the loop below. count == 0 is handled above. + */ + addi t4, t4, -1 andi t4, t4, -SZREG /* Get the first word. */ @@ -120,6 +139,9 @@ strnlen_zbb: bgtu t3, a0, 2f + /* All remaining bytes are in the first word, no loop needed. */ + bgeu t0, t4, 2f + /* Prepare for the word comparison loop. */ addi t2, t0, SZREG li t3, -1 From f294bd6a2757bcbf418b09e31cb767eb0242677a Mon Sep 17 00:00:00 2001 From: Gao Rui Date: Tue, 21 Jul 2026 19:38:36 +0800 Subject: [PATCH 2/2] kunit: string: add strnlen SIZE_MAX test coverage driver inclusion category: bugfix bugzilla: https://github.com/RVCK-Project/rvck/issues/339 -------------------------------- Extend string_test_strnlen to validate strnlen behavior with SIZE_MAX input: - Add explicit test case for strnlen(s, SIZE_MAX). - Ensure fallback to generic path returns correct length when string has no NUL terminator. - Complements existing tests for non-terminated strings and boundary conditions. This improves KUnit coverage for extreme inputs and verifies the correctness of the new fallback logic in strnlen.S. Signed-off-by: Gao Rui --- lib/string_kunit.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/string_kunit.c b/lib/string_kunit.c index 2a2e95886ad1..d00932ee457a 100644 --- a/lib/string_kunit.c +++ b/lib/string_kunit.c @@ -155,6 +155,16 @@ static void string_test_strnlen(struct kunit *test) for (size_t offset = 0; offset < STRING_TEST_MAX_OFFSET; offset++) { for (size_t len = 0; len <= STRING_TEST_MAX_LEN; len++) { + /* Test strings without NUL terminator */ + s = buf + buf_size - offset - len; + if (len > 0) + KUNIT_EXPECT_EQ(test, strnlen(s, len - 1), len - 1); + if (len > 1) + KUNIT_EXPECT_EQ(test, strnlen(s, len - 2), len - 2); + + KUNIT_EXPECT_EQ(test, strnlen(s, len), len); + + /* Test strings with NUL terminator */ s = buf + buf_size - 1 - offset - len; s[len] = '\0'; @@ -169,6 +179,9 @@ static void string_test_strnlen(struct kunit *test) KUNIT_EXPECT_EQ(test, strnlen(s, len + 2), len); KUNIT_EXPECT_EQ(test, strnlen(s, len + 10), len); + /* Test Count overflow fallback */ + KUNIT_EXPECT_EQ(test, strnlen(s, SSIZE_MAX), len); + s[len] = 'A'; } }