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