From e39d4ded3f28903fd2a504da8dfd136bcd285477 Mon Sep 17 00:00:00 2001 From: Sachin Monga Date: Thu, 1 Oct 2026 00:51:23 +0530 Subject: [PATCH] powerpc: Fix one byte overread in strncasecmp (bug 34683, CVE-2026-97399) The Power8 strncasecmp (strcasecmp.S with USE_AS_STRNCASECMP) touches s1[n]/s2[n] in three places when the first n bytes compare equal and contain no NUL: L(loop) preloads the next byte before its bdnz; L(loop1) increments then loads before its bdnz; and L(bytebybyte) loads s1[0]/s2[0] before checking r5. A caller that maps exactly n readable bytes can get SIGSEGV if byte n falls on an unmapped page. The L(bytebybyte) site was identified by Florian Weimer; the original AISLE report covered only the two loops. Fix all three sites: guard L(bytebybyte) with a r5 == 0 check, split the bdnz/load pairs in L(loop) and L(loop1) so the load only runs when the count is still positive, and preload rCHAR1/rCHAR2 in L(lessthan4) after confirming r5 != 0. strcasecmp is unaffected; its generated code is unchanged. Add string/test-strncasecmp-nonarray.c, built on the test-Xncmp-nonarray.c skeleton: maps a PROT_NONE page past both buffers and sweeps all start offsets and lengths. Runs as an xtest. Reported-by: AISLE in partnership with Red Hat Suggested-by: Florian Weimer (cherry picked from commit e622f82ea8797704ac8f761f88ed3e429e97fedd) --- string/Makefile | 4 +-- string/test-strncasecmp-nonarray.c | 4 +++ sysdeps/powerpc/powerpc64/power8/strcasecmp.S | 25 +++++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 string/test-strncasecmp-nonarray.c diff --git a/string/Makefile b/string/Makefile index c83b195e2e..d7437cd915 100644 --- a/string/Makefile +++ b/string/Makefile @@ -238,8 +238,8 @@ endif # This test allocates a lot of memory and can run for a long time. xtests += tst-strcoll-overflow -# This test runs for a long time. -xtests += test-strncmp-nonarray +# These tests run for a long time. +xtests += test-strncasecmp-nonarray test-strncmp-nonarray # This test needs libdl. ifeq (yes,$(build-shared)) diff --git a/string/test-strncasecmp-nonarray.c b/string/test-strncasecmp-nonarray.c new file mode 100644 index 0000000000..d68e58d5ec --- /dev/null +++ b/string/test-strncasecmp-nonarray.c @@ -0,0 +1,4 @@ +#define TEST_IDENTIFIER strncasecmp +#define TEST_NAME "strncasecmp" +typedef char CHAR; +#include "test-Xncmp-nonarray.c" diff --git a/sysdeps/powerpc/powerpc64/power8/strcasecmp.S b/sysdeps/powerpc/powerpc64/power8/strcasecmp.S index 7ce53407b8..838b8bf75f 100644 --- a/sysdeps/powerpc/powerpc64/power8/strcasecmp.S +++ b/sysdeps/powerpc/powerpc64/power8/strcasecmp.S @@ -355,6 +355,14 @@ L(bytebybyte): offset and string descriptors are only updated in the end of loop unrolling. */ ld rLOC, LOCALE_CTYPE_TOLOWER(rLOC) +#ifdef USE_AS_STRNCASECMP + /* This entry point can be reached from the vector loop with + r5 == 0 (the last vector chunk ended exactly at the `n' + boundary). Do not read s1[0]/s2[0] in that case: those + bytes are s1[n]/s2[n], one past the caller's bound. */ + cmpdi cr7, r5, 0 + beq cr7, L(retnull) +#endif lbz rCHAR1, 0(rSTR1) /* Load char from s1 */ lbz rCHAR2, 0(rSTR2) /* Load char from s2 */ #ifdef USE_AS_STRNCASECMP @@ -405,19 +413,23 @@ L(loop): cmpw cr1, rLWR1, rLWR2 crorc 4*cr1+eq,eq,4*cr1+eq beq cr1, L(done) +#ifdef USE_AS_STRNCASECMP + /* Test the remaining count before touching the next byte. */ + bdnz L(loop_load) + b L(lessthan4) +L(loop_load): +#endif lbz rCHAR1, 0(rSTR1) /* Load char from s1 */ lbz rCHAR2, 0(rSTR2) /* Load char from s2 */ -#ifdef USE_AS_STRNCASECMP - bdnz L(loop) -#else b L(loop) -#endif #ifdef USE_AS_STRNCASECMP L(lessthan4): clrldi r5, r5, 62 cmpdi cr7, r5, 0 beq cr7, L(retnull) mtctr r5 + lbz rCHAR1, 0(rSTR1) + lbz rCHAR2, 0(rSTR2) L(loop1): cmpdi rCHAR1, 0 sldi rADDR1, rCHAR1, 2 @@ -427,11 +439,14 @@ L(loop1): cmpw cr1, rLWR1, rLWR2 crorc 4*cr1+eq,eq,4*cr1+eq beq cr1, L(done) + bdnz L(loop1_load) + b L(retnull) +L(loop1_load): addi rSTR1, rSTR1, 1 addi rSTR2, rSTR2, 1 lbz rCHAR1, 0(rSTR1) lbz rCHAR2, 0(rSTR2) - bdnz L(loop1) + b L(loop1) #endif L(done): subf r0, rLWR2, rLWR1