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 <fweimer@redhat.com>
This commit is contained in:
Sachin Monga committed 2026-10-01 00:52:07 +05:30
1 parent 30f988fbe7
commit e622f82ea8
3 files changed
+26 -7

No files matched your search

+2 -2
View File
@@ -250,8 +250,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))
+4
View File
@@ -0,0 +1,4 @@
#define TEST_IDENTIFIER strncasecmp
#define TEST_NAME "strncasecmp"
typedef char CHAR;
#include "test-Xncmp-nonarray.c"
+20 -5
View File
@@ -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