From 0a2eaad30fa9bed18276c4eae73e6b2e869a38a2 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Thu, 20 Jan 2022 14:17:58 +0900 Subject: [PATCH] Imported Upstream version 2.7.3 --- .tarball-version | 2 +- ChangeLog | 20 ++++++++++++ NEWS | 2 +- configure | 20 ++++++------ src/patch.c | 3 ++ src/pch.c | 32 ++++++++----------- src/util.c | 83 +++++++++++++++++++++--------------------------- src/util.h | 1 + tests/symlinks | 32 +++++++++++-------- 9 files changed, 103 insertions(+), 92 deletions(-) diff --git a/.tarball-version b/.tarball-version index 37c2961..2c9b4ef 100644 --- a/.tarball-version +++ b/.tarball-version @@ -1 +1 @@ -2.7.2 +2.7.3 diff --git a/ChangeLog b/ChangeLog index 24785e3..c21081a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2015-01-22 Andreas Gruenbacher + + Fix the fix for CVE-2015-1196 + * src/util.c (filename_is_safe): New function split off from name_is_valid(). + (symlink_target_is_valid): Explain why we cannot have absolute symlinks or + symlinks with ".." components for now. + (move_file): Move absolute filename check here and explain. + * tests/symlinks: Put test case with ".." symlink in comments for now. + * NEWS: Add CVE number. + +2015-01-21 Andreas Gruenbacher + + For renames and copies, make sure that both file names are valid + * src/patch.c (main): Allow there_is_another_patch() to set the + skip_rest_of_patch flag. + * src/pch.c (intuit_diff_type): For renames and copies, also check the "other" + file name. + (pch_copy, pch_rename): Now that both names are checked in intuit_diff_type(), + we know they are defined here. + 2015-01-20 Andreas Gruenbacher Fail when out of memory in set_hunkmax() diff --git a/NEWS b/NEWS index d3f1c2d..d79cead 100644 --- a/NEWS +++ b/NEWS @@ -4,7 +4,7 @@ deleting". * Function names in hunks (from diff -p) are now preserved in reject files. * With git-style patches, symlinks that point outside the working directory - will no longer be created. + will no longer be created (CVE-2015-1196). Changes in version 2.7.1: diff --git a/configure b/configure index a5f0571..a09350c 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for GNU patch 2.7.2. +# Generated by GNU Autoconf 2.69 for GNU patch 2.7.3. # # Report bugs to . # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='GNU patch' PACKAGE_TARNAME='patch' -PACKAGE_VERSION='2.7.2' -PACKAGE_STRING='GNU patch 2.7.2' +PACKAGE_VERSION='2.7.3' +PACKAGE_STRING='GNU patch 2.7.3' PACKAGE_BUGREPORT='bug-patch@gnu.org' PACKAGE_URL='http://www.gnu.org/software/patch/' @@ -2011,7 +2011,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures GNU patch 2.7.2 to adapt to many kinds of systems. +\`configure' configures GNU patch 2.7.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -2081,7 +2081,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of GNU patch 2.7.2:";; + short | recursive ) echo "Configuration of GNU patch 2.7.3:";; esac cat <<\_ACEOF @@ -2185,7 +2185,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -GNU patch configure 2.7.2 +GNU patch configure 2.7.3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2894,7 +2894,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by GNU patch $as_me 2.7.2, which was +It was created by GNU patch $as_me 2.7.3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3828,7 +3828,7 @@ fi # Define the identity of the package. PACKAGE='patch' - VERSION='2.7.2' + VERSION='2.7.3' cat >>confdefs.h <<_ACEOF @@ -23003,7 +23003,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by GNU patch $as_me 2.7.2, which was +This file was extended by GNU patch $as_me 2.7.3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -23075,7 +23075,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -GNU patch config.status 2.7.2 +GNU patch config.status 2.7.3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/src/patch.c b/src/patch.c index 441732e..cb4dbb2 100644 --- a/src/patch.c +++ b/src/patch.c @@ -196,6 +196,9 @@ main (int argc, char **argv) bool mismatch = false; char const *outname = NULL; + if (skip_rest_of_patch) + somefailed = true; + if (have_git_diff != pch_git_diff ()) { if (have_git_diff) diff --git a/src/pch.c b/src/pch.c index 33facd9..028d51f 100644 --- a/src/pch.c +++ b/src/pch.c @@ -401,21 +401,7 @@ name_is_valid (char const *name) return false; } - if (IS_ABSOLUTE_FILE_NAME (name)) - is_valid = false; - else - for (n = name; *n; ) - { - if (*n == '.' && *++n == '.' && ( ! *++n || ISSLASH (*n))) - { - is_valid = false; - break; - } - while (*n && ! ISSLASH (*n)) - n++; - while (ISSLASH (*n)) - n++; - } + is_valid = filename_is_safe (name); /* Allow any filename if we are in the filesystem root. */ if (! is_valid && cwd_is_root (name)) @@ -978,6 +964,16 @@ intuit_diff_type (bool need_header, mode_t *p_file_type) } } + if ((pch_rename () || pch_copy ()) + && ! inname + && ! ((i == OLD || i == NEW) && + p_name[! reverse] && + name_is_valid (p_name[! reverse]))) + { + say ("Cannot %s file without two valid file names\n", pch_rename () ? "rename" : "copy"); + skip_rest_of_patch = true; + } + if (i == NONE) { if (inname) @@ -2178,14 +2174,12 @@ pch_name (enum nametype type) bool pch_copy (void) { - return p_copy[OLD] && p_copy[NEW] - && p_name[OLD] && p_name[NEW]; + return p_copy[OLD] && p_copy[NEW]; } bool pch_rename (void) { - return p_rename[OLD] && p_rename[NEW] - && p_name[OLD] && p_name[NEW]; + return p_rename[OLD] && p_rename[NEW]; } /* Return the specified line position in the old file of the old context. */ diff --git a/src/util.c b/src/util.c index 94c7582..ae05caa 100644 --- a/src/util.c +++ b/src/util.c @@ -423,55 +423,18 @@ create_backup (char const *to, const struct stat *to_st, bool leave_original) } } +/* Only allow symlink targets which are relative and free of ".." components: + * otherwise, the operating system may follow one of those symlinks in a + * pathname component, leading to a path traversal vulnerability. + * + * An alternative to disallowing many kinds of symlinks would be to implement + * path traversal in user space using openat() without following symlinks + * altogether. + */ static bool symlink_target_is_valid (char const *target, char const *to) { - bool is_valid; - - if (IS_ABSOLUTE_FILE_NAME (to)) - is_valid = true; - else if (IS_ABSOLUTE_FILE_NAME (target)) - is_valid = false; - else - { - unsigned int depth = 0; - char const *t; - - is_valid = true; - t = to; - while (*t) - { - while (*t && ! ISSLASH (*t)) - t++; - if (ISSLASH (*t)) - { - while (ISSLASH (*t)) - t++; - depth++; - } - } - - t = target; - while (*t) - { - if (*t == '.' && *++t == '.' && (! *++t || ISSLASH (*t))) - { - if (! depth--) - { - is_valid = false; - break; - } - } - else - { - while (*t && ! ISSLASH (*t)) - t++; - depth++; - } - while (ISSLASH (*t)) - t++; - } - } + bool is_valid = filename_is_safe (target); /* Allow any symlink target if we are in the filesystem root. */ return is_valid || cwd_is_root (to); @@ -520,7 +483,11 @@ move_file (char const *from, bool *from_needs_removal, read_fatal (); buffer[size] = 0; - if (! symlink_target_is_valid (buffer, to)) + /* If we are allowed to create a file with an absolute path name, + anywhere, we also don't need to worry about symlinks that can + leave the working directory. */ + if (! (IS_ABSOLUTE_FILE_NAME (to) + || symlink_target_is_valid (buffer, to))) { fprintf (stderr, "symbolic link target '%s' is invalid\n", buffer); @@ -1720,6 +1687,28 @@ int stat_file (char const *filename, struct stat *st) return xstat (filename, st) == 0 ? 0 : errno; } +/* Check if a filename is relative and free of ".." components. + Such a path cannot lead to files outside the working tree + as long as the working tree only contains symlinks that are + "filename_is_safe" when followed. */ +bool +filename_is_safe (char const *name) +{ + if (IS_ABSOLUTE_FILE_NAME (name)) + return false; + while (*name) + { + if (*name == '.' && *++name == '.' + && ( ! *++name || ISSLASH (*name))) + return false; + while (*name && ! ISSLASH (*name)) + name++; + while (ISSLASH (*name)) + name++; + } + return true; +} + /* Check if we are in the root of a particular filesystem namespace ("/" on UNIX or a particular drive's root on DOS-like systems). */ bool diff --git a/src/util.h b/src/util.h index 579c5de..6b3308a 100644 --- a/src/util.h +++ b/src/util.h @@ -69,6 +69,7 @@ enum file_id_type lookup_file_id (struct stat const *); void set_queued_output (struct stat const *, bool); bool has_queued_output (struct stat const *); int stat_file (char const *, struct stat *); +bool filename_is_safe (char const *); bool cwd_is_root (char const *); enum file_attributes { diff --git a/tests/symlinks b/tests/symlinks index 6211026..04a9b73 100644 --- a/tests/symlinks +++ b/tests/symlinks @@ -148,20 +148,24 @@ ncheck 'test ! -L symlink' # Patch should not create symlinks which point outside the working directory. -cat > symlink-target.diff < symlink-target.diff < bad-symlink-target1.diff <