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 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'; } }