diff --git a/SOURCES/glibc-rh1170118-CVE-2014-7817.patch b/SOURCES/glibc-rh1170118-CVE-2014-7817.patch new file mode 100644 index 0000000..1accbf3 --- /dev/null +++ b/SOURCES/glibc-rh1170118-CVE-2014-7817.patch @@ -0,0 +1,163 @@ +# +# commit a39208bd7fb76c1b01c127b4c61f9bfd915bfe7c +# Author: Carlos O'Donell +# Date: Wed Nov 19 11:44:12 2014 -0500 +# +# CVE-2014-7817: wordexp fails to honour WRDE_NOCMD. +# +# 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. +# +diff --git a/posix/wordexp-test.c b/posix/wordexp-test.c +index 4957006..bdd65e4 100644 +--- a/posix/wordexp-test.c ++++ b/posix/wordexp-test.c +@@ -27,6 +27,25 @@ + + #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; + +diff --git a/posix/wordexp.c b/posix/wordexp.c +index b6b65dd..26f3a26 100644 +--- a/posix/wordexp.c ++++ b/posix/wordexp.c +@@ -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, diff --git a/SOURCES/glibc-rh1170187.patch b/SOURCES/glibc-rh1170187.patch new file mode 100644 index 0000000..4ea2762 --- /dev/null +++ b/SOURCES/glibc-rh1170187.patch @@ -0,0 +1,202 @@ +commit e276f4f310a89a925dd59e583f7a1bef2114d9a7 +Author: Siddhesh Poyarekar +Date: Tue Nov 25 21:15:16 2014 +0530 + + ftell: seek to end only when there are unflushed bytes (BZ #17647) + + Currently we seek to end of file if there are unflushed writes or the + stream is in write mode, to get the current offset for writing in + append mode, which is the end of file. The latter case (i.e. stream + is in write mode, but no unflushed writes) is unnecessary since it + will only happen when the stream has just been flushed, in which case + the recorded offset ought to be reliable. + + Removing that case lets ftell give the correct offset when it follows + an ftruncate. The latter truncates the file, but does not change the + file position, due to which it is permissible to call ftell without an + intervening fseek call. + + Tested on x86_64 to verify that the added test case fails without the + patch and succeeds with it, and that there are no additional + regressions due to it. + + [BZ #17647] + * libio/fileops.c (do_ftell): Seek only when there are + unflushed writes. + * libio/wfileops.c (do_ftell_wide): Likewise. + * libio/tst-ftell-active-handler.c (do_ftruncate_test): New + test case. + (do_one_test): Call it. + +diff --git a/libio/fileops.c b/libio/fileops.c +index e0d7b76..1fc5719 100644 +--- a/libio/fileops.c ++++ b/libio/fileops.c +@@ -943,15 +943,14 @@ do_ftell (_IO_FILE *fp) + yet. */ + if (fp->_IO_buf_base != NULL) + { +- bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base +- || _IO_in_put_mode (fp)); ++ bool unflushed_writes = (fp->_IO_write_ptr > fp->_IO_write_base); + + bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING; + + /* When we have unflushed writes in append mode, seek to the end of the + file and record that offset. This is the only time we change the file + stream state and it is safe since the file handle is active. */ +- if (was_writing && append_mode) ++ if (unflushed_writes && append_mode) + { + result = _IO_SYSSEEK (fp, 0, _IO_seek_end); + if (result == _IO_pos_BAD) +@@ -961,7 +960,7 @@ do_ftell (_IO_FILE *fp) + } + + /* Adjust for unflushed data. */ +- if (!was_writing) ++ if (!unflushed_writes) + offset -= fp->_IO_read_end - fp->_IO_read_ptr; + /* We don't trust _IO_read_end to represent the current file offset when + writing in append mode because the value would have to be shifted to +diff --git a/libio/tst-ftell-active-handler.c b/libio/tst-ftell-active-handler.c +index e9dc7b3..9f23c55 100644 +--- a/libio/tst-ftell-active-handler.c ++++ b/libio/tst-ftell-active-handler.c +@@ -88,6 +88,95 @@ static size_t file_len; + typedef int (*fputs_func_t) (const void *data, FILE *fp); + fputs_func_t fputs_func; + ++/* This test verifies that the offset reported by ftell is correct after the ++ file is truncated using ftruncate. ftruncate does not change the file ++ offset on truncation and hence, SEEK_CUR should continue to point to the ++ old offset and not be changed to the new offset. */ ++static int ++do_ftruncate_test (const char *filename) ++{ ++ FILE *fp = NULL; ++ int fd; ++ int ret = 0; ++ struct test ++ { ++ const char *mode; ++ int fd_mode; ++ } test_modes[] = { ++ {"r+", O_RDWR}, ++ {"w", O_WRONLY}, ++ {"w+", O_RDWR}, ++ {"a", O_WRONLY}, ++ {"a+", O_RDWR} ++ }; ++ ++ for (int j = 0; j < 2; j++) ++ { ++ for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++) ++ { ++ int fileret; ++ printf ("\tftruncate: %s (file, \"%s\"): ", ++ j == 0 ? "fopen" : "fdopen", ++ test_modes[i].mode); ++ ++ if (j == 0) ++ fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode); ++ else ++ fileret = get_handles_fdopen (filename, fd, fp, ++ test_modes[i].fd_mode, ++ test_modes[i].mode); ++ ++ if (fileret != 0) ++ return fileret; ++ ++ /* Write some data. */ ++ size_t written = fputs_func (data, fp); ++ ++ if (written == EOF) ++ { ++ printf ("fputs[1] failed to write data\n"); ++ ret |= 1; ++ } ++ ++ /* Record the offset. */ ++ long offset = ftell (fp); ++ ++ /* Flush data to allow switching active handles. */ ++ if (fflush (fp)) ++ { ++ printf ("Flush failed: %m\n"); ++ ret |= 1; ++ } ++ ++ /* Now truncate the file. */ ++ if (ftruncate (fd, 0) != 0) ++ { ++ printf ("Failed to truncate file: %m\n"); ++ ret |= 1; ++ } ++ ++ /* ftruncate does not change the offset, so there is no need to call ++ anything to be able to switch active handles. */ ++ long new_offset = ftell (fp); ++ ++ /* The offset should remain unchanged since ftruncate does not update ++ it. */ ++ if (offset != new_offset) ++ { ++ printf ("Incorrect offset. Expected %zu, but got %ld\n", ++ offset, new_offset); ++ ++ ret |= 1; ++ } ++ else ++ printf ("offset = %ld\n", offset); ++ ++ fclose (fp); ++ } ++ } ++ ++ return ret; ++} + /* Test that ftell output after a rewind is correct. */ + static int + do_rewind_test (const char *filename) +@@ -481,6 +570,7 @@ do_one_test (const char *filename) + ret |= do_write_test (filename); + ret |= do_append_test (filename); + ret |= do_rewind_test (filename); ++ ret |= do_ftruncate_test (filename); + + return ret; + } +diff --git a/libio/wfileops.c b/libio/wfileops.c +index 6a088b1..71281c1 100644 +--- a/libio/wfileops.c ++++ b/libio/wfileops.c +@@ -626,16 +626,15 @@ do_ftell_wide (_IO_FILE *fp) + const wchar_t *wide_read_base; + const wchar_t *wide_read_ptr; + const wchar_t *wide_read_end; +- bool was_writing = ((fp->_wide_data->_IO_write_ptr +- > fp->_wide_data->_IO_write_base) +- || _IO_in_put_mode (fp)); ++ bool unflushed_writes = (fp->_wide_data->_IO_write_ptr ++ > fp->_wide_data->_IO_write_base); + + bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING; + + /* When we have unflushed writes in append mode, seek to the end of the + file and record that offset. This is the only time we change the file + stream state and it is safe since the file handle is active. */ +- if (was_writing && append_mode) ++ if (unflushed_writes && append_mode) + { + result = _IO_SYSSEEK (fp, 0, _IO_seek_end); + if (result == _IO_pos_BAD) +@@ -674,7 +673,7 @@ do_ftell_wide (_IO_FILE *fp) + struct _IO_codecvt *cv = fp->_codecvt; + int clen = (*cv->__codecvt_do_encoding) (cv); + +- if (!was_writing) ++ if (!unflushed_writes) + { + if (clen > 0) + { diff --git a/SPECS/glibc.spec b/SPECS/glibc.spec index 763be2e..d337dc4 100644 --- a/SPECS/glibc.spec +++ b/SPECS/glibc.spec @@ -1,6 +1,6 @@ %define glibcsrcdir glibc-2.17-c758a686 %define glibcversion 2.17 -%define glibcrelease 55%{?dist}.1 +%define glibcrelease 55%{?dist}.3 ############################################################################## # If run_glibc_tests is zero then tests are not run for the build. # You must always set run_glibc_tests to one for production builds. @@ -320,6 +320,9 @@ Patch1063: %{name}-rh1074410.patch Patch1509: %{name}-rh1133811-2.patch Patch1510: %{name}-rh1133811-3.patch +# Upstream CVE-2014-7817. +Patch1511: %{name}-rh1170118-CVE-2014-7817.patch + ############################################################################## # # Patches submitted, but not yet approved upstream. @@ -359,6 +362,8 @@ Patch2052: %{name}-rh1048123.patch # Upstream BZ 16680 Patch2053: %{name}-rh1074410-2.patch +Patch2065: %{name}-rh1170187.patch + ############################################################################## # End of glibc patches. ############################################################################## @@ -756,6 +761,8 @@ package or when debugging this package. %patch0061 -p1 %patch1509 -p1 %patch1510 -p1 +%patch2065 -p1 +%patch1511 -p1 ############################################################################## # %%prep - Additional prep required... @@ -1811,6 +1818,12 @@ rm -f *.filelist* %endif %changelog +* Fri Dec 5 2014 Carlos O'Donell - 2.17-55.3 +- Fix wordexp() to honour WRDE_NOCMD (CVE-2014-7817, #1170118) + +* Thu Dec 4 2014 Siddhesh Poyarekar - 2.17-55.2 +- ftell: seek to end only when there are unflushed bytes (#1170187). + * Tue Aug 26 2014 Siddhesh Poyarekar - 2.17-55.1 - Remove gconv transliteration loadable modules support (CVE-2014-5119, #1133811).