[PATCH] riscv: lib: Fix ZBB strnlen reading past count boundary

Michael Neuling posted 1 patch 2 months ago
arch/riscv/lib/strnlen.S | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
[PATCH] riscv: lib: Fix ZBB strnlen reading past count boundary
Posted by Michael Neuling 2 months ago
The ZBB-optimized strnlen loop loads one word ahead before checking the
aligned boundary:

    REG_L   t1, SZREG(t0)       // load next word
    addi    t0, t0, SZREG       // advance
    orc.b   t1, t1
    bgeu    t0, t4, 4f          // boundary check AFTER load

where t4 = (s + count) & -SZREG.  When s is aligned and count is a
multiple of SZREG, t4 equals s + count and the loop loads a full word
starting at exactly s + count.  If s + count falls on a page boundary
with the next page unmapped, this faults.

Fix by computing the aligned boundary from the last valid byte
(s + count - 1) instead of s + count.  This makes the loop stop at the
word containing the last valid byte rather than potentially loading the
word after it.  The count == 0 case is already handled by the beqz
early exit.

Also add a pre-loop guard (bgeu t0, t4) for the case where all valid
bytes fit within the first word.  With the adjusted boundary, t4 can
equal t0, and entering the loop with stale register state from the
first-word processing would produce incorrect results.

The final minu clamp ensures the result is still correct when the last
loaded word extends past s + count - 1 within the same aligned word.

Fixes: 5ba15d419fab ("riscv: lib: add strnlen() implementation")
Signed-off-by: Michael Neuling <mikey@neuling.org>
Assisted-by: Claude Opus4.6 High Thinking
---

Here is a test case that demonstrates the bug. The kernel code is pulled out 
into a standalone file for testing.

 % cat test-strnlen-single.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Minimal test: one strnlen call that triggers the ZBB over-read.
 * Run under GDB to single-step the fault.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

extern size_t kernel_strnlen(const char *s, size_t count);

int main(void)
{
	size_t page_size = sysconf(_SC_PAGESIZE);
	char *region, *start;
	size_t count = 16;  /* multiple of SZREG=8, triggers the bug */
	size_t ret;

	/* Map one page, guard page after it is unmapped */
	region = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
		      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	munmap(region + page_size, page_size);

	/* Fill with non-NUL, no terminator within count */
	memset(region, 'A', page_size);

	/* Aligned start, count reaches exactly to page end */
	start = region + page_size - count;

	printf("page=%p start=%p count=%zu end=%p (page_end=%p)\n",
	       region, start, count, start + count, region + page_size);
	printf("Calling kernel_strnlen...\n");

	/* This will fault on the buggy ZBB path */
	ret = kernel_strnlen(start, count);

	printf("Result: %zu (expected %zu)\n", ret, count);

	munmap(region, page_size);
	return 0;
}
% cat test-strnlen-zbb.S
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Standalone copy of the kernel's ZBB strnlen for userspace testing.
 * Extracted from arch/riscv/lib/strnlen.S
 */

#define SZREG	8
#define REG_L	ld

.text
.global kernel_strnlen
.type kernel_strnlen, @function

kernel_strnlen:
	/* Jump straight to ZBB path (we know we have it) */
	j	strnlen_zbb

	/*
	 * Non-ZBB fallback (byte-at-a-time)
	 */
	addi	t1, a0, -1
	add	t2, a0, a1
1:
	addi	t1, t1, 1
	beq	t1, t2, 2f
	lbu	t0, 0(t1)
	bnez	t0, 1b
2:
	sub	a0, t1, a0
	ret


strnlen_zbb:

# define CZ	ctz
# define SHIFT	srl

.option push
.option arch,+zbb

	/* If maxlen is 0, return 0. */
	beqz	a1, 3f

	/* Number of irrelevant bytes in the first word. */
	andi	t2, a0, SZREG-1

	/* Align pointer. */
	andi	t0, a0, -SZREG

	li	t3, SZREG
	sub	t3, t3, t2
	slli	t2, t2, 3

	/* Aligned boundary. */
	add	t4, a0, a1
	andi	t4, t4, -SZREG

	/* Get the first word.  */
	ld	t1, 0(t0)

	/*
	 * Shift away the partial data we loaded to remove the irrelevant bytes
	 * preceding the string with the effect of adding NUL bytes at the
	 * end of the string's first word.
	 */
	srl	t1, t1, t2

	/* Convert non-NUL into 0xff and NUL into 0x00. */
	orc.b	t1, t1

	/* Convert non-NUL into 0x00 and NUL into 0xff. */
	not	t1, t1

	/*
	 * Search for the first set bit (corresponding to a NUL byte in the
	 * original chunk).
	 */
	ctz	t1, t1

	/*
	 * The first chunk is special: compare against the number
	 * of valid bytes in this chunk.
	 */
	srli	a0, t1, 3

	/* Limit the result by maxlen. */
	minu	a0, a0, a1

	bgtu	t3, a0, 2f

	/* Prepare for the word comparison loop. */
	addi	t2, t0, SZREG
	li	t3, -1

	/*
	 * Our critical loop is 4 instructions and processes data in
	 * 4 byte or 8 byte chunks.
	 */
	.p2align 3
1:
	ld	t1, SZREG(t0)
	addi	t0, t0, SZREG
	orc.b	t1, t1
	bgeu	t0, t4, 4f
	beq	t1, t3, 1b
4:
	not	t1, t1
	ctz	t1, t1
	srli	t1, t1, 3

	/* Get number of processed bytes. */
	sub	t2, t0, t2

	/* Add number of characters in the first word.  */
	add	a0, a0, t2

	/* Add number of characters in the last word.  */
	add	a0, a0, t1

	/* Ensure the final result does not exceed maxlen. */
	minu	a0, a0, a1
2:
	ret
3:
	mv	a0, a1
	ret

.option pop

.size kernel_strnlen, .-kernel_strnlen
% riscv64-linux-gnu-gcc -march=rv64gc_zbb -O0 -g -static -o test-strnlen-single-rv64 test-strnlen-single.c test-strnlen-zbb.S
% qemu-riscv64 -cpu rv64,zbb=true ./test-strnlen-single-rv64
page=0x7ff6d698c000 start=0x7ff6d698cff0 count=16 end=0x7ff6d698d000 (page_end=0x7ff6d698d000)
Calling kernel_strnlen...
Segmentation fault (core dumped)
%

 arch/riscv/lib/strnlen.S | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/arch/riscv/lib/strnlen.S b/arch/riscv/lib/strnlen.S
index 53afa7b5b3..a8911605c2 100644
--- a/arch/riscv/lib/strnlen.S
+++ b/arch/riscv/lib/strnlen.S
@@ -83,8 +83,13 @@ strnlen_zbb:
 	sub	t3, t3, t2
 	slli	t2, t2, 3
 
-	/* Aligned boundary. */
+	/*
+	 * 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.
+	 */
 	add	t4, a0, a1
+	addi	t4, t4, -1
 	andi	t4, t4, -SZREG
 
 	/* Get the first word.  */
@@ -120,6 +125,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
-- 
2.43.0
Re: [PATCH] riscv: lib: Fix ZBB strnlen reading past count boundary
Posted by Feng Jiang 2 months ago
On 2026/4/13 09:07, Michael Neuling wrote:
> The ZBB-optimized strnlen loop loads one word ahead before checking the
> aligned boundary:
> 
>     REG_L   t1, SZREG(t0)       // load next word
>     addi    t0, t0, SZREG       // advance
>     orc.b   t1, t1
>     bgeu    t0, t4, 4f          // boundary check AFTER load
> 
> where t4 = (s + count) & -SZREG.  When s is aligned and count is a
> multiple of SZREG, t4 equals s + count and the loop loads a full word
> starting at exactly s + count.  If s + count falls on a page boundary
> with the next page unmapped, this faults.
> 
> Fix by computing the aligned boundary from the last valid byte
> (s + count - 1) instead of s + count.  This makes the loop stop at the
> word containing the last valid byte rather than potentially loading the
> word after it.  The count == 0 case is already handled by the beqz
> early exit.
> 
> Also add a pre-loop guard (bgeu t0, t4) for the case where all valid
> bytes fit within the first word.  With the adjusted boundary, t4 can
> equal t0, and entering the loop with stale register state from the
> first-word processing would produce incorrect results.
> 
> The final minu clamp ensures the result is still correct when the last
> loaded word extends past s + count - 1 within the same aligned word.
> 
> Fixes: 5ba15d419fab ("riscv: lib: add strnlen() implementation")
> Signed-off-by: Michael Neuling <mikey@neuling.org>
> Assisted-by: Claude Opus4.6 High Thinking
> ---
> ...

Hi Michael,

Thanks for catching and fixing this! Your analysis is spot on—that
"load-before-check" logic was indeed an oversight on my part, especially
regarding the page boundary edge case.

The test case you provided is extremely helpful. Since you've already
built this reproducer, would you be interested in helping to improve
the KUnit test string_test_strnlen() in lib/tests/string_kunit.c as well?
Currently, it mainly tests strings with NUL terminators and lacks coverage
for these kinds of non-terminated boundary scenarios.

Thanks again!

-- 
With Best Regards,
Feng Jiang

Re: [PATCH] riscv: lib: Fix ZBB strnlen reading past count boundary
Posted by Michael Neuling 2 months ago
> Thanks for catching and fixing this! Your analysis is spot on—that
> "load-before-check" logic was indeed an oversight on my part, especially
> regarding the page boundary edge case.

No worries.

> The test case you provided is extremely helpful. Since you've already
> built this reproducer, would you be interested in helping to improve
> the KUnit test string_test_strnlen() in lib/tests/string_kunit.c as well?
> Currently, it mainly tests strings with NUL terminators and lacks coverage
> for these kinds of non-terminated boundary scenarios.

The below is from Claude. I gave it a test under qemu riscv with and without 
the patch and it seems to catch the failure. Feel free to use it as you see fit.

  [   19.042129] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
  [   19.043133] Oops [#1]
  [   66.197273]     ok 5 string_test_strnlen
  [   66.197855] Unable to handle kernel paging request at virtual address ff20000000096000
  [   66.198980] Oops [#2]
  [   66.199867]  ra : string_test_strnlen_page_boundary+0xba/0x244
  [   66.204025]     # string_test_strnlen_page_boundary: try faulted: last line seen lib/tests/string_kunit.c:195
  [   66.204391]     # string_test_strnlen_page_boundary: internal error occurred preventing test case from running: -4
  [   66.205133]     not ok 6 string_test_strnlen_page_boundary
  [   66.227546] # string: pass:24 fail:1 skip:4 total:29
  [   66.227879] not ok 74 string

Mikey

From b4933270b53e3acccea707b6dced352ee525828f Mon Sep 17 00:00:00 2001
From: Michael Neuling <mikey@neuling.org>
Date: Mon, 13 Apr 2026 04:17:56 +0000
Subject: [PATCH] lib/string_kunit: add strnlen page boundary test

Add a kunit test that exercises strnlen with count reaching exactly to a
page boundary and no NUL terminator in the buffer.  This catches
implementations that speculatively read past the count boundary (e.g. a
word-at-a-time loop that loads before checking the limit).

The test uses vmap of a single page so the next page is an unmapped
guard page.  A buggy strnlen that reads past count will fault.

Three cases are tested:
- No NUL in buffer, count 1-128 reaching page end (the primary trigger)
- NUL present near the page boundary (correctness check)
- count=0 with pointer at the page boundary (should not read at all)

Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Claude Opus 4.6 (1M context)
---
 lib/tests/string_kunit.c | 47 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/lib/tests/string_kunit.c b/lib/tests/string_kunit.c
index 0819ace5b0..917ff8edef 100644
--- a/lib/tests/string_kunit.c
+++ b/lib/tests/string_kunit.c
@@ -176,6 +176,52 @@ static void string_test_strnlen(struct kunit *test)
 	vfree(buf);
 }
 
+/*
+ * Test strnlen with count reaching a page boundary and no NUL terminator
+ * in the buffer.  A buggy implementation that reads past the count boundary
+ * (e.g. a word-at-a-time loop that loads before checking) will fault on
+ * the unmapped guard page that vmap places after the mapping.
+ */
+static void string_test_strnlen_page_boundary(struct kunit *test)
+{
+	struct page *page;
+	char *buf;
+	size_t count;
+
+	page = alloc_page(GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, page);
+
+	buf = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, buf);
+
+	memset(buf, 'A', PAGE_SIZE);
+
+	/* Count reaches exactly to the page boundary, no NUL in buffer. */
+	for (count = 1; count <= 128; count++) {
+		char *s = buf + PAGE_SIZE - count;
+
+		KUNIT_EXPECT_EQ_MSG(test, strnlen(s, count), count,
+			"count:%zu offset_from_end:%zu", count, count);
+	}
+
+	/* Also test with NUL present within the buffer near the boundary. */
+	for (count = 2; count <= 128; count++) {
+		char *s = buf + PAGE_SIZE - count;
+		size_t nul_pos = count / 2;
+
+		s[nul_pos] = '\0';
+		KUNIT_EXPECT_EQ_MSG(test, strnlen(s, count), nul_pos,
+			"count:%zu nul_pos:%zu", count, nul_pos);
+		s[nul_pos] = 'A';
+	}
+
+	/* count = 0 should not read at all, even at the page boundary. */
+	KUNIT_EXPECT_EQ(test, strnlen(buf + PAGE_SIZE, 0), (size_t)0);
+
+	vunmap(buf);
+	__free_page(page);
+}
+
 static void string_test_strchr(struct kunit *test)
 {
 	const char *test_string = "abcdefghijkl";
@@ -887,6 +933,7 @@ static struct kunit_case string_test_cases[] = {
 	KUNIT_CASE(string_test_memset64),
 	KUNIT_CASE(string_test_strlen),
 	KUNIT_CASE(string_test_strnlen),
+	KUNIT_CASE(string_test_strnlen_page_boundary),
 	KUNIT_CASE(string_test_strchr),
 	KUNIT_CASE(string_test_strnchr),
 	KUNIT_CASE(string_test_strrchr),
-- 
2.43.0

Re: [PATCH] riscv: lib: Fix ZBB strnlen reading past count boundary
Posted by Feng Jiang 2 months ago
On 2026/4/13 13:02, Michael Neuling wrote:
>> Thanks for catching and fixing this! Your analysis is spot on—that
>> "load-before-check" logic was indeed an oversight on my part, especially
>> regarding the page boundary edge case.
> 
> No worries.
> 
>> The test case you provided is extremely helpful. Since you've already
>> built this reproducer, would you be interested in helping to improve
>> the KUnit test string_test_strnlen() in lib/tests/string_kunit.c as well?
>> Currently, it mainly tests strings with NUL terminators and lacks coverage
>> for these kinds of non-terminated boundary scenarios.
> 
> The below is from Claude. I gave it a test under qemu riscv with and without 
> the patch and it seems to catch the failure. Feel free to use it as you see fit.
> 
>   [   19.042129] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
>   [   19.043133] Oops [#1]
>   [   66.197273]     ok 5 string_test_strnlen
>   [   66.197855] Unable to handle kernel paging request at virtual address ff20000000096000
>   [   66.198980] Oops [#2]
>   [   66.199867]  ra : string_test_strnlen_page_boundary+0xba/0x244
>   [   66.204025]     # string_test_strnlen_page_boundary: try faulted: last line seen lib/tests/string_kunit.c:195
>   [   66.204391]     # string_test_strnlen_page_boundary: internal error occurred preventing test case from running: -4
>   [   66.205133]     not ok 6 string_test_strnlen_page_boundary
>   [   66.227546] # string: pass:24 fail:1 skip:4 total:29
>   [   66.227879] not ok 74 string
> 
> Mikey
> 
> From b4933270b53e3acccea707b6dced352ee525828f Mon Sep 17 00:00:00 2001
> From: Michael Neuling <mikey@neuling.org>
> Date: Mon, 13 Apr 2026 04:17:56 +0000
> Subject: [PATCH] lib/string_kunit: add strnlen page boundary test
> 
> Add a kunit test that exercises strnlen with count reaching exactly to a
> page boundary and no NUL terminator in the buffer.  This catches
> implementations that speculatively read past the count boundary (e.g. a
> word-at-a-time loop that loads before checking the limit).
> 
> The test uses vmap of a single page so the next page is an unmapped
> guard page.  A buggy strnlen that reads past count will fault.
> 
> Three cases are tested:
> - No NUL in buffer, count 1-128 reaching page end (the primary trigger)
> - NUL present near the page boundary (correctness check)
> - count=0 with pointer at the page boundary (should not read at all)
> 
> Signed-off-by: Michael Neuling <mikey@neuling.org>
> Signed-off-by: Claude Opus 4.6 (1M context)
> ---
>  lib/tests/string_kunit.c | 47 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 47 insertions(+)
> 
> diff --git a/lib/tests/string_kunit.c b/lib/tests/string_kunit.c
> index 0819ace5b0..917ff8edef 100644
> --- a/lib/tests/string_kunit.c
> +++ b/lib/tests/string_kunit.c
> @@ -176,6 +176,52 @@ static void string_test_strnlen(struct kunit *test)
>  	vfree(buf);
>  }
>  
> +/*
> + * Test strnlen with count reaching a page boundary and no NUL terminator
> + * in the buffer.  A buggy implementation that reads past the count boundary
> + * (e.g. a word-at-a-time loop that loads before checking) will fault on
> + * the unmapped guard page that vmap places after the mapping.
> + */
> +static void string_test_strnlen_page_boundary(struct kunit *test)
> +{
> +	struct page *page;
> +	char *buf;
> +	size_t count;
> +
> +	page = alloc_page(GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, page);
> +
> +	buf = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, buf);
> +
> +	memset(buf, 'A', PAGE_SIZE);
> +
> +	/* Count reaches exactly to the page boundary, no NUL in buffer. */
> +	for (count = 1; count <= 128; count++) {
> +		char *s = buf + PAGE_SIZE - count;
> +
> +		KUNIT_EXPECT_EQ_MSG(test, strnlen(s, count), count,
> +			"count:%zu offset_from_end:%zu", count, count);
> +	}
> +
> +	/* Also test with NUL present within the buffer near the boundary. */
> +	for (count = 2; count <= 128; count++) {
> +		char *s = buf + PAGE_SIZE - count;
> +		size_t nul_pos = count / 2;
> +
> +		s[nul_pos] = '\0';
> +		KUNIT_EXPECT_EQ_MSG(test, strnlen(s, count), nul_pos,
> +			"count:%zu nul_pos:%zu", count, nul_pos);
> +		s[nul_pos] = 'A';
> +	}
> +
> +	/* count = 0 should not read at all, even at the page boundary. */
> +	KUNIT_EXPECT_EQ(test, strnlen(buf + PAGE_SIZE, 0), (size_t)0);
> +
> +	vunmap(buf);
> +	__free_page(page);
> +}
> +
>  static void string_test_strchr(struct kunit *test)
>  {
>  	const char *test_string = "abcdefghijkl";
> @@ -887,6 +933,7 @@ static struct kunit_case string_test_cases[] = {
>  	KUNIT_CASE(string_test_memset64),
>  	KUNIT_CASE(string_test_strlen),
>  	KUNIT_CASE(string_test_strnlen),
> +	KUNIT_CASE(string_test_strnlen_page_boundary),
>  	KUNIT_CASE(string_test_strchr),
>  	KUNIT_CASE(string_test_strnchr),
>  	KUNIT_CASE(string_test_strrchr),

Hi Mikey,

Thanks for the patch and for building the KUnit reproducer!

I've enhanced the existing string_test_strnlen() in lib/tests/string_kunit.c
to more comprehensively cover this issue. Since this covers both the over-read
fault and general logic correctness, I think we can simply improve the existing
test case rather than adding a new standalone string_test_strnlen_page_boundary().

Below is the diff for the enhanced test and the KUnit log showing it successfully
catching the failure (Oops) on RISC-V QEMU without your fix. Feel free to integrate
this into your patch series or let me know if you'd like me to submit it separately.

The Patch:

diff --git a/lib/tests/string_kunit.c b/lib/tests/string_kunit.c
index a8d430aa5466..27349a41bb10 100644
--- a/lib/tests/string_kunit.c
+++ b/lib/tests/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,7 @@ 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);
 
+                       /* Restore buffer */
                        s[len] = 'A';


Test Log (Catching the fault):

  $ ./tools/testing/kunit/kunit.py run --arch=riscv --cross_compile=riscv64-linux-gnu- --kunitconfig=my_string.kunitconfig
  [06:31:34] Configuring KUnit Kernel ...
  [06:31:34] Building KUnit Kernel ...
  Populating config with:
  $ make ARCH=riscv O=.kunit olddefconfig CROSS_COMPILE=riscv64-linux-gnu-
  Building with:
  $ make all compile_commands.json scripts_gdb ARCH=riscv O=.kunit --jobs=6 CROSS_COMPILE=riscv64-linux-gnu-
  [06:31:48] Starting KUnit Kernel (1/1)...
  [06:31:48] ============================================================
  Running tests with:
  $ qemu-system-riscv64 -nodefaults -m 1024 -kernel .kunit/arch/riscv/boot/Image -append 'kunit.enable=1 console=ttyS0 kunit_shutdown=reboot' -no-reboot -nographic -accel kvm -accel hvf -accel tcg -serial stdio -machine virt -cpu rv64 -bios /usr/share/qemu/opensbi-riscv64-generic-fw_dynamic.bin
  [06:31:49] =================== string (28 subtests) ===================
  [06:31:50] [PASSED] string_test_memset16
  [06:31:51] [PASSED] string_test_memset32
  [06:31:51] [PASSED] string_test_memset64
  [06:31:51] [PASSED] string_test_strlen
  [06:31:51] Unable to handle kernel paging request at virtual address ff20000000026000
  [06:31:51] Current kunit_try_catch pgtable: 4K pagesize, 57-bit VAs, pgdp=0x0000000080eac000
  [06:31:51] [ff20000000026000] pgd=0000000020400c01, p4d=0000000020415001, pud=0000000020415401, pmd=0000000020415801, pte=0000000000000000
  [06:31:51] Oops [#1]
  [06:31:51] CPU: 0 UID: 0 PID: 25 Comm: kunit_try_catch Tainted: G                 N  6.19.0-rc8-00122-g17585ffa9746-dirty #301 NONE
  [06:31:51] Tainted: [N]=TEST
  [06:31:51] Hardware name: riscv-virtio,qemu (DT)
  [06:31:51] epc : strnlen_zbb+0x40/0x78
  [06:31:51]  ra : string_test_strnlen+0x156/0x420
  [06:31:51] epc : ffffffff802663a8 ra : ffffffff801a53d6 sp : ff200000000abd30
  [06:31:51]  gp : ffffffff80ca2a58 tp : ff60000001133340 t0 : ff20000000025ff8
  [06:31:51]  t1 : 0000000000000008 t2 : ff20000000026000 s0 : ff200000000abdd0
  [06:31:51]  s1 : ff2000000000b928 a0 : 0000000000000001 a1 : 0000000000000001
  [06:31:51]  a2 : 0000000000000000 a3 : ff20000000026000 a4 : 0000000000000000
  [06:31:51]  a5 : 00000000000000a1 a6 : ffffffff808a1e68 a7 : 0000000000000001
  [06:31:51]  s2 : ffffffff80890288 s3 : 0000000000000001 s4 : 0000000000000000
  [06:31:51]  s5 : ff20000000025ffe s6 : 0000000000000001 s7 : ffffffffffffffff
  [06:31:51]  s8 : ff20000000025fff s9 : ff20000000025fff s10: ff20000000026000
  [06:31:51]  s11: 0000000000000081 t3 : ffffffffffffffff t4 : ff20000000026000
  [06:31:51]  t5 : 0000000000000d25 t6 : ff60000001055000
  [06:31:51] status: 0000000200000120 badaddr: ff20000000026000 cause: 000000000000000d
  [06:31:51] [<ffffffff802663a8>] strnlen_zbb+0x40/0x78
  [06:31:51] [<ffffffff8019bed4>] kunit_try_run_case+0x58/0x144
  [06:31:51] [<ffffffff8019dd7e>] kunit_generic_run_threadfn_adapter+0x1a/0x34
  [06:31:51] [<ffffffff80033fda>] kthread+0xa6/0x144
  [06:31:51] [<ffffffff8000a5ba>] ret_from_fork_kernel+0xe/0xf0
  [06:31:51] [<ffffffff8026e13a>] ret_from_fork_kernel_asm+0x16/0x18
  [06:31:51] Code: 6013 5513 0033 5533 0ab5 6a63 03c5 8393 0082 5e7d (b303) 0082
  [06:31:51] ---[ end trace 0000000000000000 ]---
  [06:31:51]     # string_test_strnlen: try faulted: last line seen lib/tests/string_kunit.c:161
  [06:31:51]     # string_test_strnlen: internal error occurred preventing test case from running: -4
  [06:31:51] [FAILED] string_test_strnlen
  [06:31:51] [PASSED] string_test_strchr
  [06:31:51] [PASSED] string_test_strnchr
  [06:31:51] [PASSED] string_test_strrchr
  [06:31:51] [PASSED] string_test_strspn
  [06:31:51] [PASSED] string_test_strcmp
  [06:31:51] [PASSED] string_test_strcmp_long_strings
  [06:31:51] [PASSED] string_test_strncmp
  [06:31:51] [PASSED] string_test_strncmp_long_strings
  [06:31:51] [PASSED] string_test_strcasecmp
  [06:31:51] [PASSED] string_test_strcasecmp_long_strings
  [06:31:51] [PASSED] string_test_strncasecmp
  [06:31:51] [PASSED] string_test_strncasecmp_long_strings
  [06:31:51] [PASSED] string_test_strscpy
  [06:31:51] [PASSED] string_test_strcat
  [06:31:51] [PASSED] string_test_strncat
  [06:31:51] [PASSED] string_test_strlcat
  [06:31:51] [PASSED] string_test_strtomem
  [06:31:51] [PASSED] string_test_memtostr
  [06:31:51] [PASSED] string_test_strends
  [06:31:51] [PASSED] string_bench_strlen
  [06:31:51] [PASSED] string_bench_strnlen
  [06:31:51] [PASSED] string_bench_strchr
  [06:31:51] [PASSED] string_bench_strrchr
  [06:31:51]     # module: string_kunit
  [06:31:51] # string: pass:27 fail:1 skip:0 total:28
  [06:31:51] # Totals: pass:27 fail:1 skip:0 total:28
  [06:31:51] ===================== [FAILED] string ======================
  [06:31:51] ============================================================
  [06:31:51] Testing complete. Ran 28 tests: passed: 27, failed: 1
  [06:31:52] Elapsed time: 17.644s total, 0.001s configuring, 14.419s building, 3.141s running

Thanks again!

-- 
With Best Regards,
Feng Jiang