Bash typeset bug 2025-08-11
From: [email protected]
To: [email protected]
Subject: Bash 5.3 crashes appending to array being declared
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -g -O2
uname output: Linux nixos 6.6.87.2-microsoft-standard-WSL2 #1 SMP
PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025 x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu
Bash Version: 5.2
Patch Level: 37
Release Status: release
Affected Bash versions: 5.2.37, 5.3.0-gf6cfb14 (Aug 8, 2025), possibly
other versions
Description:
Bash 5.3 crashes appending to array being declared
Bash version: ff6cfb14 (heads/devel, Aug 8, 2025)
Development bash: GNU bash, version 5.3.0(7)-maint (x86_64-pc-linux-gnu)
System bash: GNU bash, 5.2.37(1)-release (x86_64-pc-linux-gnu)
Found with AFL++ running in a Docker Desktop container with Nixos in
Windows Subsystem for Linux 2 on Windows 10. This is crash #217 in my
output_devel-gff6cfb14_try3 folder.
Crash happens with bash {persistent mode + ASAN}, bash {just ASAN},
and system bash
Running the following base64'd code causes Bash 5.3-gff6cfb14 to
crash. AddressSanitizer says this is a heap buffer overflow
vulnerability. The script calls typeset twice. I think this may be
related to the getopt bug I reported previously, as the reproducer is
very simple. The variable which is overrun is allocated in
bind_tempenv_variable, but when the variable is disposed of, the wrong
branch in dispose_variable_value is taken because it is still tagged
as an array, which was the type of the original variable from
new_shell_variable before bind_tempenv_variable overwrote the pointer.
One of the stack frames has 0x1 as the `a` argument instead of
0x50200006150, even though the caller passed in the correct pointer.
The crash is caused by changing var->value from an ARRAY pointer to
something else without clearing the "is an array" flag on the variable
(var->attributes & att_array). `dispose_variable_value` thinks the
variable is still an array, so it calls array_dispose, which calls
array_flush, which interprets the string "_" as an ARRAY object but
overflows the short string trying to read `a->head`.
Repeat-By:
Sequence of events leading up to the crash in the first minimized script:
1. dl_init
2. bind_tempenv_variable
3. new_shell_variable (creates the variable, sets var->attributes to
-1094795586)
4. make_new_variable/assign_in_env (sets var->value to 0x0)
5. assign_in_env (sets var->value to 0x503000012460 "recY=/home/nixosy")
6. assign_in_env (sets var->attributes to 1048577)
7. convert_var_to_array (frees var->value and assigns a new `ARRAY*` to it)
8. convert_var_to_array (var->attributes 1048577 -> 1048581 [att_array])
9. declare_internal (var->attributes 1048581 -> 1048709 [att_array])
10. bind_tempenv_variable (Changes var->value from pointing to an
ARRAY to "_" without unsetting att_array flag in var->attributes)
11. dispose_variable_value thinks the variable is an array, so it
calls array_dispose
12. array_dispose calls array_flush.
13. AddressSanitizer: heap-buffer-overflow because array_dispose tries
to interpret "_" as an ARRAY pointer but overruns the single character
string.
Fix:
Seems to be fixed by adding `VUNSETATTR(var, att_array)` in
bind_tempenv_variable, but fails trap6.sub test because basic commands
like /bin/echo are not in the usual place on NixOS. May introduce
other bugs, haven't tested the patch much but at least Bash doesn't
segfault anymore with the patch.
[Description of how to fix the problem. If you don't know a
fix for the problem, don't include this section.]
diff --git a/variables.c b/variables.c
index 4e6f93bf..3b6beb15 100644
--- a/variables.c
+++ b/variables.c
@@ -4461,7 +4461,8 @@ bind_tempenv_variable (const char *name, const
char *value)
if (var)
{
- FREE (value_cell (var));
+ dispose_variable_value(var);
+ VUNSETATTR(var, att_array|att_assoc);
var_setvalue (var, savestring (value));
INVALIDATE_EXPORTSTR (var);
}
AFL++ 4.32a
Docker version: 4.43.2 (199162) Engine: 28.3.2
NixOS version: 25.05.806304.dfcd5b901dba (Warbler)
Clang version (Docker, but clang on NixOS is the same version):
```
Ubuntu clang version 19.1.7
(++20250114103253+cd708029e0b2-1~exp1~20250114103309.40)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm-19/bin
```
wsl.exe --version:
```
WSL version: 2.5.9.0
Kernel version: 6.6.87.2-1
WSLg version: 1.0.66
MSRDC version: 1.2.6074
Direct3D version: 1.611.1-81528511
DXCore version: 10.0.26100.1-240331-1435.ge-release
Windows version: 10.0.19045.6159
```
Computer: MSi GS63 Stealth 8RE running Windows 10 x64 upgraded with
32-gigs memory and 2TB hard drive
# Original reproducer (crash #217 from output_devel-gff6cfb14_try3)
IHJlY1k9fmUFYWRvcmVhZG9tbHkgICAgICB0eXBlc2V0IC1udCByZWNZPX4JCQlfKz0oaStvgikK
Xys9eSAgICAgIHR5cGVzZXQgLWF0IHJlY1k9fgkJCV8gJiY8JjYAAAAENi08ezAAT1JARThUIDPv
AAA=
Minimized:
`bash -c 'recY=~ typeset -nt recY=~;_+=y typeset -at recY=~ _'`
Even more minimized:
`./bash -c '_+=y typeset -a _'`
=================================================================
==89026==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x502000006160 at pc 0x5555559df162 bp 0x7fffffff8d20 sp
0x7fffffff8d18
READ of size 8 at 0x502000006160 thread T0
#0 0x5555559df161 (/home/nixos/src/bash/nonAFL/bash+0x48b161)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#1 0x5555559df2eb (/home/nixos/src/bash/nonAFL/bash+0x48b2eb)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#2 0x55555590fecf (/home/nixos/src/bash/nonAFL/bash+0x3bbecf)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#3 0x5555559b7dc2 (/home/nixos/src/bash/nonAFL/bash+0x463dc2)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#4 0x555555915a69 (/home/nixos/src/bash/nonAFL/bash+0x3c1a69)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#5 0x555555915984 (/home/nixos/src/bash/nonAFL/bash+0x3c1984)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#6 0x5555558d7384 (/home/nixos/src/bash/nonAFL/bash+0x383384)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#7 0x5555558d5434 (/home/nixos/src/bash/nonAFL/bash+0x381434)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#8 0x5555558ea578 (/home/nixos/src/bash/nonAFL/bash+0x396578)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#9 0x5555558d6ab0 (/home/nixos/src/bash/nonAFL/bash+0x382ab0)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#10 0x5555558d5434 (/home/nixos/src/bash/nonAFL/bash+0x381434)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#11 0x555555889417 (/home/nixos/src/bash/nonAFL/bash+0x335417)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#12 0x55555588429c (/home/nixos/src/bash/nonAFL/bash+0x33029c)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#13 0x7ffff762a47d
(/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib/libc.so.6+0x2a47d)
(BuildId: 184c6644327611cadef0a544327ebb842fceaa2c)
#14 0x7ffff762a538
(/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib/libc.so.6+0x2a538)
(BuildId: 184c6644327611cadef0a544327ebb842fceaa2c)
#15 0x555555741b04 (/home/nixos/src/bash/nonAFL/bash+0x1edb04)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
0x502000006160 is located 14 bytes after 2-byte region
[0x502000006150,0x502000006152)
allocated by thread T0 here:
#0 0x555555834807 (/home/nixos/src/bash/nonAFL/bash+0x2e0807)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#1 0x555555a46a0c (/home/nixos/src/bash/nonAFL/bash+0x4f2a0c)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#2 0x5555558ff2ac (/home/nixos/src/bash/nonAFL/bash+0x3ab2ac)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#3 0x5555558e24b8 (/home/nixos/src/bash/nonAFL/bash+0x38e4b8)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#4 0x5555558d7351 (/home/nixos/src/bash/nonAFL/bash+0x383351)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#5 0x5555558d5434 (/home/nixos/src/bash/nonAFL/bash+0x381434)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#6 0x5555558ea578 (/home/nixos/src/bash/nonAFL/bash+0x396578)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#7 0x5555558d6ab0 (/home/nixos/src/bash/nonAFL/bash+0x382ab0)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#8 0x5555558d5434 (/home/nixos/src/bash/nonAFL/bash+0x381434)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#9 0x555555889417 (/home/nixos/src/bash/nonAFL/bash+0x335417)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#10 0x55555588429c (/home/nixos/src/bash/nonAFL/bash+0x33029c)
(BuildId: 013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
#11 0x7ffff762a47d
(/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib/libc.so.6+0x2a47d)
(BuildId: 184c6644327611cadef0a544327ebb842fceaa2c)
SUMMARY: AddressSanitizer: heap-buffer-overflow
(/home/nixos/src/bash/nonAFL/bash+0x48b161) (BuildId:
013b1df8bcdfc6808bc7213e3d20e4e77b3bb7d5)
Shadow bytes around the buggy address:
0x502000005e80: fa fa 00 00 fa fa fd fa fa fa fd fa fa fa fd fa
0x502000005f00: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
0x502000005f80: fa fa fd fa fa fa fd fa fa fa fd fa fa fa 00 00
0x502000006000: fa fa 02 fa fa fa 02 fa fa fa fd fa fa fa 05 fa
0x502000006080: fa fa 05 fa fa fa fd fd fa fa 00 04 fa fa fd fa
=>0x502000006100: fa fa 03 fa fa fa 02 fa fa fa 02 fa[fa]fa fd fa
0x502000006180: fa fa 02 fa fa fa 00 00 fa fa fa fa fa fa fa fa
0x502000006200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000006280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000006300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000006380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==89026==ABORTING
Program received signal SIGKILL, Killed.
0x0000555555852890 in __sanitizer::internal__exit(int) ()
(rr) bt
#0 0x0000555555852890 in __sanitizer::internal__exit(int) ()
#1 0x000055555585f8d3 in __sanitizer::Die() ()
#2 0x000055555583e743 in __asan::ScopedInErrorReport::~ScopedInErrorReport() ()
#3 0x000055555583dd3a in __asan::ReportGenericError(unsigned long,
unsigned long, unsigned long, unsigned long, bool, unsigned long,
unsigned int, bool) [clone .part.0] ()
#4 0x000055555583f356 in __asan_report_load8 ()
#5 0x00005555559df162 in array_flush (a=0x502000006150) at array.c:103
#6 0x00005555559df2ec in array_dispose (a=0x1) at array.c:119
#7 0x000055555590fed0 in dispose_variable (var=0x504000011990) at
variables.c:3732
#8 0x00005555559b7dc3 in hash_flush (table=table@entry=0x502000005ff0,
free_data=free_data@entry=0x555555915be8 <propagate_temp_var>) at
hashlib.c:374
#9 0x0000555555915a6a in dispose_temporary_env (pushf=0x555555915be8
<propagate_temp_var>) at variables.c:4631
#10 0x0000555555915985 in dispose_used_env_vars () at variables.c:4651
#11 0x00005555558d7385 in execute_command_internal (command=<optimized
out>, asynchronous=<optimized out>,
pipe_in=<optimized out>, pipe_out=<optimized out>,
fds_to_close=<optimized out>) at execute_cmd.c:945
#12 0x00005555558d5435 in execute_command (command=0x5030000128e0) at
execute_cmd.c:456
#13 0x00005555558ea579 in execute_connection (command=0x5030000129d0,
asynchronous=<optimized out>,
pipe_in=<optimized out>, pipe_out=<optimized out>,
fds_to_close=<optimized out>) at execute_cmd.c:2946
#14 0x00005555558d6ab1 in execute_command_internal (command=<optimized
out>, asynchronous=<optimized out>,
pipe_in=<optimized out>, pipe_out=<optimized out>,
fds_to_close=<optimized out>) at execute_cmd.c:1117
#15 0x00005555558d5435 in execute_command (command=0x5030000129d0) at
execute_cmd.c:456
#16 0x0000555555889418 in reader_loop () at eval.c:183
#17 0x000055555588429d in main (argc=2, argv=0x7fffffff96f8,
env=<optimized out>) at shell.c:834
(rr) rc
Continuing.
Hardware watchpoint 2: *0x502000006150
Old value = 95
New value = 48830
0x00007ffff777887b in ?? ()
(rr) bt
#0 0x00007ffff777887b in ?? ()
#1 0x00005555558ff2bb in _Z6strcpyPcU17pass_object_size1PKc
(__dest=0x502000006150 "\276\276",
__src=0x502000005ef0 "_")
at
/nix/store/41pf3md9zgpda9kwh6rzn5kaddf7i0lp-glibc-2.40-66-dev/include/bits/string_fortified.h:81
#2 bind_tempenv_variable (name=0x555555bc4860 <str> "_",
value=0x502000005ef0 "_") at variables.c:4465
#3 bind_variable (name=0x555555bc4860 <str> "_", value=0x502000005ef0
"_", flags=flags@entry=0) at variables.c:3240
#4 0x00005555558e24b9 in bind_lastarg (arg=0x502000005ef0 "_") at
execute_cmd.c:4194
#5 execute_simple_command (simple_command=0x503000012910,
pipe_in=<optimized out>, pipe_in@entry=-1,
pipe_out=<optimized out>, pipe_out@entry=-1, async=async@entry=0,
fds_to_close=fds_to_close@entry=0x502000005b90)
at execute_cmd.c:4943
#6 0x00005555558d7352 in execute_command_internal (command=<optimized
out>, asynchronous=<optimized out>,
pipe_in=<optimized out>, pipe_out=<optimized out>,
fds_to_close=<optimized out>) at execute_cmd.c:938
#7 0x00005555558d5435 in execute_command (command=0x5030000128e0) at
execute_cmd.c:456
#8 0x00005555558ea579 in execute_connection (command=0x5030000129d0,
asynchronous=<optimized out>,
pipe_in=<optimized out>, pipe_out=<optimized out>,
fds_to_close=<optimized out>) at execute_cmd.c:2946
#9 0x00005555558d6ab1 in execute_command_internal (command=<optimized
out>, asynchronous=<optimized out>,
pipe_in=<optimized out>, pipe_out=<optimized out>,
fds_to_close=<optimized out>) at execute_cmd.c:1117
#10 0x00005555558d5435 in execute_command (command=0x5030000129d0) at
execute_cmd.c:456
#11 0x0000555555889418 in reader_loop () at eval.c:183
#12 0x000055555588429d in main (argc=2, argv=0x7fffffff96f8,
env=<optimized out>) at shell.c:834