>> 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 0x
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 = 0x7fb3
```
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 '; 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)
#