URL: <https://savannah.gnu.org/bugs/?68742>
Summary: [bash 5.3 patch 19/20] OOB global read in
parameter_brace_expand via ${#<high-byte>}
Group: The GNU Bourne-Again SHell
Submitter: None
Submitted: Mon 05 Oct 2026 09:36:01 PM UTC
Category: None
Severity: 3 - Normal
Priority: 5 - Normal
Item Group: None
Status: None
Privacy: Public
Assigned to: None
Open/Closed: Open
Discussion Lock: Unlocked
_______________________________________________________
Follow-up Comments:
-------------------------------------------------------
Date: Mon 05 Oct 2026 09:36:01 PM UTC By: Anonymous
**Bash version:** 5.3 patch 19 and patch 20 (upstream git tip)
**Not affected:** released bash-5.3, bash-5.2, or earlier
### Summary
A missing `(unsigned char)` cast in the `legal_variable_starter()` macro
causes a 4-byte out-of-bounds read of `sh_syntaxtab[]` when parsing
`${#X...}` where `X` is a byte in the range 0x80..0xFF. Reachable from
any shell input; information disclosure (no code execution).
### Root cause
Patch 19 (commit 549639c, 2026-09-15) replaced the `isalpha()`-based
check with a syntax-table lookup to work around locales where
`isalpha(0x80..0xFF)` is true:
// general.h
#define legal_variable_starter(c) (sh_syntaxtab[c] & CNAMESTART)
#define legal_variable_char(c) (sh_syntaxtab[c] & CNAME)
The sibling macros in `syntax.h` all cast to `unsigned char`:
#define shellmeta(c) (sh_syntaxtab[(unsigned char)(c)] & CSHMETA)
#define shellbreak(c) (sh_syntaxtab[(unsigned char)(c)] & CSHBRK)
#define shellquote(c) (sh_syntaxtab[(unsigned char)(c)] & CQUOTE)
The new macros do not. The one caller that passes a plain `char`
without pre-casting is `subst.c:9798`:
if (string[t_index] == '#' && legal_variable_starter (string[t_index+1]))
With `char` signed on x86-64 Linux, any byte 0x80..0xFF becomes a
negative int, indexing `sh_syntaxtab[-N]`. `sh_syntaxtab` is declared
`int[256]`, so the access is 4 bytes outside the array -- confirmed by
AddressSanitizer to land in the preceding global `sh_syntabsiz`.
### Reproducer
git clone --depth 1 https://git.savannah.gnu.org/git/bash.git
cd bash
./configure CFLAGS='-g -O0 -fsanitize=address' \
LDFLAGS='-fsanitize=address' --without-bash-malloc
touch configure configure.ac aclocal.m4
make -j$(nproc)
ASAN_OPTIONS='detect_leaks=0' \
printf 'echo ${#\xff}\n' | ./bash --norc --noprofile
### Expected output
ERROR: AddressSanitizer: global-buffer-overflow
READ of size 4 at 0x...
#0 parameter_brace_expand subst.c:9798
#1 param_expand subst.c:10800
#2 expand_word_internal subst.c:11531
0x... is located 4 bytes before global variable 'sh_syntaxtab'
### Suggested fix
--- a/general.h
+++ b/general.h
@@
-#define legal_variable_starter(c) (sh_syntaxtab[c] & CNAMESTART)
-#define legal_variable_char(c) (sh_syntaxtab[c] & CNAME)
+#define legal_variable_starter(c) (sh_syntaxtab[(unsigned char)(c)] &
CNAMESTART)
+#define legal_variable_char(c) (sh_syntaxtab[(unsigned char)(c)] &
CNAME)
Verified: rebuild with the patch, ASan is quiet, `${#var}` still
returns correct lengths.
### Impact
* 4-byte out-of-bounds read of adjacent global memory
* Reachable from any attacker-controlled shell input
* Information disclosure; no memory corruption, no code execution
* Trivially fixable (2 characters per macro)
_______________________________________________________
File Attachments:
Name: Screenshot@From@[email protected] Size: 594KiB
<https://file.savannah.gnu.org/file/Screenshot%40From%402026-10-05%4022-12-29.png?file_id=59063>
AGPL NOTICE
These attachments are served by Savane. You can download the corresponding
source code of Savane at
https://savannah.gnu.org/source/savane-a67ef128893f2d0917e9740f8a8f55ec063a2511.tar.gz
_______________________________________________________
Reply to this item at:
<https://savannah.gnu.org/bugs/?68742>
_______________________________________________
Message sent via Savannah
https://savannah.gnu.org/
signature.asc
Description: PGP signature
