Bash Version: 5.1
Patch Level: 4
Release Status: release
Git Commit: f3a35a2d (current master)

I don't really know the contribution rules for bash; my apologies in advance if I'm missing anything or otherwise doing something wrong.

Description:

`history -d -#` can sometimes fail with the error `-bash: history: -#: history position out of range` even when the history entry is present.

Repeat-By:

(Note that all of the `history` commands below are prefixed by a space to prevent adding them to history.)

    test:~$ HISTCONTROL=ignorespace
    test:~$ HISTSIZE=4
    test:~$  history -c
    test:~$ echo 1 >/dev/null
    test:~$ echo 2 >/dev/null
    test:~$ echo 3 >/dev/null
    test:~$ echo 4 >/dev/null
    test:~$ echo 5 >/dev/null
    test:~$ echo 6 >/dev/null
    test:~$  history
        3  echo 3 >/dev/null
        4  echo 4 >/dev/null
        5  echo 5 >/dev/null
        6  echo 6 >/dev/null
    test:~$  history -d -1
    test:~$  history
        3  echo 3 >/dev/null
        4  echo 4 >/dev/null
        5  echo 5 >/dev/null
    test:~$ echo 6 >/dev/null
    test:~$ echo 7 >/dev/null
    test:~$  history
        4  echo 4 >/dev/null
        5  echo 5 >/dev/null
        6  echo 6 >/dev/null
        7  echo 7 >/dev/null
    test:~$  history -d -1
    -bash: history: -1: history position out of range

Fix:

Attached. In the code below from history.def, after `ind` is first assigned to, it should be in the range [0, history_length) if it refers to a valid entry. It's then compared to `history_base`, however it should be compared to `0` instead, e.g. `if (ind < 0)`.

    if (delete_arg[0] == '-' && delete_offset < 0)
      {
/* since the_history[history_length] == 0x0, this calculation means that history -d -1 will delete the last history entry, which at
           this point is the history -d -1 we just added. */
        ind = history_length + delete_offset;
        if (ind < history_base)
          {
            sh_erange (delete_arg, _("history position"));
            return (EXECUTION_FAILURE);
          }
opt = ind + history_base; /* compensate for opt - history_base below */
      }

After applying the patch, everything seems to work as intended, however I didn't look into how to write a test for this....

Regards,
Chris Gurnee
diff --git a/builtins/history.def b/builtins/history.def
index 5db44c2c..0ee14cb2 100644
--- a/builtins/history.def
+++ b/builtins/history.def
@@ -189,35 +189,27 @@ history_builtin (list)
 	  return (EXECUTION_FAILURE);
 	}
       if (delete_arg[0] == '-' && delete_start < 0)
-        {
-	  /* the_history[history_length] == 0x0, so this is correct */
-          delete_start += history_length;
-	  if (delete_start < history_base)
-	    {
-start_error:
-	      sh_erange (delete_arg, _("history position"));
-	      return (EXECUTION_FAILURE);
-	    }
-        }
-      /* numbers as displayed by display_history are offset by history_base */
+	/* the_history[history_length] == 0x0, so this is correct */
+	delete_start += history_length;
       else if (delete_start > 0)
+	/* numbers as displayed by display_history are offset by history_base */
 	delete_start -= history_base;
       if (delete_start < 0 || delete_start >= history_length)
-	goto start_error;
+	  {
+	    sh_erange (delete_arg, _("history position"));
+	    return (EXECUTION_FAILURE);
+	  }
       if (range[0] == '-' && delete_end < 0)
-        {
-          delete_end += history_length;
-	  if (delete_end < history_base)
-	    {
-range_error:
-	      sh_erange (range, _("history position"));
-	      return (EXECUTION_FAILURE);
-	    }
-        }
+	{
+	  delete_end += history_length;
+	}
       else if (delete_end > 0)
 	delete_end -= history_base;
       if (delete_end < 0 || delete_end >= history_length)
-	goto range_error;
+	  {
+	    sh_erange (range, _("history position"));
+	    return (EXECUTION_FAILURE);
+	  }
       result = bash_delete_history_range (delete_start, delete_end);
       if (where_history () > history_length)
 	history_set_pos (history_length);
@@ -237,7 +229,7 @@ range_error:
 	     that history -d -1 will delete the last history entry, which at
 	     this point is the history -d -1 we just added. */
 	  ind = history_length + delete_offset;
-	  if (ind < history_base)
+	  if (ind < 0)
 	    {
 	      sh_erange (delete_arg, _("history position"));
 	      return (EXECUTION_FAILURE);

Reply via email to