CVE-2019-18276: Improper Check for Dropped Privileges (CWE-273)
value: An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support "saved UID" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use "enable -f" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.
CWE description: The product attempts to drop privileges but does not check or incorrectly checks to see if the drop succeeded.
CVSS v3 base score: 7.8
Published: 2019-11-28T01:15Z
Repository: https://github.com/bminor/bash
Language: C
Files changed: CWRU/CWRU.chlog, MANIFEST, bashline.c, builtins/help.def, config.h.in, configure, configure.ac, doc/bash.1, doc/bashref.texi, lib/glob/glob.c, pathexp.c, shell.c, tests/glob.tests, tests/glob6.sub, tests/glob7.sub
Commit date: 2019-07-01 09:03:53 -0400
Commit message: commit bash-20190628 snapshot

Diff: diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog
index 4ae6b56d..f9a92c46 100644
--- a/CWRU/CWRU.chlog
+++ b/CWRU/CWRU.chlog
@@ -6165,5 +6165,39 @@ general.c
 				   ----
 doc/{bash.1,bashref.texi}
 	- pathname expansion: note that under some circumstances the shell
 	  will invoke the matching engine for words with unquoted backslashes
 	- posixglob: document the new shell option and its effects
+
+				   6/27
+				   ----
+{config.h.in,configure.ac}
+	- setresuid,setresgid: check and set HAVE_{DECL,}_SETRES[UG]ID as
+	  appropriate
+
+shell.c
+	- disable_priv_mode: if we have setres[ug]id, use them over
+	  set[ug]id, which only set the save user-id and group-id if the
+	  process is running as root. From Ian Eldred Pudney
+	  in https://savannah.gnu.org/patch/?9822
+
+				   6/28
+				   ----
+lib/glob/glob.c
+	- glob_vector: don't bother trying to read the directory if the filename
+	  pattern doesn't have any globbing characters except backslash; just
+	  dequote the pattern and try to lstat(2) it as if there were no
+	  globbing characters at all. From an austingroup-bugs discussion
+	  message from Stephane Chazelas <stephane.chazelas@gmail.com>
+
+bashline.c
+	- completion_glob_pattern: just call glob_pattern_p and make sure it
+	  returns 1 (non-backslash globbing characters)
+
+builtins/help.def
+	- help_builtin: don't try pattern matching the help topic argument
+	  unless glob_pattern_p returns 1
+
+pathexp.c
+	- unquoted_glob_pattern_p: when in a bracket expression (open > 0),
+	  don't allow an unquoted slash as part of the bracket expression.
+	  Report from Stephane Chazelas <stephane.chazelas@gmail.com>
diff --git a/MANIFEST b/MANIFEST
index ac2d288f..75c1ef90 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1098,10 +1098,12 @@ tests/glob.tests	f
 tests/glob1.sub		f
 tests/glob2.sub		f
 tests/glob3.sub		f
 tests/glob4.sub		f
 tests/glob5.sub		f
+tests/glob6.sub		f
+tests/glob7.sub		f
 tests/glob.right	f
 tests/globstar.tests	f
 tests/globstar.right	f
 tests/globstar1.sub	f
 tests/globstar2.sub	f
diff --git a/bashline.c b/bashline.c
index 81bc214d..dc976525 100644
--- a/bashline.c
+++ b/bashline.c
@@ -3844,59 +3844,11 @@ bash_complete_command_internal (what_to_do)
 
 static int
 completion_glob_pattern (string)
      char *string;
 {
-  register int c;
-  char *send;
-  int open;
-
-  DECLARE_MBSTATE;
-
-  open = 0;
-  send = string + strlen (string);
-
-  while (c = *string++)
-    {
-      switch (c)
-	{
-	case '?':
-	case '*':
-	  return (1);
-
-	case '[':
-	  open++;
-	  continue;
-
-	case ']':
-	  if (open)
-	    return (1);
-	  continue;
-
-	case '+':
-	case '@':
-	case '!':
-	  if (*string == '(')	/*)*/
-	    return (1);
-	  continue;
-
-	case '\\':
-	  if (*string++ == 0)
-	    return (0);
-	}
-
-      /* Advance one fewer byte than an entire multibyte character to
-	 account for the auto-increment in the loop above. */
-#ifdef HANDLE_MULTIBYTE
-      string--;
-      ADVANCE_CHAR_P (string, send - string);
-      string++;
-#else
-      ADVANCE_CHAR_P (string, send - string);
-#endif
-    }
-  return (0);
+  return (glob_pattern_p (string) == 1);
 }
 
 static char *globtext;
 static char *globorig;
 
diff --git a/builtins/help.def b/builtins/help.def
index 006c4b5d..92f9b382 100644
--- a/builtins/help.def
+++ b/builtins/help.def
@@ -126,11 +126,11 @@ help_builtin (list)
       return (EXECUTION_SUCCESS);
     }
 
   /* We should consider making `help bash' do something. */
 
-  if (glob_pattern_p (list->word->word))
+  if (glob_pattern_p (list->word->word) == 1)
     {
       printf ("%s", ngettext ("Shell commands matching keyword `", "Shell commands matching keywords `", (list->next ? 2 : 1)));
       print_word_list (list, ", ");
       printf ("'\n\n");
     }
diff --git a/config.h.in b/config.h.in
index d8f5b882..92633057 100644
--- a/config.h.in
+++ b/config.h.in
@@ -1,8 +1,8 @@
 /* config.h -- Configuration file for bash. */
 
-/* Copyright (C) 1987-2009,2011-2012 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2009,2011-2012,2013-2019 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
    Bash is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -811,10 +811,18 @@
 
 /* Define if you have the setregid function.  */
 #undef HAVE_SETREGID
 #undef HAVE_DECL_SETREGID
 
+/* Define if you have the setregid function.  */
+#undef HAVE_SETRESGID
+#undef HAVE_DECL_SETRESGID
+
+/* Define if you have the setresuid function.  */
+#undef HAVE_SETRESUID
+#undef HAVE_DECL_SETRESUID
+
 /* Define if you have the setvbuf function.  */
 #undef HAVE_SETVBUF
 
 /* Define if you have the siginterrupt function.  */
 #undef HAVE_SIGINTERRUPT
diff --git a/configure b/configure
index 08a9c16d..8f37d2af 100755
--- a/configure
+++ b/configure
@@ -1,7 +1,7 @@
 #! /bin/sh
-# From configure.ac for Bash 5.0, version 5.010.
+# From configure.ac for Bash 5.0, version 5.011.
 # Guess values for system-dependent variables and create Makefiles.
 # Generated by GNU Autoconf 2.69 for bash 5.0-maint.
 #
 # Report bugs to <bug-bash@gnu.org>.
 #
@@ -14237,10 +14237,21 @@ fi
 
 cat >>confdefs.h <<_ACEOF
 #define HAVE_DECL_SETREGID $ac_have_decl
 _ACEOF
 
+ac_fn_c_check_decl "$LINENO" "" "ac_cv_have_decl_" "$ac_includes_default"
+if test "x$ac_cv_have_decl_" = xyes; then :
+  ac_have_decl=1
+else
+  ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_ $ac_have_decl
+_ACEOF
+(setresuid, setresgid)
 ac_fn_c_check_decl "$LINENO" "strcpy" "ac_cv_have_decl_strcpy" "$ac_includes_default"
 if test "x$ac_cv_have_decl_strcpy" = xyes; then :
   ac_have_decl=1
 else
   ac_have_decl=0
diff --git a/configure.ac b/configure.ac
index 400089cd..84de396b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -19,11 +19,11 @@ dnl Process this file with autoconf to produce a configure script.
 #   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/>.
 
-AC_REVISION([for Bash 5.0, version 5.010])dnl
+AC_REVISION([for Bash 5.0, version 5.011])dnl
 
 define(bashvers, 5.0)
 define(relstatus, maint)
 
 AC_INIT([bash], bashvers-relstatus, [bug-bash@gnu.org])
@@ -848,10 +848,11 @@ AC_CHECK_DECLS([AUDIT_USER_TTY],,, [[#include <linux/audit.h>]])
 
 AC_CHECK_DECLS([confstr])
 AC_CHECK_DECLS([printf])
 AC_CHECK_DECLS([sbrk])
 AC_CHECK_DECLS([setregid])
+AC_CHECK_DECLS[(setresuid, setresgid])
 AC_CHECK_DECLS([strcpy])
 AC_CHECK_DECLS([strsignal])
 
 dnl Extra test to detect the horribly broken HP/UX 11.00 strtold(3)
 AC_CHECK_DECLS([strtold], [
diff --git a/doc/bash.1 b/doc/bash.1
index f25be103..fc243ace 100644
--- a/doc/bash.1
+++ b/doc/bash.1
@@ -4700,11 +4700,12 @@ are evaluated as arithmetic expressions  (see
 .B "ARITHMETIC EVALUATION"
 above).
 .PD
 .SH "SIMPLE COMMAND EXPANSION"
 When a simple command is executed, the shell performs the following
-expansions, assignments, and redirections, from left to right.
+expansions, assignments, and redirections, from left to right, in
+the following order.
 .IP 1.
 The words that the parser has marked as variable assignments (those
 preceding the command name) and redirections are saved for later
 processing.
 .IP 2.
diff --git a/doc/bashref.texi b/doc/bashref.texi
index 14367ec1..00ce43f3 100644
--- a/doc/bashref.texi
+++ b/doc/bashref.texi
@@ -2966,11 +2966,12 @@ is not specified.  If the file does not exist, it is created.
 @node Simple Command Expansion
 @subsection Simple Command Expansion
 @cindex command expansion
 
 When a simple command is executed, the shell performs the following
-expansions, assignments, and redirections, from left to right.
+expansions, assignments, and redirections, from left to right, in
+the following order.
 
 @enumerate
 @item
 The words that the parser has marked as variable assignments (those
 preceding the command name) and redirections are saved for later
diff --git a/lib/glob/glob.c b/lib/glob/glob.c
index e1cbce28..b7f2d830 100644
--- a/lib/glob/glob.c
+++ b/lib/glob/glob.c
@@ -632,10 +632,11 @@ glob_vector (pat, dir, flags)
   int lose, skip, ndirs, isdir, sdlen, add_current, patlen;
   register char **name_vector;
   register unsigned int i;
   int mflags;		/* Flags passed to strmatch (). */
   int pflags;		/* flags passed to sh_makepath () */
+  int hasglob;		/* return value from glob_pattern_p */
   int nalloca;
   struct globval *firstmalloc, *tmplink;
   char *convfn;
 
   lastlink = 0;
@@ -673,14 +674,16 @@ glob_vector (pat, dir, flags)
     }
 
   patlen = (pat && *pat) ? strlen (pat) : 0;
 
   /* If the filename pattern (PAT) does not contain any globbing characters,
+     or contains a pattern with only backslash escapes (hasglob == 2),
      we can dispense with reading the directory, and just see if there is
      a filename `DIR/PAT'.  If there is, and we can access it, just make the
      vector to return and bail immediately. */
-  if (skip == 0 && glob_pattern_p (pat) == 0)
+  hasglob = 0;
+  if (skip == 0 && (hasglob = glob_pattern_p (pat)) == 0 || hasglob == 2)
     {
       int dirlen;
       struct stat finfo;
 
       if (glob_testdir (dir, 0) < 0)
diff --git a/pathexp.c b/pathexp.c
index eee5dad8..0c2ab273 100644
--- a/pathexp.c
+++ b/pathexp.c
@@ -59,11 +59,14 @@ int extended_glob = EXTGLOB_DEFAULT;
 int glob_star = 0;
 
 /* Do we handle backslashes in patterns the way Posix specifies? */
 int posix_glob_backslash = 1;
 
-/* Return nonzero if STRING has any unquoted special globbing chars in it.  */
+/* Return nonzero if STRING has any unquoted special globbing chars in it.
+   This is supposed to be called when pathname expansion is performed, so
+   it implements the rules in Posix 2.13.3, specifically that an unquoted
+   slash cannot appear in a bracket expression. */
 int
 unquoted_glob_pattern_p (string)
      register char *string;
 {
   register int c;
@@ -86,14 +89,18 @@ unquoted_glob_pattern_p (string)
 	case '[':
 	  open++;
 	  continue;
 
 	case ']':
-	  if (open)
+	  if (open)		/* XXX - if --open == 0? */
 	    return (1);
 	  continue;
 
+	case '/':
+	  if (open)
+	    open = 0;
+
 	case '+':
 	case '@':
 	case '!':
 	  if (*string == '(')	/*)*/
 	    return (1);
@@ -107,10 +114,15 @@ unquoted_glob_pattern_p (string)
 	    {
 	      bsquote = 1;
 	      string++;
 	      continue;
 	    }
+	  else if (open && *string == '/')
+	    {
+	      string++;		/* quoted slashes in bracket expressions are ok */
+	      continue;
+	    }
 	  else if (*string == 0)
 	    return (0);
 	 	  
 	case CTLESC:
 	  if (*string++ == '\0')
diff --git a/shell.c b/shell.c
index c82b815a..cfaf73f3 100644
--- a/shell.c
+++ b/shell.c
@@ -1291,20 +1291,28 @@ uidget ()
 void
 disable_priv_mode ()
 {
   int e;
 
+#if HAVE_DECL_SETRESUID
+  if (setresuid (current_user.uid, current_user.uid, current_user.uid) < 0)
+#else
   if (setuid (current_user.uid) < 0)
+#endif
     {
       e = errno;
       sys_error (_("cannot set uid to %d: effective uid %d"), current_user.uid, current_user.euid);
 #if defined (EXIT_ON_SETUID_FAILURE)
       if (e == EAGAIN)
 	exit (e);
 #endif
     }
+#if HAVE_DECL_SETRESGID
+  if (setresgid (current_user.gid, current_user.gid, current_user.gid) < 0)
+#else
   if (setgid (current_user.gid) < 0)
+#endif
     sys_error (_("cannot set gid to %d: effective gid %d"), current_user.gid, current_user.egid);
 
   current_user.euid = current_user.uid;
   current_user.egid = current_user.gid;
 }
diff --git a/tests/glob.tests b/tests/glob.tests
index a40dbbbb..dc86437e 100644
--- a/tests/glob.tests
+++ b/tests/glob.tests
@@ -11,10 +11,12 @@ expect()
 ${THIS_SH} ./glob1.sub
 ${THIS_SH} ./glob2.sub
 ${THIS_SH} ./glob3.sub
 ${THIS_SH} ./glob4.sub
 ${THIS_SH} ./glob5.sub
+${THIS_SH} ./glob6.sub
+${THIS_SH} ./glob7.sub
 
 MYDIR=$PWD	# save where we are
 
 TESTDIR=$TMPDIR/glob-test-$$
 mkdir $TESTDIR
diff --git a/tests/glob6.sub b/tests/glob6.sub
new file mode 100644
index 00000000..b099811d
--- /dev/null
+++ b/tests/glob6.sub
@@ -0,0 +1,54 @@
+# tests of the backslash-in-glob-patterns discussion on the austin-group ML
+
+: ${TMPDIR:=/var/tmp}
+
+ORIG=$PWD
+GLOBDIR=$TMPDIR/bash-glob-$$
+mkdir $GLOBDIR && cd $GLOBDIR
+
+# does the pattern matcher allow backslashes as escape characters and remove
+# them as part of matching?
+touch abcdefg
+pat='ab\cd*'
+printf '<%s>\n' $pat
+pat='\.'
+printf '<%s>\n' $pat
+rm abcdefg
+
+# how about when escaping pattern characters?
+touch '*abc.c'
+a='\**.c'
+printf '%s\n' $a
+rm -f '*abc.c'
+
+# how about when making the distinction between readable and searchable path
+# components?
+mkdir -m a=x searchable
+mkdir -m a=r readable
+
+p='searchable/\.'
+printf "%s\n" $p
+
+p='searchable/\./.'
+printf "%s\n" $p
+
+p='readable/\.'
+printf "%s\n" $p
+
+p='readable/\./.'
+printf "%s\n" $p
+
+printf "%s\n" 'searchable/\.'
+printf "%s\n" 'readable/\.'
+
+echo */.
+
+p='*/\.'
+echo $p
+
+echo */'.'
+
+rmdir searchable readable
+
+cd $ORIG
+rmdir $GLOBDIR
diff --git a/tests/glob7.sub b/tests/glob7.sub
new file mode 100644
index 00000000..0212b8e5
--- /dev/null
+++ b/tests/glob7.sub
@@ -0,0 +1,11 @@
+# according to Posix 2.13.3, a slash in a bracket expression renders that
+# bracket expression invalid
+shopt -s nullglob
+
+echo 1: [qwe/qwe]
+echo 2: [qwe/
+echo 3: [qwe/]
+
+echo 4: [qwe\/qwe]
+echo 5: [qwe\/
+echo 6: [qwe\/]

Vulnerable code:   register int c;
  char *send;
  int open;

  DECLARE_MBSTATE;

  open = 0;
  send = string + strlen (string);

  while (c = *string++)
    {
      switch (c)
	{
	case '?':
	case '*':
	  return (1);

	case '[':
	  open++;
	  continue;

	case ']':
	  if (open)
	    return (1);
	  continue;

	case '+':
	case '@':
	case '!':
	  if (*string == '(')	/*)*/
	    return (1);
	  continue;

	case '\\':
	  if (*string++ == 0)
	    return (0);
	}

      /* Advance one fewer byte than an entire multibyte character to
	 account for the auto-increment in the loop above. */
#ifdef HANDLE_MULTIBYTE
      string--;
      ADVANCE_CHAR_P (string, send - string);
      string++;
#else
      ADVANCE_CHAR_P (string, send - string);
#endif
    }
  return (0);
  if (glob_pattern_p (list->word->word))
/* Copyright (C) 1987-2009,2011-2012 Free Software Foundation, Inc.
# From configure.ac for Bash 5.0, version 5.010.
AC_REVISION([for Bash 5.0, version 5.010])dnl
expansions, assignments, and redirections, from left to right.
expansions, assignments, and redirections, from left to right.
  if (skip == 0 && glob_pattern_p (pat) == 0)
/* Return nonzero if STRING has any unquoted special globbing chars in it.  */
	  if (open)
Fixed code: 
				   6/27
				   ----
{config.h.in,configure.ac}
	- setresuid,setresgid: check and set HAVE_{DECL,}_SETRES[UG]ID as
	  appropriate

shell.c
	- disable_priv_mode: if we have setres[ug]id, use them over
	  set[ug]id, which only set the save user-id and group-id if the
	  process is running as root. From Ian Eldred Pudney
	  in https://savannah.gnu.org/patch/?9822

				   6/28
				   ----
lib/glob/glob.c
	- glob_vector: don't bother trying to read the directory if the filename
	  pattern doesn't have any globbing characters except backslash; just
	  dequote the pattern and try to lstat(2) it as if there were no
	  globbing characters at all. From an austingroup-bugs discussion
	  message from Stephane Chazelas <stephane.chazelas@gmail.com>

bashline.c
	- completion_glob_pattern: just call glob_pattern_p and make sure it
	  returns 1 (non-backslash globbing characters)

builtins/help.def
	- help_builtin: don't try pattern matching the help topic argument
	  unless glob_pattern_p returns 1

pathexp.c
	- unquoted_glob_pattern_p: when in a bracket expression (open > 0),
	  don't allow an unquoted slash as part of the bracket expression.
	  Report from Stephane Chazelas <stephane.chazelas@gmail.com>
tests/glob6.sub		f
tests/glob7.sub		f
  return (glob_pattern_p (string) == 1);
  if (glob_pattern_p (list->word->word) == 1)
/* Copyright (C) 1987-2009,2011-2012,2013-2019 Free Software Foundation, Inc.
/* Define if you have the setregid function.  */
#undef HAVE_SETRESGID
#undef HAVE_DECL_SETRESGID

/* Define if you have the setresuid function.  */
#undef HAVE_SETRESUID
#undef HAVE_DECL_SETRESUID

# From configure.ac for Bash 5.0, version 5.011.
ac_fn_c_check_decl "$LINENO" "" "ac_cv_have_decl_" "$ac_includes_default"
if test "x$ac_cv_have_decl_" = xyes; then :
  ac_have_decl=1
else
  ac_have_decl=0
fi

cat >>confdefs.h <<_ACEOF
#define HAVE_DECL_ $ac_have_decl
_ACEOF
(setresuid, setresgid)
AC_REVISION([for Bash 5.0, version 5.011])dnl
AC_CHECK_DECLS[(setresuid, setresgid])
expansions, assignments, and redirections, from left to right, in
the following order.
expansions, assignments, and redirections, from left to right, in
the following order.
  int hasglob;		/* return value from glob_pattern_p */
     or contains a pattern with only backslash escapes (hasglob == 2),
  hasglob = 0;
  if (skip == 0 && (hasglob = glob_pattern_p (pat)) == 0 || hasglob == 2)
/* Return nonzero if STRING has any unquoted special globbing chars in it.
   This is supposed to be called when pathname expansion is performed, so
   it implements the rules in Posix 2.13.3, specifically that an unquoted
   slash cannot appear in a bracket expression. */
	  if (open)		/* XXX - if --open == 0? */
	case '/':
	  if (open)
	    open = 0;

	  else if (open && *string == '/')
	    {
	      string++;		/* quoted slashes in bracket expressions are ok */
	      continue;
	    }
#if HAVE_DECL_SETRESUID
  if (setresuid (current_user.uid, current_user.uid, current_user.uid) < 0)
#else
#endif
#if HAVE_DECL_SETRESGID
  if (setresgid (current_user.gid, current_user.gid, current_user.gid) < 0)
#else
#endif
${THIS_SH} ./glob6.sub
${THIS_SH} ./glob7.sub
# tests of the backslash-in-glob-patterns discussion on the austin-group ML

: ${TMPDIR:=/var/tmp}

ORIG=$PWD
GLOBDIR=$TMPDIR/bash-glob-$$
mkdir $GLOBDIR && cd $GLOBDIR

# does the pattern matcher allow backslashes as escape characters and remove
# them as part of matching?
touch abcdefg
pat='ab\cd*'
printf '<%s>\n' $pat
pat='\.'
printf '<%s>\n' $pat
rm abcdefg

# how about when escaping pattern characters?
touch '*abc.c'
a='\**.c'
printf '%s\n' $a
rm -f '*abc.c'

# how about when making the distinction between readable and searchable path
# components?
mkdir -m a=x searchable
mkdir -m a=r readable

p='searchable/\.'
printf "%s\n" $p

p='searchable/\./.'
printf "%s\n" $p

p='readable/\.'
printf "%s\n" $p

p='readable/\./.'
printf "%s\n" $p

printf "%s\n" 'searchable/\.'
printf "%s\n" 'readable/\.'

echo */.

p='*/\.'
echo $p

echo */'.'

rmdir searchable readable

cd $ORIG
rmdir $GLOBDIR
# according to Posix 2.13.3, a slash in a bracket expression renders that
# bracket expression invalid
shopt -s nullglob

echo 1: [qwe/qwe]
echo 2: [qwe/
echo 3: [qwe/]

echo 4: [qwe\/qwe]
echo 5: [qwe\/
echo 6: [qwe\/]