[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
RE: eval '<$(;)' causes Segmentation Fault
From: |
youheng.lue |
Subject: |
RE: eval '<$(;)' causes Segmentation Fault |
Date: |
Tue, 27 Aug 2024 00:44:31 +0200 |
>> 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ü