fixes for several bugs found via fuzzing: overflow in $'...' hex expansion; fix for accessing freed memory when reading input from stdin and using multiple redirections to stdin; fix for pointer aliasing problem after a syntax error while executing a command string

This commit is contained in:
Chet Ramey
2025-12-30 16:06:40 -05:00
parent 722a855459
commit 4b27601dfd
10 changed files with 235 additions and 11 deletions
+41
View File
@@ -12406,3 +12406,44 @@ bashline.c
doc/bash.1,doc/bashref.texi
- fc: documented new -D option
12/19
-----
lib/sh/strtrans.c
- ansicstr: handle int overflow in \x{...} and break out of the loop
if we overflow
Fuzzing report from Александр Ушаков <anushakov@edu.hse.ru>
12/23
-----
input.c
- duplicate_buffered_stream: if we are duplicating the current input
buffered stream, make sure to mark both the current and new
buffered streams as B_SHAREDBUF, since they both share the buffer
and underlying file descriptor
- sync_shared_buffers: new function, after syncing the underlying
file descriptor, if the buffered stream is marked B_SHAREDBUF, make
sure to update b_used and b_inputp on other (saved) buffered streams
- sync_buffered_stream: call sync_shared_buffers
- count_shared_buffers: return a count of how many other buffered
streams are sharing the same buffer with the buffered stream passed
as an argument
- unshare_shared_buffers: unset the B_SHAREDBUF flag on the other
buffered stream (the caller guarantees there is only 1) sharing a
buffer with the buffered stream passed as an argument
- save_bash_input,duplicate_buffered_stream,close_buffered_stream:
if the buffered stream we're going to free up is marked B_SHAREDBUF,
count the number of buffered streams sharing the buffer, and
if there is only one other buffered stream sharing it, unset the
B_SHAREDBUF flag on that buffered stream
From a fuzzing report from Александр Ушаков <anushakov@edu.hse.ru>
builtins/evalstring.c
- parse_and_execute: save and restore currently_executing_command to
avoid pointer aliasing problems with local COMMAND variable
From a fuzzing report from Александр Ушаков <anushakov@edu.hse.ru>
execute_cmd.c
- execute_command_internal: set currently_executing_command to NULL
in places where we return from this function
+1
View File
@@ -1300,6 +1300,7 @@ tests/history6.sub f
tests/history7.sub f
tests/history8.sub f
tests/history9.sub f
tests/history10.sub f
tests/ifs.tests f
tests/ifs.right f
tests/ifs1.sub f
+4 -3
View File
@@ -412,6 +412,7 @@ parse_and_execute (char *string, const char *from_file, int flags)
protects installed by the string we're evaluating, so
it will undo the current function scope. */
dispose_command (command);
currently_executing_command = NULL;
discard_unwind_frame ("pe_dispose");
}
else
@@ -491,6 +492,7 @@ parse_and_execute (char *string, const char *from_file, int flags)
begin_unwind_frame ("pe_dispose");
add_unwind_protect (uw_dispose_fd_bitmap, bitmap);
add_unwind_protect (uw_dispose_command, command); /* XXX */
unwind_protect_pointer (currently_executing_command);
global_command = (COMMAND *)NULL;
@@ -567,9 +569,8 @@ INTERNAL_DEBUG(("parse_and_execute: calling cat_file, parse_and_execute_level =
#endif
last_result = execute_command_internal
(command, 0, NO_PIPE, NO_PIPE, bitmap);
dispose_command (command);
dispose_fd_bitmap (bitmap);
discard_unwind_frame ("pe_dispose");
run_unwind_frame ("pe_dispose");
/* If the global value didn't change, we restore what we had. */
if (((subshell_environment & SUBSHELL_COMSUB) || executing_funsub) && local_alflag == expaliases_flag)
+4 -2
View File
@@ -1,5 +1,5 @@
# Shellmath
Introducing decimal arithmetic libraries for the Bash shell, because
Introducing floating-point arithmetic libraries for the Bash shell, because
they said it couldn't be done... and because:
.
@@ -90,7 +90,7 @@ script is exercising the shellmath arithmetic subroutines 31 times.___)
The comment header in `faster_e_demo.sh` explains the optimization and shows
how to put this faster version to work for you.
## Runtime efficiency competitive with awk and bc
## Competitive with awk and bc in runtime efficiency
The file `timingData.txt` captures the results of some timing experiments that compare
`shellmath` against the GNU versions of the calculators `awk` and `bc`. The experiments
exercised each of the arithmetic operations and captured the results in a shell variable.
@@ -164,3 +164,5 @@ You can run your floating-point calculations directly in Bash!
## Please see also:
[A short discussion on arbitrary precision and shellmath](https://github.com/clarity20/shellmath/wiki/Shellmath-and-arbitrary-precision-arithmetic)
-- Michael Wood
+4
View File
@@ -808,6 +808,7 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
jump_to_top_level (ERREXIT);
}
currently_executing_command = (COMMAND *)NULL;
return (last_command_exit_value);
}
else
@@ -816,6 +817,7 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
run_pending_traps ();
currently_executing_command = (COMMAND *)NULL;
/* Posix 2013 2.9.3.1: "the exit status of an asynchronous list
shall be zero." */
last_command_exit_value = 0;
@@ -903,6 +905,8 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
jump_to_top_level (ERREXIT);
}
}
currently_executing_command = (COMMAND *)NULL;
return (last_command_exit_value);
}
+80 -4
View File
@@ -162,6 +162,10 @@ stream_setsize (size_t size)
int bash_input_fd_changed;
static inline int count_shared_buffers (BUFFERED_STREAM *);
static inline void unshare_shared_buffers (BUFFERED_STREAM *);
static inline void sync_shared_buffers (BUFFERED_STREAM *);;;;
/* This provides a way to map from a file descriptor to the buffer
associated with that file descriptor, rather than just the other
way around. This is needed so that buffers are managed properly
@@ -276,7 +280,13 @@ save_bash_input (int fd, int new_fd)
descriptor? Free up the buffer and report the error. */
internal_error (_("save_bash_input: buffer already exists for new fd %d"), nfd);
if (buffers[nfd]->b_flag & B_SHAREDBUF)
buffers[nfd]->b_buffer = (char *)NULL;
{
/* If this buffer is shared by only one other buffered stream, mark
it as no longer shared. */
if (count_shared_buffers (buffers[nfd]) == 1)
unshare_shared_buffers (buffers[nfd]);
buffers[nfd]->b_buffer = (char *)NULL;
}
free_buffered_stream (buffers[nfd]);
}
@@ -362,6 +372,10 @@ duplicate_buffered_stream (int fd1, int fd2)
/* If this buffer is shared with another fd, don't free the buffer */
else if (buffers[fd2]->b_flag & B_SHAREDBUF)
{
/* If this buffer is shared by only one other buffered stream, mark
it as no longer shared. */
if (count_shared_buffers (buffers[fd2]) == 1)
unshare_shared_buffers (buffers[fd2]);
buffers[fd2]->b_buffer = (char *)NULL;
free_buffered_stream (buffers[fd2]);
}
@@ -380,7 +394,11 @@ duplicate_buffered_stream (int fd1, int fd2)
}
if (buffers[fd2] && (fd_is_bash_input (fd1) || (buffers[fd1] && (buffers[fd1]->b_flag & B_SHAREDBUF))))
buffers[fd2]->b_flag |= B_SHAREDBUF;
{
/* Make sure both buffered streams are marked as sharing an input buffer */
buffers[fd1]->b_flag |= B_SHAREDBUF;
buffers[fd2]->b_flag |= B_SHAREDBUF;
}
return (fd2);
}
@@ -446,11 +464,17 @@ close_buffered_stream (BUFFERED_STREAM *bp)
{
int fd;
if (!bp)
if (bp == NULL)
return (0);
fd = bp->b_fd;
if (bp->b_flag & B_SHAREDBUF)
bp->b_buffer = (char *)NULL;
{
/* If this buffer is shared by only one other buffered stream, mark
it as no longer shared. */
if (count_shared_buffers (buffers[fd]) == 1)
unshare_shared_buffers (buffers[fd]);
bp->b_buffer = (char *)NULL;
}
free_buffered_stream (bp);
return (close (fd));
}
@@ -581,6 +605,49 @@ bufstream_ungetc(int c, BUFFERED_STREAM *bp)
return (c);
}
/* Return the number of buffered streams sharing a buffer with BP */
static inline int
count_shared_buffers (BUFFERED_STREAM *bp)
{
size_t i;
int n;
n = 0;
for (i = 0; i < nbuffers; i++)
if (buffers[i] && (buffers[i]->b_flag & B_SHAREDBUF) && buffers[i] != bp && buffers[i]->b_buffer == bp->b_buffer)
n++;
return n;
}
/* Mark the buffered stream sharing a buffer with BP as no longer B_SHAREDBUF */
static inline void
unshare_shared_buffers (BUFFERED_STREAM *bp)
{
size_t i;
for (i = 0; i < nbuffers; i++)
if (buffers[i] && (buffers[i]->b_flag & B_SHAREDBUF) && buffers[i] != bp && buffers[i]->b_buffer == bp->b_buffer)
{
buffers[i]->b_flag &= ~B_SHAREDBUF;
return;
}
}
/* Sync the input and read offsets on all buffered streams that share a buffer
with BP */
static inline void
sync_shared_buffers (BUFFERED_STREAM *bp)
{
size_t i;
for (i = 0; i < nbuffers; i++)
if (buffers[i] && (buffers[i]->b_flag & B_SHAREDBUF) && buffers[i] != bp && buffers[i]->b_buffer == bp->b_buffer)
{
buffers[i]->b_used = bp->b_used;
buffers[i]->b_inputp = bp->b_inputp;
}
}
/* Seek backwards on file BFD to synchronize what we've read so far
with the underlying file pointer. */
int
@@ -596,6 +663,15 @@ sync_buffered_stream (int bfd)
if (chars_left)
lseek (bp->b_fd, -chars_left, SEEK_CUR);
bp->b_used = bp->b_inputp = 0;
/* If we are sharing the buffer between buffered streams, we must have
duplicated the file descriptor and called duplicate_buffered_stream().
In this case, the buffers share the underlying fd and all of them
need to be updated to force a read on the next call, since we've changed
the file offset. */
if (bp->b_flag & B_SHAREDBUF)
sync_shared_buffers (bp);
return (0);
}
+11 -2
View File
@@ -130,7 +130,7 @@ ansicstr (const char *string, size_t len, int flags, int *sawc, size_t *rlen)
c &= 0xFF;
break;
case 'x': /* Hex digit -- non-ANSI */
if ((flags & 2) && *s == '{')
if ((flags & 2) && *s == '{') /* undocumented */
{
flags |= 16; /* internal flag value */
s++;
@@ -143,8 +143,17 @@ ansicstr (const char *string, size_t len, int flags, int *sawc, size_t *rlen)
chars are consumed. */
if (flags & 16)
{
v = c;
for ( ; ISXDIGIT ((unsigned char)*s); s++)
c = (c * 16) + HEXVALUE (*s);
{
v = (v * 16) + HEXVALUE (*s);
if (v > INT_MAX) /* abandon on overflow */
{
v &= INT_MAX;
break;
}
}
c = v;
flags &= ~16;
if (*s == '}')
s++;
+36
View File
@@ -403,3 +403,39 @@ EOF
5 echo three
history10.sub
1 echo first
2 echo one
3 echo two
4 echo three
5 echo x
6 echo y
7 echo z
8 echo last
echo one append
one append
echo two append
two append
echo three append
three append
1 echo first
2 echo x
3 echo y
4 echo z
5 echo last
6 echo one append
7 echo two append
8 echo three append
edit
echo edit append
edit append
1 echo first
2 echo x
3 echo y
4 echo z
5 echo last
6 echo one append
7 echo two append
8 echo three append
9 # simulate readline edit_and_execute_command
10 echo edit append
+1
View File
@@ -137,3 +137,4 @@ test_runsub ./history6.sub
test_runsub ./history7.sub
test_runsub ./history8.sub
test_runsub ./history9.sub
test_runsub ./history10.sub
+53
View File
@@ -0,0 +1,53 @@
#
# Copyright 2025 Free Software Foundation, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# basic test for fc -D
: ${TMPDIR:=/tmp}
stream()
{
# something you can see
sed 's/$/ append/' < $1 >$TMPF && mv $TMPF $1
rm -f $TMPF
}
HISTFILE=$TMPDIR/fchist-$BASHPID
TMPF=$TMPDIR/fctmp-$BASHPID
trap 'rm -f "$HISTFILE" "$TMPF"' 0 1 2 3 6 15
cat > $HISTFILE <<EOF
echo first
echo one
echo two
echo three
echo x
echo y
echo z
echo last
EOF
set -o history
history
fc -e stream -D 2 4
history
# simulate readline edit_and_execute_command
echo edit
fc -e stream -D
history