Bash typeset bug 2025-08-11

From: the.true.nathan.mi...@gmail.com
To: bug-bash@gnu.org
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

Reply via email to