diff -up patch-2.7.1/src/pch.c.CVE-2015-1196 patch-2.7.1/src/pch.c --- patch-2.7.1/src/pch.c.CVE-2015-1196 2012-09-22 18:44:33.000000000 +0100 +++ patch-2.7.1/src/pch.c 2015-01-20 13:29:14.304859557 +0000 @@ -387,29 +387,6 @@ skip_hex_digits (char const *str) return s == str ? NULL : s; } -/* Check if we are in the root of a particular filesystem namespace ("/" on - UNIX or a particular drive's root on DOS-like systems). */ -static bool -cwd_is_root (char const *name) -{ - unsigned int prefix_len = FILE_SYSTEM_PREFIX_LEN (name); - char root[prefix_len + 2]; - struct stat st; - dev_t root_dev; - ino_t root_ino; - - memcpy (root, name, prefix_len); - root[prefix_len] = '/'; - root[prefix_len + 1] = 0; - if (stat (root, &st)) - return false; - root_dev = st.st_dev; - root_ino = st.st_ino; - if (stat (".", &st)) - return false; - return root_dev == st.st_dev && root_ino == st.st_ino; -} - static bool name_is_valid (char const *name) { diff -up patch-2.7.1/src/util.c.CVE-2015-1196 patch-2.7.1/src/util.c --- patch-2.7.1/src/util.c.CVE-2015-1196 2012-09-22 21:09:10.000000000 +0100 +++ patch-2.7.1/src/util.c 2015-01-20 13:29:14.305859561 +0000 @@ -422,6 +422,60 @@ create_backup (char const *to, const str } } +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++; + } + } + + /* Allow any symlink target if we are in the filesystem root. */ + return is_valid || cwd_is_root (to); +} + /* Move a file FROM (where *FROM_NEEDS_REMOVAL is nonzero if FROM needs removal when cleaning up at the end of execution, and where *FROMST is FROM's status if known), @@ -465,6 +519,13 @@ move_file (char const *from, bool *from_ read_fatal (); buffer[size] = 0; + if (! symlink_target_is_valid (buffer, to)) + { + fprintf (stderr, "symbolic link target '%s' is invalid\n", + buffer); + fatal_exit (0); + } + if (! backup) { if (unlink (to) == 0) @@ -1660,3 +1721,26 @@ int stat_file (char const *filename, str return xstat (filename, st) == 0 ? 0 : errno; } + +/* 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 +cwd_is_root (char const *name) +{ + unsigned int prefix_len = FILE_SYSTEM_PREFIX_LEN (name); + char root[prefix_len + 2]; + struct stat st; + dev_t root_dev; + ino_t root_ino; + + memcpy (root, name, prefix_len); + root[prefix_len] = '/'; + root[prefix_len + 1] = 0; + if (stat (root, &st)) + return false; + root_dev = st.st_dev; + root_ino = st.st_ino; + if (stat (".", &st)) + return false; + return root_dev == st.st_dev && root_ino == st.st_ino; +} diff -up patch-2.7.1/src/util.h.CVE-2015-1196 patch-2.7.1/src/util.h --- patch-2.7.1/src/util.h.CVE-2015-1196 2012-09-21 21:21:16.000000000 +0100 +++ patch-2.7.1/src/util.h 2015-01-20 13:29:14.306859564 +0000 @@ -69,6 +69,7 @@ enum file_id_type lookup_file_id (struct void set_queued_output (struct stat const *, bool); bool has_queued_output (struct stat const *); int stat_file (char const *, struct stat *); +bool cwd_is_root (char const *); enum file_attributes { FA_TIMES = 1, diff -up patch-2.7.1/tests/symlinks.CVE-2015-1196 patch-2.7.1/tests/symlinks --- patch-2.7.1/tests/symlinks.CVE-2015-1196 2012-09-19 02:18:42.000000000 +0100 +++ patch-2.7.1/tests/symlinks 2015-01-20 13:29:14.306859564 +0000 @@ -146,6 +146,59 @@ ncheck 'test ! -L symlink' # -------------------------------------------------------------- +# Patch should not create symlinks which point outside the working directory. + +cat > symlink-target.diff < bad-symlink-target1.diff < bad-symlink-target2.diff <