]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
fixes for several bugs found via fuzzing: overflow in $'...' hex expansion; fix for...
authorChet Ramey <chet.ramey@case.edu>
Tue, 30 Dec 2025 21:06:40 +0000 (16:06 -0500)
committerChet Ramey <chet.ramey@case.edu>
Tue, 30 Dec 2025 21:06:40 +0000 (16:06 -0500)
CWRU/CWRU.chlog
MANIFEST
builtins/evalstring.c
examples/shellmath/README.md
execute_cmd.c
input.c
lib/sh/strtrans.c
tests/history.right
tests/history.tests
tests/history10.sub [new file with mode: 0644]

index 18463f6a76b7341b51b319b359fb91505591490d..15553f703cb627f91fb784fb7c92e58a218f6274 100644 (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
index 1e00729f8aacf2cd6c6486fa734eedfed4c0759c..3bf2fa24052a0be839d921e052e03e5adc0e5788 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -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
index 31ece9ffe4409af6d97409302166da7fcde59746..b21cfba4d01933586d782d0bbf47a43ef43c7baa 100644 (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)
index 1b4725611250cd62722574a1612186c1445b7d41..2aa1398937c4225d8e674fd9fd214032cc39f782 100644 (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
index c139b3e0e1f73c63108035b1086499b08bc0634b..34d10ba92638f1583cce3e7f02dbe90d7f07eeac 100644 (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);
     }
 
diff --git a/input.c b/input.c
index 5de6b7ca748bef5384ce1264acf74e2653da9e83..6d038f2c50a03e93fbc3fd18be4e4e01eddeb15d 100644 (file)
--- a/input.c
+++ b/input.c
@@ -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);
 }
 
index e3d67b6a3d28e2f28a0bbcc886afe930a5fca3bd..16ca16dd2514b272b8a3ea1440a691a46d48db8c 100644 (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++;
index 54ed23b08d207a5f4d9a9d8088d6137da1edfb38..235903bc1e06a95789e89500d82be1e075d12d19 100644 (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
index 213c75c4bf3b85397f74e8b70c767e6c774c9b56..15c6b0108133cc8452893de56e3caea6fdac16f9 100644 (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
diff --git a/tests/history10.sub b/tests/history10.sub
new file mode 100644 (file)
index 0000000..100971d
--- /dev/null
@@ -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