]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
copy: revert recent patch for vulnerable dirs
authorPaul Eggert <eggert@cs.ucla.edu>
Mon, 25 Sep 2017 06:05:17 +0000 (23:05 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Mon, 25 Sep 2017 06:10:56 +0000 (23:10 -0700)
I plan to propose a better patch to catch vulnerable parent
directories.
* NEWS, doc/coreutils.texi (Target directory): Document this.
* src/cp.c, src/install.c, src/ln.c, src/mv.c:
Do not include targetdir.h.
(target_directory_operand): Remove test for vulnerable parents.
* src/cp.c (stat_target_operand): Remove.  All uses removed.
* src/local.mk (noinst_HEADERS): Remove src/targetdir.h.
(src_ginstall_SOURCES, src_cp_SOURCES, src_ln_SOURCES)
(src_mv_SOURCES): Remove src/targetdir.c.
* src/targetdir.c, src/targetdir.h: Remove.
* tests/mv/vulnerable-target.sh: Remove.
* tests/local.mk (all_root_tests): Remove it.

NEWS
doc/coreutils.texi
src/cp.c
src/install.c
src/ln.c
src/local.mk
src/mv.c
src/targetdir.c [deleted file]
src/targetdir.h [deleted file]
tests/local.mk
tests/mv/vulnerable-target.sh [deleted file]

diff --git a/NEWS b/NEWS
index 15ae40bcbf0fcbd73662e38992b1e1317d1c0625..5d57c727a3c2d777127f1532389b446852f2424b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,17 +2,6 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
-** Changes in behavior
-
-  cp, install, ln, and mv by default now reject some usages that are
-  likely vulnerable to hijacking, to foil some attacks on unsafe
-  shared target directories.  For example, if /tmp/risky is
-  world-writable and /tmp/risky/d is a directory, 'cp f /tmp/risky/d'
-  now fails because the target directory is vulnerable.  To suppress
-  the vulnerability heuristic, append '/' to the name of the target
-  directory, or use the -t or -T options, or (for cp, ln, and mv) set
-  the POSIXLY_CORRECT environment variable.
-
 ** Bug fixes
 
   ptx -S no longer infloops for a pattern which returns zero-length matches.
index 9fa680a01224cdd287022f8460b2bca59ad95c35..47a9f6d253005d1c2decfd8392777ba230947b56 100644 (file)
@@ -1282,38 +1282,7 @@ The @command{cp}, @command{install}, @command{ln}, and @command{mv}
 commands normally treat the last operand specially when it is a
 directory or a symbolic link to a directory.  For example, @samp{cp
 source dest} is equivalent to @samp{cp source dest/source} if
-@file{dest} is a directory.
-
-@cindex vulnerable target directory
-@vindex POSIXLY_CORRECT
-However, if the destination is a directory whose name could be that of
-an ordinary file, and the directory's parent appears to be vulnerable
-to an attack by another user, then these programs report the
-vulnerability and fail.  If you are not worried about such an attack
-you can suppress this heuristic by appending @samp{/} to the
-destination's name, or by using the @option{--target-directory}
-(@option{-t}) or @option{--no-target-directory} (@option{-T}) options.
-(For @command{cp}, @command{ln}, and @command{mv} you can also
-suppress the heuristic by setting the @env{POSIXLY_CORRECT}
-environment variable.)  For example, if @file{/tmp/risky/d} is a
-directory whose parent @file{/tmp/risky} is world-writable and is
-not sticky, the command @samp{cp passwd /tmp/risky/d} fails with
-a diagnostic reporting a vulnerable target directory, as an attacker
-could replace @file{/tmp/risky/d} by a symbolic link to a victim
-directory while @command{cp} is running.  In this example, you can
-suppress the heuristic by issuing one of the following shell commands
-instead:
-
-@example
-# These can be hijacked if /tmp/risky is rwxrwxrwx!
-cp passwd /tmp/risky/d/
-cp -t /tmp/risky/d passwd
-cp -T passwd /tmp/risky/d/passwd
-POSIXLY_CORRECT=yes cp passwd /tmp/risky/d
-@end example
-
-As this example illustrates, sometimes the default behavior with
-destination directories is not exactly
+@file{dest} is a directory.  Sometimes this behavior is not exactly
 what is wanted, so these commands support the following options to
 allow more fine-grained control:
 
index d4efe5c31ca96477725251815ff7ba216d0dca3a..6743f1036582dce1793c224cc83224047d0dc940 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
@@ -33,7 +33,6 @@
 #include "ignore-value.h"
 #include "quote.h"
 #include "stat-time.h"
-#include "targetdir.h"
 #include "utimens.h"
 #include "acl.h"
 
@@ -568,20 +567,6 @@ make_dir_parents_private (char const *const_dir, size_t src_offset,
   return true;
 }
 
-/* Store FILE's status into *ST.  If FILE does not exist, set *NEW_DST.
-   If there is some other error, report it and exit.  */
-
-static void
-stat_target_operand (char const *file, struct stat *st, bool *new_dst)
-{
-  if (stat (file, st) != 0)
-    {
-      if (errno != ENOENT)
-        die (EXIT_FAILURE, errno, _("failed to access %s"), quoteaf (file));
-      *new_dst = true;
-    }
-}
-
 /* FILE is the last operand of this command.
    Return true if FILE is a directory.
    But report an error and exit if there is a problem accessing FILE,
@@ -592,17 +577,17 @@ stat_target_operand (char const *file, struct stat *st, bool *new_dst)
    Otherwise, set *NEW_DST.  */
 
 static bool
-target_directory_operand (char *file, struct stat *st, bool *new_dst)
+target_directory_operand (char const *file, struct stat *st, bool *new_dst)
 {
-  stat_target_operand (file, st, new_dst);
-  if (*new_dst || ! S_ISDIR (st->st_mode))
-    return false;
-  enum targetdir ty = targetdir_operand_type (file, NULL);
-  if (ty == TARGETDIR_VULNERABLE && ! getenv ("POSIXLY_CORRECT"))
-    die (EXIT_FAILURE, 0,
-         _("vulnerable target directory %s (append '/' to use anyway)"),
-         quoteaf (file));
-  return ty != TARGETDIR_NOT;
+  int err = (stat (file, st) == 0 ? 0 : errno);
+  bool is_a_dir = !err && S_ISDIR (st->st_mode);
+  if (err)
+    {
+      if (err != ENOENT)
+        die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
+      *new_dst = true;
+    }
+  return is_a_dir;
 }
 
 /* Scan the arguments, and copy each by calling copy.
@@ -638,7 +623,7 @@ do_copy (int n_files, char **file, const char *target_directory,
           usage (EXIT_FAILURE);
         }
       /* Update NEW_DST and SB, which may be checked below.  */
-      stat_target_operand (file[n_files -1], &sb, &new_dst);
+      ignore_value (target_directory_operand (file[n_files -1], &sb, &new_dst));
     }
   else if (!target_directory)
     {
index a66abb0b648e0ee6e3fbaba3b65198dacba10178..8c4f244f84cf5bc9adfc9f937d441b0287124fc0 100644 (file)
@@ -42,7 +42,6 @@
 #include "savewd.h"
 #include "selinux.h"
 #include "stat-time.h"
-#include "targetdir.h"
 #include "utimens.h"
 #include "xstrtol.h"
 
@@ -396,29 +395,20 @@ setdefaultfilecon (char const *file)
    directory if it referred to anything at all.  */
 
 static bool
-target_directory_operand (char *file)
+target_directory_operand (char const *file)
 {
+  char const *b = last_component (file);
+  size_t blen = strlen (b);
+  bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1]));
   struct stat st;
-  if (stat (file, &st) != 0)
-    {
-      int err = errno;
-      if (err == ENOENT)
-        {
-          char const *b = last_component (file);
-          size_t blen = strlen (b);
-          if (blen != 0 && ! ISSLASH (b[blen - 1]))
-            return false;
-        }
-      die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
-    }
-  if (! S_ISDIR (st.st_mode))
-    return false;
-  enum targetdir ty = targetdir_operand_type (file, NULL);
-  if (ty == TARGETDIR_VULNERABLE)
-    die (EXIT_FAILURE, 0,
-         _("vulnerable target directory %s (append '/' to use anyway)"),
+  int err = (stat (file, &st) == 0 ? 0 : errno);
+  bool is_a_dir = !err && S_ISDIR (st.st_mode);
+  if (err && err != ENOENT)
+    die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
+  if (is_a_dir < looks_like_a_dir)
+    die (EXIT_FAILURE, err, _("target %s is not a directory"),
          quoteaf (file));
-  return ty != TARGETDIR_NOT;
+  return is_a_dir;
 }
 
 /* Report that directory DIR was made, if OPTIONS requests this.  */
index 67b634d125580282e07a24565640fe56b04bf5e0..0ff8a0b27c3d1fe1dc18a7ba06a6985b5c819cbd 100644 (file)
--- a/src/ln.c
+++ b/src/ln.c
@@ -32,7 +32,6 @@
 #include "hash-triple.h"
 #include "relpath.h"
 #include "same.h"
-#include "targetdir.h"
 #include "yesno.h"
 #include "canonicalize.h"
 
@@ -121,33 +120,22 @@ errno_nonexisting (int err)
    directory if it referred to anything at all.  */
 
 static bool
-target_directory_operand (char *file)
+target_directory_operand (char const *file)
 {
+  char const *b = last_component (file);
+  size_t blen = strlen (b);
+  bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1]));
   struct stat st;
   int stat_result =
     (dereference_dest_dir_symlinks ? stat (file, &st) : lstat (file, &st));
-  if (stat_result != 0)
-    {
-      int err = errno;
-      if (! errno_nonexisting (err))
-        die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
-      char const *b = last_component (file);
-      size_t blen = strlen (b);
-      if (blen == 0 || ISSLASH (b[blen - 1]))
-        die (EXIT_FAILURE, err, _("target %s is not a directory"),
-             quoteaf (file));
-      return false;
-    }
-  if (! S_ISDIR (st.st_mode))
-    return false;
-  enum targetdir ty
-    = targetdir_operand_type (file,
-                              dereference_dest_dir_symlinks ? NULL : &st);
-  if (ty == TARGETDIR_VULNERABLE && ! getenv ("POSIXLY_CORRECT"))
-    die (EXIT_FAILURE, 0,
-         _("%s: vulnerable target directory (append '/' to use anyway)"),
+  int err = (stat_result == 0 ? 0 : errno);
+  bool is_a_dir = !err && S_ISDIR (st.st_mode);
+  if (err && ! errno_nonexisting (errno))
+    die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
+  if (is_a_dir < looks_like_a_dir)
+    die (EXIT_FAILURE, err, _("target %s is not a directory"),
          quoteaf (file));
-  return ty != TARGETDIR_NOT;
+  return is_a_dir;
 }
 
 /* Return FROM represented as relative to the dir of TARGET.
index 40061e0d64ada753f817e6201c81ed1b6f81c798..38b4bd44992c1a0aace3c4c03fa33a557f8b8b91 100644 (file)
@@ -59,7 +59,6 @@ noinst_HEADERS =              \
   src/remove.h                 \
   src/set-fields.h             \
   src/system.h                 \
-  src/targetdir.h              \
   src/uname.h
 
 EXTRA_DIST +=          \
@@ -345,8 +344,8 @@ copy_sources = \
 
 transform = s/ginstall/install/;/libstdbuf/!$(program_transform_name)
 
-src_ginstall_SOURCES = src/install.c src/prog-fprintf.c src/targetdir.c \
-  $(copy_sources) $(selinux_sources)
+src_ginstall_SOURCES = src/install.c src/prog-fprintf.c $(copy_sources) \
+                      $(selinux_sources)
 
 # This is for the '[' program.  Automake transliterates '[' and '/' to '_'.
 src___SOURCES = src/lbracket.c
@@ -354,7 +353,7 @@ src___SOURCES = src/lbracket.c
 nodist_src_coreutils_SOURCES = src/coreutils.h
 src_coreutils_SOURCES = src/coreutils.c
 
-src_cp_SOURCES = src/cp.c src/targetdir.c $(copy_sources) $(selinux_sources)
+src_cp_SOURCES = src/cp.c $(copy_sources) $(selinux_sources)
 src_dir_SOURCES = src/ls.c src/ls-dir.c
 src_vdir_SOURCES = src/ls.c src/ls-vdir.c
 src_id_SOURCES = src/id.c src/group-list.c
@@ -362,16 +361,14 @@ src_groups_SOURCES = src/groups.c src/group-list.c
 src_ls_SOURCES = src/ls.c src/ls-ls.c
 src_ln_SOURCES = src/ln.c \
   src/force-link.c src/force-link.h \
-  src/relpath.c src/relpath.h \
-  src/targetdir.c
+  src/relpath.c src/relpath.h
 src_chown_SOURCES = src/chown.c src/chown-core.c
 src_chgrp_SOURCES = src/chgrp.c src/chown-core.c
 src_kill_SOURCES = src/kill.c src/operand2sig.c
 src_realpath_SOURCES = src/realpath.c src/relpath.c src/relpath.h
 src_timeout_SOURCES = src/timeout.c src/operand2sig.c
 
-src_mv_SOURCES = src/mv.c src/remove.c src/targetdir.c \
-  $(copy_sources) $(selinux_sources)
+src_mv_SOURCES = src/mv.c src/remove.c $(copy_sources) $(selinux_sources)
 src_rm_SOURCES = src/rm.c src/remove.c
 
 src_mkdir_SOURCES = src/mkdir.c src/prog-fprintf.c $(selinux_sources)
index abe72f571b0373d6ea9d610f68bea43be87b70e4..9e80193dffb4b8b0242ab9d059cd3f155c3dfa99 100644 (file)
--- a/src/mv.c
+++ b/src/mv.c
@@ -32,7 +32,6 @@
 #include "filenamecat.h"
 #include "remove.h"
 #include "root-dev-ino.h"
-#include "targetdir.h"
 #include "priv-set.h"
 
 /* The official name of this program (e.g., no 'g' prefix).  */
@@ -149,23 +148,14 @@ cp_option_init (struct cp_options *x)
    than nonexistence (errno == ENOENT).  */
 
 static bool
-target_directory_operand (char *file)
+target_directory_operand (char const *file)
 {
   struct stat st;
-  if (stat (file, &st) == 0)
-    {
-      if (! S_ISDIR (st.st_mode))
-        return false;
-      enum targetdir ty = targetdir_operand_type (file, NULL);
-      if (ty == TARGETDIR_VULNERABLE && ! getenv ("POSIXLY_CORRECT"))
-        die (EXIT_FAILURE, 0,
-             _("vulnerable target directory %s (append / to use anyway)"),
-             quoteaf (file));
-      return ty != TARGETDIR_NOT;
-    }
-  if (errno != ENOENT)
-    die (EXIT_FAILURE, errno, _("failed to access %s"), quoteaf (file));
-  return false;
+  int err = (stat (file, &st) == 0 ? 0 : errno);
+  bool is_a_dir = !err && S_ISDIR (st.st_mode);
+  if (err && err != ENOENT)
+    die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file));
+  return is_a_dir;
 }
 
 /* Move SOURCE onto DEST.  Handles cross-file-system moves.
diff --git a/src/targetdir.c b/src/targetdir.c
deleted file mode 100644 (file)
index d577893..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/* Check target directories for commands like cp and mv.
-   Copyright 2017 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   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 <https://www.gnu.org/licenses/>.  */
-
-/* Written by Paul Eggert.  */
-
-#include <config.h>
-#include <targetdir.h>
-
-#include <die.h>
-#include <root-uid.h>
-
-#include "system.h"
-
-/* Check whether DIR, which the caller presumably has already verified
-   is a directory or a symlink to a directory, is likely to be
-   vulnerable as the target directory of a command like 'cp ... DIR'.
-   If DIR_LSTAT is nonnull, it is the result of calling lstat on DIR.
-
-   Return TARGETDIR_OK if DIR is OK, which does not necessarily mean
-   that DIR is a directory or that it is invulnerable to the attack,
-   only that it satisfies the heuristics.  Return TARGETDIR_NOT if DIR
-   becomes inaccessible or a non-directory while checking things.
-   Return TARGETDIR_VULNERABLE if the heuristics suggest that DIR is a
-   likely candidate to be hijacked by a symlink attack.
-
-   This function might temporarily modify the DIR string; it restores
-   the string to its original value before returning.  */
-
-extern enum targetdir
-targetdir_operand_type (char *restrict dir,
-                        struct stat const *restrict dir_lstat)
-{
-  char *lc = last_component (dir);
-  size_t lclen = strlen (lc);
-
-  /* If DIR ends in / or has a last component of . or .. then it is
-     good enough.  */
-  if (lclen == 0 || ISSLASH (lc[lclen - 1])
-      || STREQ (lc, ".") || STREQ (lc, ".."))
-    return TARGETDIR_OK;
-
-  char lc0 = *lc;
-  *lc = '\0';
-  struct stat parent_stat;
-  bool parent_stat_ok = stat (*dir ? dir : ".", &parent_stat) == 0;
-  *lc = lc0;
-
-  /* If DIR's parent cannot be statted, DIR can't be statted either.  */
-  if (! parent_stat_ok)
-    return TARGETDIR_NOT;
-
-  uid_t euid = geteuid ();
-  if (parent_stat.st_uid == ROOT_UID || parent_stat.st_uid == euid)
-    {
-      /* If no other non-root user can write the parent directory, it
-         is safe.  If the parent directory's UID and GID are the same,
-         assume the common convention of a single-user group with the
-         same ID, to avoid returning TARGETDIR_VULNERABLE when users
-         employing this convention have mode-775 directories.  */
-      if (! (parent_stat.st_mode
-             & (S_IWOTH
-                | (parent_stat.st_uid == parent_stat.st_gid ? 0 : S_IWGRP))))
-        return TARGETDIR_OK;
-
-      /* If the parent is sticky, and no other non-root user owns
-         either the parent or DIR, it should be OK.  Do not follow
-         symlinks when checking DIR for this.  */
-      if (parent_stat.st_mode & S_ISVTX)
-        {
-          struct stat st;
-          if (!dir_lstat)
-            {
-              if (lstat (dir, &st) != 0)
-                return TARGETDIR_NOT;
-              dir_lstat = &st;
-            }
-          if (dir_lstat->st_uid == ROOT_UID || dir_lstat->st_uid == euid)
-            return TARGETDIR_OK;
-        }
-    }
-
-  return TARGETDIR_VULNERABLE;
-}
diff --git a/src/targetdir.h b/src/targetdir.h
deleted file mode 100644 (file)
index 29ea867..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-struct stat;
-enum targetdir { TARGETDIR_VULNERABLE = -1, TARGETDIR_NOT, TARGETDIR_OK };
-enum targetdir targetdir_operand_type (char *restrict,
-                                       struct stat const *restrict);
index 70c49e8b7d1564b2df7ba95e0e8d0555f1f4e6bf..42e247e6f2ec6ed0449e887f0012a0ebe53d3f60 100644 (file)
@@ -662,7 +662,6 @@ all_tests =                                 \
   tests/mv/to-symlink.sh                       \
   tests/mv/trailing-slash.sh                   \
   tests/mv/update.sh                           \
-  tests/mv/vulnerable-target.sh                        \
   tests/readlink/can-e.sh                      \
   tests/readlink/can-f.sh                      \
   tests/readlink/can-m.sh                      \
diff --git a/tests/mv/vulnerable-target.sh b/tests/mv/vulnerable-target.sh
deleted file mode 100755 (executable)
index 49ee363..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/sh
-# Check that mv diagnoses vulnerable target directories.
-
-# Copyright 2017 Free Software Foundation, Inc.
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# 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 <https://www.gnu.org/licenses/>.
-
-. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ mv
-
-unset POSIXLY_CORRECT
-
-mkdir -m a+rwx risky || framework_failure_
-mkdir risky/d || framework_failure_
-echo foo >foo || framework_failure_
-
-returns_ 1 mv foo risky/d || fail=1
-mv foo risky/d/ || fail=1
-mv risky/d/foo . || fail=1
-mv -t risky/d foo || fail=1
-mv risky/d/foo . || fail=1
-mv -T foo risky/d/foo || fail=1
-mv risky/d/foo . || fail=1
-POSIXLY_CORRECT=yes mv foo risky/d || fail=1
-mv risky/d/foo . || fail=1
-
-Exit $fail