diff options
author | Jim Meyering <meyering@redhat.com> | 2007-11-16 09:31:15 +0100 |
---|---|---|
committer | Jim Meyering <meyering@redhat.com> | 2007-11-22 00:19:06 +0100 |
commit | 2bdc48121916ab0d7bb7d0cb5cee25549c3705c9 (patch) | |
tree | 8a808a3c99c29aa7f5e958f7bac4f274f8eb753c | |
parent | fa636dcf6a298a4935ba950d433c33a07d0f8504 (diff) | |
download | coreutils-2bdc48121916ab0d7bb7d0cb5cee25549c3705c9.tar.xz |
cp: by default, refuse to copy through a dangling destination symlink
* NEWS: Mention this change.
* doc/coreutils.texi (cp invocation): Describe the new behavior.
* src/copy.c: No longer include "canonicalize.h".
(copy_reg): Upon failure to open a dangling destination symlink, don't
canonicalize the name, but rather fail (default) or, with POSIXLY_CORRECT,
repeat the open call without O_EXCL (potentially dangerous).
* src/copy.h (struct cp_options) [open_dangling_dest_symlink]:
New member. Reorder the others, grouping "bool" and "enum"
members together.
* tests/cp/thru-dangling: Test for changed and new behavior.
* src/cp.c (cp_option_init): Initialize new member.
* src/install.c (cp_option_init): Likewise.
* src/mv.c (cp_option_init): Likewise.
Signed-off-by: Jim Meyering <meyering@redhat.com>
-rw-r--r-- | ChangeLog | 18 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | doc/coreutils.texi | 13 | ||||
-rw-r--r-- | src/copy.c | 46 | ||||
-rw-r--r-- | src/copy.h | 35 | ||||
-rw-r--r-- | src/cp.c | 7 | ||||
-rw-r--r-- | src/install.c | 1 | ||||
-rw-r--r-- | src/mv.c | 1 | ||||
-rwxr-xr-x | tests/cp/thru-dangling | 13 |
9 files changed, 94 insertions, 43 deletions
@@ -1,3 +1,21 @@ +2007-11-22 Jim Meyering <meyering@redhat.com> + + cp: by default, refuse to copy through a dangling destination symlink + * NEWS: Mention this change. + * doc/coreutils.texi (cp invocation): Describe the new behavior. + * src/copy.c: No longer include "canonicalize.h". + (copy_reg): Upon failure to open a dangling destination symlink, + don't canonicalize the name, but rather fail (default) or, with + POSIXLY_CORRECT, repeat the open call without O_EXCL (potentially + dangerous). + * src/copy.h (struct cp_options) [open_dangling_dest_symlink]: + New member. Reorder the others, grouping "bool" and "enum" + members together. + * tests/cp/thru-dangling: Test for changed and new behavior. + * src/cp.c (cp_option_init): Initialize new member. + * src/install.c (cp_option_init): Likewise. + * src/mv.c (cp_option_init): Likewise. + 2007-11-21 Pádraig Brady <P@draigBrady.com> * doc/coreutils.texi (split invocation): Improve the @@ -15,6 +15,9 @@ GNU coreutils NEWS -*- outline -*- ** Changes in behavior + cp, by default, refuses to copy through a dangling destination symlink + Set POSIXLY_CORRECT if you require the old, risk-prone behavior. + pr -F no longer suppresses the footer or the first two blank lines in the header. This is for compatibility with BSD and POSIX. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 136a3f7fa..6ed1171de 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -6920,10 +6920,15 @@ recursively. This default can be overridden with the @option{-H} options. If more than one of these options is specified, the last one silently overrides the others. -When copying to a symbolic link, @command{cp} normally follows the -link when creating or copying to a regular file, even if the link is -dangling. However, @command{cp} does not follow these links when -creating directories or special files. Also, when an option like +When copying to a symbolic link, @command{cp} follows the +link only when it refers to an existing regular file. +However, when copying to a dangling symbolic link, @command{cp} +refuses by default, and fails with a diagnostic, since the operation +is inherently dangerous. This behavior is contrary to historical +practice and to @acronym{POSIX}. +Set @env{POSIXLY_CORRECT} to make @command{cp} attempt to create +the target of a dangling destination symlink, in spite of the possible risk. +Also, when an option like @option{--backup} or @option{--link} acts to rename or remove the destination before copying, @command{cp} renames or removes the symbolic link rather than the file it points to. diff --git a/src/copy.c b/src/copy.c index 1a265e3c2..31e29b1bf 100644 --- a/src/copy.c +++ b/src/copy.c @@ -33,7 +33,6 @@ #include "acl.h" #include "backupfile.h" #include "buffer-lcm.h" -#include "canonicalize.h" #include "copy.h" #include "cp-hash.h" #include "euidaccess.h" @@ -265,7 +264,6 @@ copy_reg (char const *src_name, char const *dst_name, char *buf; char *buf_alloc = NULL; char *name_alloc = NULL; - char const *followed_dest_name = dst_name; int dest_desc; int dest_errno; int source_desc; @@ -362,35 +360,39 @@ copy_reg (char const *src_name, char const *dst_name, if (*new_dst) { - int open_flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY; - dest_desc = open (dst_name, open_flags, + int open_flags = O_WRONLY | O_CREAT | O_BINARY; + dest_desc = open (dst_name, open_flags | O_EXCL , dst_mode & ~omitted_permissions); dest_errno = errno; /* When trying to copy through a dangling destination symlink, the above open fails with EEXIST. If that happens, and - lstat'ing the DST_NAME shows that it is a symlink, repeat - the open call, but this time with the name of the final, - missing directory entry. All of this is relevant only for - cp, i.e., not in move_mode. */ + lstat'ing the DST_NAME shows that it is a symlink, then we + have a problem: trying to resolve this dangling symlink to + a directory/destination-entry pair is fundamentally racy, + so punt. If POSIXLY_CORRECT is set, simply call open again, + but without O_EXCL (potentially dangerous). If not, fail + with a diagnostic. These shenanigans are necessary only + when copying, i.e., not in move_mode. */ if (dest_desc < 0 && dest_errno == EEXIST && ! x->move_mode) { struct stat dangling_link_sb; if (lstat (dst_name, &dangling_link_sb) == 0 && S_ISLNK (dangling_link_sb.st_mode)) { - /* FIXME: This is way overkill, since all that's needed - is to follow the symlink that is the last file name - component. */ - name_alloc = - canonicalize_filename_mode (dst_name, CAN_MISSING); - if (name_alloc) + if (x->open_dangling_dest_symlink) { - followed_dest_name = name_alloc; - dest_desc = open (followed_dest_name, open_flags, + dest_desc = open (dst_name, open_flags, dst_mode & ~omitted_permissions); dest_errno = errno; } + else + { + error (0, 0, _("not writing through dangling symlink %s"), + quote (dst_name)); + return_val = false; + goto close_src_desc; + } } } } @@ -591,7 +593,7 @@ copy_reg (char const *src_name, char const *dst_name, timespec[0] = get_stat_atime (src_sb); timespec[1] = get_stat_mtime (src_sb); - if (gl_futimens (dest_desc, followed_dest_name, timespec) != 0) + if (gl_futimens (dest_desc, dst_name, timespec) != 0) { error (0, errno, _("preserving times for %s"), quote (dst_name)); if (x->require_preserve) @@ -604,7 +606,7 @@ copy_reg (char const *src_name, char const *dst_name, if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb)) { - switch (set_owner (x, followed_dest_name, dest_desc, + switch (set_owner (x, dst_name, dest_desc, src_sb->st_uid, src_sb->st_gid)) { case -1: @@ -617,24 +619,24 @@ copy_reg (char const *src_name, char const *dst_name, } } - set_author (followed_dest_name, dest_desc, src_sb); + set_author (dst_name, dest_desc, src_sb); if (x->preserve_mode || x->move_mode) { - if (copy_acl (src_name, source_desc, followed_dest_name, dest_desc, src_mode) != 0 + if (copy_acl (src_name, source_desc, dst_name, dest_desc, src_mode) != 0 && x->require_preserve) return_val = false; } else if (x->set_mode) { - if (set_acl (followed_dest_name, dest_desc, x->mode) != 0) + if (set_acl (dst_name, dest_desc, x->mode) != 0) return_val = false; } else if (omitted_permissions) { omitted_permissions &= ~ cached_umask (); if (omitted_permissions - && fchmod_or_lchmod (dest_desc, followed_dest_name, dst_mode) != 0) + && fchmod_or_lchmod (dest_desc, dst_name, dst_mode) != 0) { error (0, errno, _("preserving permissions for %s"), quote (dst_name)); diff --git a/src/copy.h b/src/copy.h index 2ea7c0549..47c97f389 100644 --- a/src/copy.h +++ b/src/copy.h @@ -82,13 +82,25 @@ struct cp_options { enum backup_type backup_type; + /* How to handle symlinks in the source. */ + enum Dereference_symlink dereference; + + /* This value is used to determine whether to prompt before removing + each existing destination file. It works differently depending on + whether move_mode is set. See code/comments in copy.c. */ + enum Interactive interactive; + + /* Control creation of sparse files. */ + enum Sparse_type sparse_mode; + + /* Set the mode of the destination file to exactly this value + if SET_MODE is nonzero. */ + mode_t mode; + /* If true, copy all files except (directories and, if not dereferencing them, symbolic links,) as if they were regular files. */ bool copy_as_regular; - /* How to handle symlinks in the source. */ - enum Dereference_symlink dereference; - /* If true, remove each existing destination nondirectory before trying to open it. */ bool unlink_dest_before_opening; @@ -104,11 +116,6 @@ struct cp_options Create destination directories as usual. */ bool hard_link; - /* This value is used to determine whether to prompt before removing - each existing destination file. It works differently depending on - whether move_mode is set. See code/comments in copy.c. */ - enum Interactive interactive; - /* If true, rather than copying, first attempt to use rename. If that fails, then resort to copying. */ bool move_mode; @@ -168,13 +175,6 @@ struct cp_options set it based on current umask modified by UMASK_KILL. */ bool set_mode; - /* Set the mode of the destination file to exactly this value - if SET_MODE is nonzero. */ - mode_t mode; - - /* Control creation of sparse files. */ - enum Sparse_type sparse_mode; - /* If true, create symbolic links instead of copying files. Create destination directories as usual. */ bool symbolic_link; @@ -189,6 +189,11 @@ struct cp_options /* If true, stdin is a tty. */ bool stdin_tty; + /* If true, open a dangling destination symlink when not in move_mode. + Otherwise, copy_reg gives a diagnostic (it refuses to write through + such a symlink) and returns false. */ + bool open_dangling_dest_symlink; + /* This is a set of destination name/inode/dev triples. Each such triple represents a file we have created corresponding to a source file name that was specified on the command line. Use it to avoid clobbering @@ -761,6 +761,13 @@ cp_option_init (struct cp_options *x) x->update = false; x->verbose = false; + + /* By default, refuse to open a dangling destination symlink, because + in general one cannot do that safely, give the current semantics of + open's O_EXCL flag, (which POSIX doesn't even allow cp to use, btw). + But POSIX requires it. */ + x->open_dangling_dest_symlink = getenv ("POSIXLY_CORRECT") != NULL; + x->dest_info = NULL; x->src_info = NULL; } diff --git a/src/install.c b/src/install.c index 8e4875844..802dfcf77 100644 --- a/src/install.c +++ b/src/install.c @@ -190,6 +190,7 @@ cp_option_init (struct cp_options *x) x->mode = S_IRUSR | S_IWUSR; x->stdin_tty = false; + x->open_dangling_dest_symlink = false; x->update = false; x->preserve_security_context = false; x->verbose = false; @@ -146,6 +146,7 @@ cp_option_init (struct cp_options *x) x->mode = 0; x->stdin_tty = isatty (STDIN_FILENO); + x->open_dangling_dest_symlink = false; x->update = false; x->verbose = false; x->dest_info = NULL; diff --git a/tests/cp/thru-dangling b/tests/cp/thru-dangling index 0503af943..0256a167c 100755 --- a/tests/cp/thru-dangling +++ b/tests/cp/thru-dangling @@ -1,5 +1,5 @@ #!/bin/sh -# Ensure that cp works when the destination is a dangling symlink +# Ensure that cp works as documented, when the destination is a dangling symlink # Copyright (C) 2007 Free Software Foundation, Inc. @@ -26,10 +26,19 @@ fi ln -s no-such dangle || framework_failure echo hi > f || framework_failure echo hi > exp || framework_failure +echo "cp: not writing through dangling symlink \`dangle'" \ + > exp-err || framework_failure fail=0 -cp f dangle > out 2>&1 || fail=1 +# Starting with 6.9.90, this usage fails, by default: +cp f dangle > err 2>&1 && fail=1 + +compare err exp-err || fail=1 +test -f no-such && fail=1 + +# But you can set POSIXLY_CORRECT to get the historical behavior. +POSIXLY_CORRECT=1 cp f dangle > out 2>&1 || fail=1 cat no-such >> out || fail=1 compare out exp || fail=1 |