]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
cp: fix symlink checks when overwriting files
authorPádraig Brady <P@draigBrady.com>
Mon, 14 May 2018 09:26:05 +0000 (02:26 -0700)
committerPádraig Brady <P@draigBrady.com>
Tue, 15 May 2018 16:55:19 +0000 (09:55 -0700)
Ensure this _does_ recreate the symlink
  Given "path1" and "path2" are on different devices.
  $ touch "path1/file"
  $ cd path2/; ln -s path1/file
  $ cp -dsf path1/file .

Ensure this does _not_ overwrite file
  $ touch file
  $ ln -s file l1
  $ cp -sf l1 file

* src/copy.c (same_file_ok): Remove device ids from consideration,
instead deferring to future EXDEV with --link or allowing
the first case above to work.
Also ensure that we do not exist this function too early,
when the destination file is not a symlink, which protects
against the second case.
* tests/cp/cross-dev-symlink.sh: Add a test for the first case.
* tests/cp/same-file.sh: Add a test for the second case above.
* NEWS: Mention the bug fixes.
* THANKS.in: Mention the reporters who also analyzed the code.
Fixes https://bugs.gnu.org/31364

NEWS
THANKS.in
src/copy.c
tests/cp/cross-dev-symlink.sh [new file with mode: 0755]
tests/cp/same-file.sh
tests/local.mk

diff --git a/NEWS b/NEWS
index 3f34a11094fb8820edae666b126900ec9512b8f3..f981596ba905c5d331b64a741dcb55616de7b8b1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,15 +4,12 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Bug fixes
 
-  'ls -aA' is now equivalent to 'ls -A', since -A now overrides -a.
-  [bug introduced in coreutils-5.3.0]
-
-  'mv -n A B' no longer suffers from a race condition that can
-  overwrite a simultaneously-created B.  This bug fix requires
-  platform support for the renameat2 or renameatx_np syscalls, found
-  in recent Linux and macOS kernels.  As a side effect, ‘mv -n A A’
-  now silently does nothing if A exists.
-  [bug introduced with coreutils-7.1]
+  'cp --symlink SRC DST' will again correctly validate DST.
+  If DST is a regular file and SRC is a symlink to DST,
+  then cp will no longer allow that operation to clobber DST.
+  Also with -d, if DST is a symlink, then it can always be replaced,
+  even if it points to SRC on a separate device.
+  [bugs introduced with coreutils-8.27]
 
   'cp -n -u' and 'mv -n -u' now consistently ignore the -u option.
   Previously, this option combination suffered from race conditions
@@ -32,6 +29,17 @@ GNU coreutils NEWS                                    -*- outline -*-
   display width between 6 and 12 inclusive.  Previously this would have
   output ambiguous months for Arabic or Catalan locales.
 
+  'ls -aA' is now equivalent to 'ls -A', since -A now overrides -a.
+  [bug introduced in coreutils-5.3.0]
+
+  'mv -n A B' no longer suffers from a race condition that can
+  overwrite a simultaneously-created B.  This bug fix requires
+  platform support for the renameat2 or renameatx_np syscalls, found
+  in recent Linux and macOS kernels.  As a side effect, ‘mv -n A A’
+  now silently does nothing if A exists.
+  [bug introduced with coreutils-7.1]
+
+
 ** Improvements
 
   cut supports line lengths up to the max file size on 32 bit systems.
index c63cc9bc7f1e89f49d28858848952104ff4be261..3951b66d69905b7135404af82fb95ea2fd25dbff 100644 (file)
--- a/THANKS.in
+++ b/THANKS.in
@@ -261,6 +261,7 @@ Ian Kent                            ikent@redhat.com
 Ian Lance Taylor                    ian@cygnus.com
 Ian Turner                          vectro@pipeline.com
 Iida Yosiaki                        iida@gnu.org
+Illia Bobyr                         ibobyr@google.com
 Ilya N. Golubev                     gin@mo.msk.ru
 Ingo Saitz                          ingo@debian.org
 Ivan Labath                         labath3@st.fmph.uniba.sk
@@ -285,6 +286,7 @@ Jan-Pawel Wrozstinski               jpwroz@gmail.com
 Jari Aalto                          jari.aalto@cante.net
 Jarkko Hietaniemi                   jhi@epsilon.hut.fi
 Jarod Wilson                        jwilson@redhat.com
+Jason Smith                         jasonmsmith@google.com
 Jean Charles Delepine               delepine@u-picardie.fr
 Jean-Pierre Tosoni                  jpt.7196@gmail.com
 Jeff Moore                          jbm@mordor.com
index 4998c83e6a6e37eb5060efca08a08dd3157adbae..0407c5689b8e7eb9837ba9900e8b6eb1c79e929f 100644 (file)
@@ -1627,14 +1627,9 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
         }
     }
 
-  /* It's ok to remove a destination symlink.  But that works only
-     when creating symbolic links, or when the source and destination
-     are on the same file system and when creating hard links or when
-     unlinking before opening the destination.  */
-  if (x->symbolic_link
-      || ((x->hard_link || x->unlink_dest_before_opening)
-          && S_ISLNK (dst_sb_link->st_mode)))
-    return dst_sb_link->st_dev == src_sb_link->st_dev;
+  /* It's ok to recreate a destination symlink. */
+  if (x->symbolic_link && S_ISLNK (dst_sb_link->st_mode))
+    return true;
 
   if (x->dereference == DEREF_NEVER)
     {
@@ -1651,10 +1646,13 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
       if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb))
         return true;
 
-      /* FIXME: shouldn't this be testing whether we're making symlinks?  */
       if (x->hard_link)
         {
-          *return_now = true;
+          /* It's ok to attempt to hardlink the same file,
+            and return early if not replacing a symlink.
+            Note we need to return early to avoid a later
+            unlink() of DST (when SRC is a symlink).  */
+          *return_now = ! S_ISLNK (dst_sb_link->st_mode);
           return true;
         }
     }
diff --git a/tests/cp/cross-dev-symlink.sh b/tests/cp/cross-dev-symlink.sh
new file mode 100755 (executable)
index 0000000..12ef258
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Ensure symlinks can be replaced across devices
+
+# Copyright (C) 2018 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_ cp
+require_root_
+
+cwd=$(pwd)
+cleanup_() { cd /; umount "$cwd/mnt"; }
+
+truncate -s100M fs.img || framework_failure_
+mkfs -t ext4 fs.img    || skip_ 'failed to create ext4 file system'
+mkdir mnt              || framework_failure_
+mount fs.img mnt       || skip_ 'failed to mount ext4 file system'
+
+mkdir mnt/path1 || framework_failure_
+touch mnt/path1/file || framework_failure_
+mkdir path2 || framework_failure_
+cd path2 && ln -s ../mnt/path1/file || framework_failure_
+
+cp -dsf ../mnt/path1/file . || fail=1
+
+Exit $fail
index 01bdb987f27bf5ebd1cdc3c869361af1508e5ba8..e50a991d4c69e0af3df30654e0639a14a52e8437 100755 (executable)
@@ -47,7 +47,7 @@ contents=XYZ
 for args in 'foo symlink' 'symlink foo' 'foo foo' 'sl1 sl2' \
             'foo hardlink' 'hlsl sl2'; do
   for options in '' -d -f -df --rem -b -bd -bf -bdf \
-                 -l -dl -fl -dfl -bl -bdl -bfl -bdfl; do
+                 -l -dl -fl -dfl -bl -bdl -bfl -bdfl -s -sf; do
     case $args$options in
       # These tests are not portable.
       # They all involve making a hard link to a symbolic link.
@@ -100,6 +100,7 @@ for args in 'foo symlink' 'symlink foo' 'foo foo' 'sl1 sl2' \
         # and put brackets around the output.
         if test -s _err; then
           sed '
+            s/symbolic link/symlink/
             s/^[^:]*:\([^:]*\).*/cp:\1/
             1s/^/[/
             $s/$/]/
@@ -149,6 +150,8 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -bdl (foo symlink symlink.~1~ -> foo)
 0 -bfl (foo symlink symlink.~1~ -> foo)
 0 -bdfl (foo symlink symlink.~1~ -> foo)
+1 -s [cp: cannot create symlink 'symlink' to 'foo'] (foo symlink -> foo)
+0 -sf (foo symlink -> foo)
 
 1 [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo)
 1 -d [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo)
@@ -164,6 +167,8 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -fl (foo symlink -> foo)
 0 -bl (foo symlink -> foo)
 0 -bfl (foo symlink -> foo)
+1 -s [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo)
+1 -sf [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo)
 
 1 [cp: 'foo' and 'foo' are the same file] (foo)
 1 -d [cp: 'foo' and 'foo' are the same file] (foo)
@@ -182,6 +187,8 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -bdl (foo)
 0 -bfl (foo foo.~1~)
 0 -bdfl (foo foo.~1~)
+1 -s [cp: 'foo' and 'foo' are the same file] (foo)
+1 -sf [cp: 'foo' and 'foo' are the same file] (foo)
 
 1 [cp: 'sl1' and 'sl2' are the same file] (foo sl1 -> foo sl2 -> foo)
 0 -d (foo sl1 -> foo sl2 -> foo)
@@ -196,6 +203,8 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -fl (foo sl1 -> foo sl2)
 0 -bl (foo sl1 -> foo sl2 sl2.~1~ -> foo)
 0 -bfl (foo sl1 -> foo sl2 sl2.~1~ -> foo)
+1 -s [cp: cannot create symlink 'sl2' to 'sl1'] (foo sl1 -> foo sl2 -> foo)
+0 -sf (foo sl1 -> foo sl2 -> sl1)
 
 1 [cp: 'foo' and 'hardlink' are the same file] (foo hardlink)
 1 -d [cp: 'foo' and 'hardlink' are the same file] (foo hardlink)
@@ -214,6 +223,8 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -bdl (foo hardlink)
 0 -bfl (foo hardlink)
 0 -bdfl (foo hardlink)
+1 -s [cp: 'foo' and 'hardlink' are the same file] (foo hardlink)
+1 -sf [cp: 'foo' and 'hardlink' are the same file] (foo hardlink)
 
 1 [cp: 'hlsl' and 'sl2' are the same file] (foo hlsl -> foo sl2 -> foo)
 0 -d (foo hlsl -> foo sl2 -> foo)
@@ -232,6 +243,8 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -bdl (foo hlsl -> foo sl2 -> foo)
 0 -bfl (foo hlsl -> foo sl2 sl2.~1~ -> foo)
 0 -bdfl (foo hlsl -> foo sl2 -> foo)
+1 -s [cp: cannot create symlink 'sl2' to 'hlsl'] (foo hlsl -> foo sl2 -> foo)
+0 -sf (foo hlsl -> foo sl2 -> hlsl)
 
 EOF
 
index e60ea1d1e01e37dafcab4751da9e243d3a806a2a..1f8b189c23529b9e6b47cc8fe35689d4dbcd90d5 100644 (file)
@@ -113,6 +113,7 @@ all_root_tests =                            \
   tests/cp/cp-mv-enotsup-xattr.sh              \
   tests/cp/capability.sh                       \
   tests/cp/sparse-fiemap.sh                    \
+  tests/cp/cross-dev-symlink.sh                        \
   tests/dd/skip-seek-past-dev.sh               \
   tests/df/problematic-chars.sh                        \
   tests/df/over-mount-device.sh                        \