>> These still cause the current devel branch to segfault, but (at least >> for me, on macOS) only when invoked via argument, as OP directed. For >> example, reading the scripts via stdin avoids the segfault. Tested on commit cf694865de527e597de5a906643a74037341a431 I reproduced within a Docker container based on the official Bash development image (bash:devel), using Alpine Linux 3.20 with GCC as the compiler. Detailed Dockerfile is at the end of the mail.
Configuration Information [Automatically generated, do not change]: Machine: x86_64 OS: linux-musl Compiler: gcc Compilation CFLAGS: -g -O2 uname output: Linux 839141c7e5ba 6.8.0-40-generic #40~22.04.3-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 30 17:30:19 UTC 2 x86_64 Linux Machine Type: x86_64-pc-linux-musl Bash Version: 5.3 Patch Level: 0 Release Status: alpha POC: $ cat poc.sh eval '<${;}' > The specific case is an empty command containing only a redirection that > results in an expansion error read from a script or string. I can confirm that the error is triggerted in the "execute_null_command" function and later containing a redirection. Specifically the variable `INPUT_STREAM bashinput.location` is both a char pointer and an int. At first it is used as a char pointer in the function "parse_and_execute" BEFORE ``` gdb> p bash_input.location.string $3 = 0x7fb3dc0db3b0 "<${;}" ``` However at shell.c:1758 in the fuction unset_bash_input it gets overwritten to a fd: ``` bash_input.location.buffered_fd = -1; ``` which overwrites the last 4 bytes of the pointer with 0xffffffff Backtrace for unset_bash_input: ► 0 0x5c55279555d3 unset_bash_input+35 1 0x5c5527986ed8 make_child+952 2 0x5c552796bb73 execute_null_command+387 3 0x5c55279703fa execute_command_internal+9786 4 0x5c55279703fa execute_command_internal+9786 5 0x5c55279ce969 parse_and_execute+1513 AFTER ``` gdb> p bash_input.location.string $14 = 0x7fb3ffffffff <error: Cannot access memory at address 0x7fb3ffffffff> ``` This leads to an OOB read, which leads to a segfault at evalstring.c:359 ```c while (*(bash_input.location.string) || parser_expanding_alias ()) ``` Security: This might pose a security risk, since an attacker might leverage this to break out of a restricted shell. 1. It might be possible for them to get lucky with ASLR and get a useful OOB read that doesn't crash. 2. An attacker controls the size of `bash_input.location.string` which is allocated and therefore have some control where the OOB Read happens Proposed fix: I suggest modifying INPUT_STREAM from a union to a struct to prevent the type confusion between pointer and int that leads to the OOB read. This is a theoretical fix and I have not tested it yet, so further validation is required to confirm its effectiveness. ####### Dockerfile ########## FROM alpine:3.20 # https://git.savannah.gnu.org/cgit/bash.git/log/?h=devel ENV _BASH_COMMIT cf694865de527e597de5a906643a74037341a431 # optimize asynchronous function invocations; fix for running return from trap while sourcing a file; restore completion function if read -e is interrupted ENV _BASH_VERSION devel-20240815 # prefixed with "_" since "$BASH..." have meaning in Bash parlance RUN set -eux; \ \ apk add --no-cache --virtual .build-deps \ bison \ coreutils \ dpkg-dev dpkg \ gcc \ libc-dev \ make \ ncurses-dev \ patch \ tar \ ; \ \ wget -O bash.tar.gz "https://git.savannah.gnu.org/cgit/bash.git/snapshot/bash-$_BASH_COMMIT.tar.gz"; \ \ mkdir -p /usr/src/bash; \ tar \ --extract \ --file=bash.tar.gz \ --strip-components=1 \ --directory=/usr/src/bash \ ; \ rm bash.tar.gz; \ \ if [ -d bash-patches ]; then \ apk add --no-cache --virtual .patch-deps patch; \ for p in bash-patches/*; do \ patch \ --directory=/usr/src/bash \ --input="$(readlink -f "$p")" \ --strip=0 \ ; \ rm "$p"; \ done; \ rmdir bash-patches; \ apk del --no-network .patch-deps; \ fi; \ \ # https://lists.gnu.org/archive/html/bug-bash/2023-05/msg00011.html { echo '#include <unistd.h>'; echo; cat /usr/src/bash/lib/sh/strscpy.c; } > /usr/src/bash/lib/sh/strscpy.c.new; \ mv /usr/src/bash/lib/sh/strscpy.c.new /usr/src/bash/lib/sh/strscpy.c; \ \ cd /usr/src/bash; \ gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ ./configure \ --build="$gnuArch" \ --enable-readline \ --with-curses \ # musl does not implement brk/sbrk (they simply return -ENOMEM) # bash: xmalloc: locale.c:81: cannot allocate 18 bytes (0 bytes allocated) --without-bash-malloc \ || { \ cat >&2 config.log; \ false; \ }; \ make -j "$(nproc)"; \ make install; \ cd /; \ # delete a few installed bits for smaller image size rm -rf \ /usr/local/share/doc/bash/*.html \ /usr/local/share/info \ /usr/local/share/locale \ /usr/local/share/man \ ; \ \ runDeps="$( \ scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \ | tr ',' '\n' \ | sort -u \ | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ )"; \ apk add --no-network --virtual .bash-rundeps $runDeps; \ apk del --no-network .build-deps; \ \ [ "$(which bash)" = '/usr/local/bin/bash' ]; \ bash --version; \ bash -c 'help' > /dev/null ## BELOW HERE ARE CUSTOM TOOLS USED FOR DEBUGGING # Install RUN set -eux; \ \ apk add gdb; \ apk add git; # pwndbg # RUN set -eux; \ # apk add nix; \ # nix shell github:pwndbg/pwndbg --extra-experimental-features nix-command --extra-experimental-features flakes ----------- Best regards, Youheng Lü