CVE-2014-7817: wordexp fails to honour WRDE_NOCMD.
authorCarlos O'Donell <carlos@redhat.com>
Wed, 19 Nov 2014 16:44:12 +0000 (11:44 -0500)
committerDongkyun, Son <dongkyun.s@samsung.com>
Sun, 22 May 2016 13:26:58 +0000 (22:26 +0900)
The function wordexp() fails to properly handle the WRDE_NOCMD
flag when processing arithmetic inputs in the form of "$((... ``))"
where "..." can be anything valid. The backticks in the arithmetic
epxression are evaluated by in a shell even if WRDE_NOCMD forbade
command substitution. This allows an attacker to attempt to pass
dangerous commands via constructs of the above form, and bypass
the WRDE_NOCMD flag. This patch fixes this by checking for WRDE_NOCMD
in exec_comm(), the only place that can execute a shell. All other
checks for WRDE_NOCMD are superfluous and removed.

We expand the testsuite and add 3 new regression tests of roughly
the same form but with a couple of nested levels.

On top of the 3 new tests we add fork validation to the WRDE_NOCMD
testing. If any forks are detected during the execution of a wordexp()
call with WRDE_NOCMD, the test is marked as failed. This is slightly
heuristic since vfork might be used in the future, but it provides a
higher level of assurance that no shells were executed as part of
command substitution with WRDE_NOCMD in effect. In addition it doesn't
require libpthread or libdl, instead we use the public implementation
namespace function __register_atfork (already part of the public ABI
for libpthread).

Tested on x86_64 with no regressions.

(cherry picked from commit a39208bd7fb76c1b01c127b4c61f9bfd915bfe7c)

ChangeLog
NEWS
posix/wordexp-test.c
posix/wordexp.c

index 31139c1..f9f3407 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2014-11-19  Carlos O'Donell  <carlos@redhat.com>
+           Florian Weimer  <fweimer@redhat.com>
+           Joseph Myers  <joseph@codesourcery.com>
+           Adam Conrad  <adconrad@0c3.net>
+           Andreas Schwab  <schwab@suse.de>
+           Brooks  <bmoses@google.com>
+
+       [BZ #17625]
+       * wordexp-test.c (__dso_handle): Add prototype.
+       (__register_atfork): Likewise.
+       (__app_register_atfork): New function.
+       (registered_forks): New global.
+       (register_fork): New function.
+       (test_case): Add 3 new tests for WRDE_CMDSUB.
+       (main): Call __app_register_atfork.
+       (testit): If WRDE_NOCMD set registered_forks to zero, run test, and if
+       fork count is non-zero fail the test.
+       * posix/wordexp.c (exec_comm): Return WRDE_CMDSUB if WRDE_NOCMD flag
+       is set.
+       (parse_dollars): Remove check for WRDE_NOCMD.
+       (parse_dquote): Likewise.
+
 2014-11-10  Renlin Li  <Renlin.Li@arm.com>
 
        [BZ #17555]
diff --git a/NEWS b/NEWS
index 1df7f5e..3de92cd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -9,7 +9,14 @@ Version 2.20.1
 
 * The following bugs are resolved with this release:
 
-  17266, 17370, 17371, 17460, 17485, 17555.
+  17266, 17370, 17371, 17460, 17485, 17555, 17625.
+
+* CVE-2104-7817 The wordexp function could ignore the WRDE_NOCMD flag
+  under certain input conditions resulting in the execution of a shell for
+  command substitution when the applicaiton did not request it. The
+  implementation now checks WRDE_NOCMD immediately before executing the
+  shell and returns the error WRDE_CMDSUB as expected.
+
 \f
 Version 2.20
 
index 4957006..bdd65e4 100644 (file)
 
 #define IFS " \n\t"
 
+extern void *__dso_handle __attribute__ ((__weak__, __visibility__ ("hidden")));
+extern int __register_atfork (void (*) (void), void (*) (void), void (*) (void), void *);
+
+static int __app_register_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void))
+{
+  return __register_atfork (prepare, parent, child,
+                           &__dso_handle == NULL ? NULL : __dso_handle);
+}
+
+/* Number of forks seen.  */
+static int registered_forks;
+
+/* For each fork increment the fork count.  */
+static void
+register_fork (void)
+{
+  registered_forks++;
+}
+
 struct test_case_struct
 {
   int retval;
@@ -206,6 +225,12 @@ struct test_case_struct
     { WRDE_SYNTAX, NULL, "$((2+))", 0, 0, { NULL, }, IFS },
     { WRDE_SYNTAX, NULL, "`", 0, 0, { NULL, }, IFS },
     { WRDE_SYNTAX, NULL, "$((010+4+))", 0, 0, { NULL }, IFS },
+    /* Test for CVE-2014-7817. We test 3 combinations of command
+       substitution inside an arithmetic expression to make sure that
+       no commands are executed and error is returned.  */
+    { WRDE_CMDSUB, NULL, "$((`echo 1`))", WRDE_NOCMD, 0, { NULL, }, IFS },
+    { WRDE_CMDSUB, NULL, "$((1+`echo 1`))", WRDE_NOCMD, 0, { NULL, }, IFS },
+    { WRDE_CMDSUB, NULL, "$((1+$((`echo 1`))))", WRDE_NOCMD, 0, { NULL, }, IFS },
 
     { -1, NULL, NULL, 0, 0, { NULL, }, IFS },
   };
@@ -258,6 +283,15 @@ main (int argc, char *argv[])
          return -1;
     }
 
+  /* If we are not allowed to do command substitution, we install
+     fork handlers to verify that no forks happened.  No forks should
+     happen at all if command substitution is disabled.  */
+  if (__app_register_atfork (register_fork, NULL, NULL) != 0)
+    {
+      printf ("Failed to register fork handler.\n");
+      return -1;
+    }
+
   for (test = 0; test_case[test].retval != -1; test++)
     if (testit (&test_case[test]))
       ++fail;
@@ -367,6 +401,9 @@ testit (struct test_case_struct *tc)
 
   printf ("Test %d (%s): ", ++tests, tc->words);
 
+  if (tc->flags & WRDE_NOCMD)
+    registered_forks = 0;
+
   if (tc->flags & WRDE_APPEND)
     {
       /* initial wordexp() call, to be appended to */
@@ -378,6 +415,13 @@ testit (struct test_case_struct *tc)
     }
   retval = wordexp (tc->words, &we, tc->flags);
 
+  if ((tc->flags & WRDE_NOCMD)
+      && (registered_forks > 0))
+    {
+         printf ("FAILED fork called for WRDE_NOCMD\n");
+         return 1;
+    }
+
   if (tc->flags & WRDE_DOOFFS)
       start_offs = sav_we.we_offs;
 
index b6b65dd..26f3a26 100644 (file)
@@ -893,6 +893,10 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
   pid_t pid;
   int noexec = 0;
 
+  /* Do nothing if command substitution should not succeed.  */
+  if (flags & WRDE_NOCMD)
+    return WRDE_CMDSUB;
+
   /* Don't fork() unless necessary */
   if (!comm || !*comm)
     return 0;
@@ -2082,9 +2086,6 @@ parse_dollars (char **word, size_t *word_length, size_t *max_length,
            }
        }
 
-      if (flags & WRDE_NOCMD)
-       return WRDE_CMDSUB;
-
       (*offset) += 2;
       return parse_comm (word, word_length, max_length, words, offset, flags,
                         quoted? NULL : pwordexp, ifs, ifs_white);
@@ -2196,9 +2197,6 @@ parse_dquote (char **word, size_t *word_length, size_t *max_length,
          break;
 
        case '`':
-         if (flags & WRDE_NOCMD)
-           return WRDE_CMDSUB;
-
          ++(*offset);
          error = parse_backtick (word, word_length, max_length, words,
                                  offset, flags, NULL, NULL, NULL);
@@ -2357,12 +2355,6 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags)
        break;
 
       case '`':
-       if (flags & WRDE_NOCMD)
-         {
-           error = WRDE_CMDSUB;
-           goto do_error;
-         }
-
        ++words_offset;
        error = parse_backtick (&word, &word_length, &max_length, words,
                                &words_offset, flags, pwordexp, ifs,