Fix for CVE-2013-4235.
Backport of:
* faeab50e710131816b261de66141524898c2c487 (main fix)
* dab764d0195fc16d1d39330eee8a33e8917826d8
* e666de721aedf6deae8b11bef2e0701cf110f307
* 83d42e9e884829be028b3d2b276dc35bfc8c30cf
* 479fc86fbe4add5ae0c66571965627c8fbac881d
* 3db58ddf6394dfd1a0fe81dcb94dc81fe9fe6d6a (partly)
* f3bdb28e57e5e38c1e89347976c7d61a181eec32 (regression fix)
* 10cd68e0f04b48363eb32d2c6e168b358fb27810 (regression fix)
Index: shadow-4.2.1/libmisc/copydir.c
===================================================================
--- shadow-4.2.1.orig/libmisc/copydir.c
+++ shadow-4.2.1/libmisc/copydir.c
@@ -69,42 +69,43 @@ struct link_name {
 };
 static /*@exposed@*/struct link_name *links;
 
-static int copy_entry (const char *src, const char *dst,
+struct path_info {
+	const char *full_path;
+	int dirfd;
+	const char *name;
+};
+
+static int copy_entry (const struct path_info *src, const struct path_info *dst,
                        bool reset_selinux,
                        uid_t old_uid, uid_t new_uid,
                        gid_t old_gid, gid_t new_gid);
-static int copy_dir (const char *src, const char *dst,
+static int copy_dir (const struct path_info *src, const struct path_info *dst,
                      bool reset_selinux,
-                     const struct stat *statp, const struct timeval mt[],
+                     const struct stat *statp, const struct timespec mt[],
                      uid_t old_uid, uid_t new_uid,
                      gid_t old_gid, gid_t new_gid);
-#ifdef	S_IFLNK
 static /*@null@*/char *readlink_malloc (const char *filename);
-static int copy_symlink (const char *src, const char *dst,
+static int copy_symlink (const struct path_info *src, const struct path_info *dst,
                          unused bool reset_selinux,
-                         const struct stat *statp, const struct timeval mt[],
+                         const struct stat *statp, const struct timespec mt[],
                          uid_t old_uid, uid_t new_uid,
                          gid_t old_gid, gid_t new_gid);
-#endif				/* S_IFLNK */
-static int copy_hardlink (const char *dst,
+static int copy_hardlink (const struct path_info *dst,
                           unused bool reset_selinux,
                           struct link_name *lp);
-static int copy_special (const char *src, const char *dst,
+static int copy_special (const struct path_info *src, const struct path_info *dst,
                          bool reset_selinux,
-                         const struct stat *statp, const struct timeval mt[],
+                         const struct stat *statp, const struct timespec mt[],
                          uid_t old_uid, uid_t new_uid,
                          gid_t old_gid, gid_t new_gid);
-static int copy_file (const char *src, const char *dst,
+static int copy_file (const struct path_info *src, const struct path_info *dst,
                       bool reset_selinux,
-                      const struct stat *statp, const struct timeval mt[],
+                      const struct stat *statp, const struct timespec mt[],
                       uid_t old_uid, uid_t new_uid,
                       gid_t old_gid, gid_t new_gid);
-static int chown_if_needed (const char *dst, const struct stat *statp,
+static int chownat_if_needed (const struct path_info *dst, const struct stat *statp,
                             uid_t old_uid, uid_t new_uid,
                             gid_t old_gid, gid_t new_gid);
-static int lchown_if_needed (const char *dst, const struct stat *statp,
-                             uid_t old_uid, uid_t new_uid,
-                             gid_t old_gid, gid_t new_gid);
 static int fchown_if_needed (int fdst, const struct stat *statp,
                              uid_t old_uid, uid_t new_uid,
                              gid_t old_gid, gid_t new_gid);
@@ -138,6 +139,57 @@ static struct error_context ctx = {
 };
 #endif				/* WITH_ACL || WITH_ATTR */
 
+#ifdef WITH_ACL
+static int perm_copy_path(const struct path_info *src,
+						  const struct path_info *dst,
+						  struct error_context *errctx)
+{
+	int src_fd, dst_fd, ret;
+
+	src_fd = openat(src->dirfd, src->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
+	if (src_fd < 0) {
+		return -1;
+	}
+
+	dst_fd = openat(dst->dirfd, dst->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
+	if (dst_fd < 0) {
+		(void) close (src_fd);
+		return -1;
+	}
+
+	ret = perm_copy_fd(src->full_path, src_fd, dst->full_path, dst_fd, errctx);
+	(void) close (src_fd);
+	(void) close (dst_fd);
+	return ret;
+}
+#endif				/* WITH_ACL */
+
+#ifdef WITH_ATTR
+static int attr_copy_path(const struct path_info *src,
+						  const struct path_info *dst,
+						  int (*callback) (const char *, struct error_context *),
+						  struct error_context *errctx)
+{
+	int src_fd, dst_fd, ret;
+
+	src_fd = openat(src->dirfd, src->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
+	if (src_fd < 0) {
+		return -1;
+	}
+
+	dst_fd = openat(dst->dirfd, dst->name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
+	if (dst_fd < 0) {
+		(void) close (src_fd);
+		return -1;
+	}
+
+	ret = attr_copy_fd(src->full_path, src_fd, dst->full_path, dst_fd, callback, errctx);
+	(void) close (src_fd);
+	(void) close (dst_fd);
+	return ret;
+}
+#endif				/* WITH_ATTR */
+
 /*
  * remove_link - delete a link from the linked list
  */
@@ -210,51 +262,35 @@ static /*@exposed@*/ /*@null@*/struct li
 	return NULL;
 }
 
-/*
- * copy_tree - copy files in a directory tree
- *
- *	copy_tree() walks a directory tree and copies ordinary files
- *	as it goes.
- *
- *	When reset_selinux is enabled, extended attributes (and thus
- *	SELinux attributes) are not copied.
- *
- *	old_uid and new_uid are used to set the ownership of the copied
- *	files. Unless old_uid is set to -1, only the files owned by
- *	old_uid have their ownership changed to new_uid. In addition, if
- *	new_uid is set to -1, no ownership will be changed.
- *
- *	The same logic applies for the group-ownership and
- *	old_gid/new_gid.
- */
-int copy_tree (const char *src_root, const char *dst_root,
+static int copy_tree_impl (const struct path_info *src, const struct path_info *dst,
                bool copy_root, bool reset_selinux,
                uid_t old_uid, uid_t new_uid,
                gid_t old_gid, gid_t new_gid)
 {
-	int err = 0;
+	int dst_fd, src_fd, err = 0;
 	bool set_orig = false;
-	struct DIRECT *ent;
+	const struct DIRECT *ent;
 	DIR *dir;
 
 	if (copy_root) {
 		struct stat sb;
-		if (access (dst_root, F_OK) == 0) {
+		if (   fstatat (dst->dirfd, dst->name, &sb, 0) == 0
+				|| errno != ENOENT) {
 			return -1;
 		}
 
-		if (LSTAT (src_root, &sb) == -1) {
+		if (fstatat (src->dirfd, src->name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
 			return -1;
 		}
 
 		if (!S_ISDIR (sb.st_mode)) {
 			fprintf (stderr,
 			         "%s: %s is not a directory",
-			         Prog, src_root);
+			         Prog, src->full_path);
 			return -1;
 		}
 
-		return copy_entry (src_root, dst_root, reset_selinux,
+		return copy_entry (src, dst, reset_selinux,
 		                   old_uid, new_uid, old_gid, new_gid);
 	}
 
@@ -264,8 +300,14 @@ int copy_tree (const char *src_root, con
 	 * target is created.  It assumes the target directory exists.
 	 */
 
-	if (   (access (src_root, F_OK) != 0)
-	    || (access (dst_root, F_OK) != 0)) {
+	src_fd = openat (src->dirfd, src->name, O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
+	if (src_fd < 0) {
+		return -1;
+	}
+
+	dst_fd = openat (dst->dirfd, dst->name, O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
+	if (dst_fd < 0) {
+		(void) close (src_fd);
 		return -1;
 	}
 
@@ -276,14 +318,16 @@ int copy_tree (const char *src_root, con
 	 * regular files (and directories ...) are copied, and no file
 	 * is made set-ID.
 	 */
-	dir = opendir (src_root);
+	dir = fdopendir (src_fd);
 	if (NULL == dir) {
+		(void) close (src_fd);
+		(void) close (dst_fd);
 		return -1;
 	}
 
 	if (src_orig == NULL) {
-		src_orig = src_root;
-		dst_orig = dst_root;
+		src_orig = src->full_path;
+		dst_orig = dst->full_path;
 		set_orig = true;
 	}
 	while ((0 == err) && (ent = readdir (dir)) != NULL) {
@@ -296,8 +340,8 @@ int copy_tree (const char *src_root, con
 			char *dst_name;
 			size_t src_len = strlen (ent->d_name) + 2;
 			size_t dst_len = strlen (ent->d_name) + 2;
-			src_len += strlen (src_root);
-			dst_len += strlen (dst_root);
+			src_len += strlen (src->full_path);
+			dst_len += strlen (dst->full_path);
 
 			src_name = (char *) malloc (src_len);
 			dst_name = (char *) malloc (dst_len);
@@ -309,12 +353,21 @@ int copy_tree (const char *src_root, con
 				 * Build the filename for both the source and
 				 * the destination files.
 				 */
+				struct path_info src_entry, dst_entry;
 				(void) snprintf (src_name, src_len, "%s/%s",
-				                 src_root, ent->d_name);
+           						 src->full_path, ent->d_name);
 				(void) snprintf (dst_name, dst_len, "%s/%s",
-				                 dst_root, ent->d_name);
+						         dst->full_path, ent->d_name);
 
-				err = copy_entry (src_name, dst_name,
+				src_entry.full_path = src_name;
+				src_entry.dirfd = dirfd(dir);
+				src_entry.name = ent->d_name;
+
+				dst_entry.full_path = dst_name;
+				dst_entry.dirfd = dst_fd;
+				dst_entry.name = ent->d_name;
+
+				err = copy_entry (&src_entry, &dst_entry,
 				                  reset_selinux,
 				                  old_uid, new_uid,
 				                  old_gid, new_gid);
@@ -328,6 +381,7 @@ int copy_tree (const char *src_root, con
 		}
 	}
 	(void) closedir (dir);
+	(void) close(dst_fd);
 
 	if (set_orig) {
 		src_orig = NULL;
@@ -374,7 +428,7 @@ int copy_tree (const char *src_root, con
  *	old_gid) will be modified, unless old_uid (resp. old_gid) is set
  *	to -1.
  */
-static int copy_entry (const char *src, const char *dst,
+static int copy_entry (const struct path_info *src, const struct path_info *dst,
                        bool reset_selinux,
                        uid_t old_uid, uid_t new_uid,
                        gid_t old_gid, gid_t new_gid)
@@ -382,32 +436,32 @@ static int copy_entry (const char *src,
 	int err = 0;
 	struct stat sb;
 	struct link_name *lp;
-	struct timeval mt[2];
+	struct timespec mt[2];
 
-	if (LSTAT (src, &sb) == -1) {
+	if (fstatat(src->dirfd, src->name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
 		/* If we cannot stat the file, do not care. */
 	} else {
 #ifdef HAVE_STRUCT_STAT_ST_ATIM
 		mt[0].tv_sec  = sb.st_atim.tv_sec;
-		mt[0].tv_usec = sb.st_atim.tv_nsec / 1000;
+		mt[0].tv_nsec = sb.st_atim.tv_nsec;
 #else				/* !HAVE_STRUCT_STAT_ST_ATIM */
 		mt[0].tv_sec  = sb.st_atime;
 # ifdef HAVE_STRUCT_STAT_ST_ATIMENSEC
-		mt[0].tv_usec = sb.st_atimensec / 1000;
+		mt[0].tv_nsec = sb.st_atimensec;
 # else				/* !HAVE_STRUCT_STAT_ST_ATIMENSEC */
-		mt[0].tv_usec = 0;
+		mt[0].tv_nsec = 0;
 # endif				/* !HAVE_STRUCT_STAT_ST_ATIMENSEC */
 #endif				/* !HAVE_STRUCT_STAT_ST_ATIM */
 
 #ifdef HAVE_STRUCT_STAT_ST_MTIM
 		mt[1].tv_sec  = sb.st_mtim.tv_sec;
-		mt[1].tv_usec = sb.st_mtim.tv_nsec / 1000;
+		mt[1].tv_nsec = sb.st_mtim.tv_nsec;
 #else				/* !HAVE_STRUCT_STAT_ST_MTIM */
 		mt[1].tv_sec  = sb.st_mtime;
 # ifdef HAVE_STRUCT_STAT_ST_MTIMENSEC
-		mt[1].tv_usec = sb.st_mtimensec / 1000;
+		mt[1].tv_nsec = sb.st_mtimensec;
 # else				/* !HAVE_STRUCT_STAT_ST_MTIMENSEC */
-		mt[1].tv_usec = 0;
+		mt[1].tv_nsec = 0;
 # endif				/* !HAVE_STRUCT_STAT_ST_MTIMENSEC */
 #endif				/* !HAVE_STRUCT_STAT_ST_MTIM */
 
@@ -416,7 +470,6 @@ static int copy_entry (const char *src,
 			                old_uid, new_uid, old_gid, new_gid);
 		}
 
-#ifdef	S_IFLNK
 		/*
 		 * Copy any symbolic links
 		 */
@@ -425,13 +478,12 @@ static int copy_entry (const char *src,
 			err = copy_symlink (src, dst, reset_selinux, &sb, mt,
 			                    old_uid, new_uid, old_gid, new_gid);
 		}
-#endif				/* S_IFLNK */
 
 		/*
 		 * See if this is a previously copied link
 		 */
 
-		else if ((lp = check_link (src, &sb)) != NULL) {
+		else if ((lp = check_link (src->full_path, &sb)) != NULL) {
 			err = copy_hardlink (dst, reset_selinux, lp);
 		}
 
@@ -470,9 +522,9 @@ static int copy_entry (const char *src,
  *
  *	Return 0 on success, -1 on error.
  */
-static int copy_dir (const char *src, const char *dst,
+static int copy_dir (const struct path_info *src, const struct path_info *dst,
                      bool reset_selinux,
-                     const struct stat *statp, const struct timeval mt[],
+                     const struct stat *statp, const struct timespec mt[],
                      uid_t old_uid, uid_t new_uid,
                      gid_t old_gid, gid_t new_gid)
 {
@@ -484,18 +536,18 @@ static int copy_dir (const char *src, co
 	 */
 
 #ifdef WITH_SELINUX
-	if (set_selinux_file_context (dst) != 0) {
+	if (set_selinux_file_context (dst->full_path) != 0) {
 		return -1;
 	}
 #endif				/* WITH_SELINUX */
-	if (   (mkdir (dst, statp->st_mode) != 0)
-	    || (chown_if_needed (dst, statp,
-	                         old_uid, new_uid, old_gid, new_gid) != 0)
+	if (   (mkdirat (dst->dirfd, dst->name, statp->st_mode) != 0)
+			|| (chownat_if_needed (dst, statp,
+					old_uid, new_uid, old_gid, new_gid) != 0)
 #ifdef WITH_ACL
-	    || (   (perm_copy_file (src, dst, &ctx) != 0)
+	    || (   (perm_copy_path (src, dst, &ctx) != 0)
 	        && (errno != 0))
 #else				/* !WITH_ACL */
-	    || (chmod (dst, statp->st_mode) != 0)
+	    || (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0)
 #endif				/* !WITH_ACL */
 #ifdef WITH_ATTR
 	/*
@@ -506,19 +558,18 @@ static int copy_dir (const char *src, co
 	 * additional logic so that no unexpected permissions result.
 	 */
 	    || (   !reset_selinux
-	        && (attr_copy_file (src, dst, NULL, &ctx) != 0)
+	        && (attr_copy_path (src, dst, NULL, &ctx) != 0)
 	        && (errno != 0))
 #endif				/* WITH_ATTR */
-	    || (copy_tree (src, dst, false, reset_selinux,
+	    || (copy_tree_impl (src, dst, false, reset_selinux,
 	                   old_uid, new_uid, old_gid, new_gid) != 0)
-	    || (utimes (dst, mt) != 0)) {
+		|| (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0)) {
 		err = -1;
 	}
 
 	return err;
 }
 
-#ifdef	S_IFLNK
 /*
  * readlink_malloc - wrapper for readlink
  *
@@ -565,9 +616,9 @@ static /*@null@*/char *readlink_malloc (
  *
  *	Return 0 on success, -1 on error.
  */
-static int copy_symlink (const char *src, const char *dst,
+static int copy_symlink (const struct path_info *src, const struct path_info *dst,
                          unused bool reset_selinux,
-                         const struct stat *statp, const struct timeval mt[],
+                         const struct stat *statp, const struct timespec mt[],
                          uid_t old_uid, uid_t new_uid,
                          gid_t old_gid, gid_t new_gid)
 {
@@ -585,7 +636,7 @@ static int copy_symlink (const char *src
 	 * destination directory name.
 	 */
 
-	oldlink = readlink_malloc (src);
+	oldlink = readlink_malloc (src->full_path);
 	if (NULL == oldlink) {
 		return -1;
 	}
@@ -605,14 +656,14 @@ static int copy_symlink (const char *src
 	}
 
 #ifdef WITH_SELINUX
-	if (set_selinux_file_context (dst) != 0) {
+	if (set_selinux_file_context (dst->full_path) != 0) {
 		free (oldlink);
 		return -1;
 	}
 #endif				/* WITH_SELINUX */
-	if (   (symlink (oldlink, dst) != 0)
-	    || (lchown_if_needed (dst, statp,
-	                          old_uid, new_uid, old_gid, new_gid) != 0)) {
+	if (   (symlinkat (oldlink, dst->dirfd, dst->name) != 0)
+			|| (chownat_if_needed (dst, statp,
+					old_uid, new_uid, old_gid, new_gid) != 0)) {
 		/* FIXME: there are no modes on symlinks, right?
 		 *        ACL could be copied, but this would be much more
 		 *        complex than calling perm_copy_file.
@@ -625,18 +676,12 @@ static int copy_symlink (const char *src
 	}
 	free (oldlink);
 
-#ifdef HAVE_LUTIMES
-	/* 2007-10-18: We don't care about
-	 *  exit status of lutimes because
-	 *  it returns ENOSYS on many system
-	 *  - not implemented
-	 */
-	(void) lutimes (dst, mt);
-#endif				/* HAVE_LUTIMES */
+	if (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0) {
+		return -1;
+	}
 
 	return 0;
 }
-#endif				/* S_IFLNK */
 
 /*
  * copy_hardlink - copy a hardlink
@@ -645,13 +690,13 @@ static int copy_symlink (const char *src
  *
  *	Return 0 on success, -1 on error.
  */
-static int copy_hardlink (const char *dst,
+static int copy_hardlink (const struct path_info *dst,
                           unused bool reset_selinux,
                           struct link_name *lp)
 {
 	/* FIXME: selinux, ACL, Extended Attributes needed? */
 
-	if (link (lp->ln_name, dst) != 0) {
+	if (linkat (AT_FDCWD, lp->ln_name, dst->dirfd, dst->name, 0) != 0) {
 		return -1;
 	}
 
@@ -675,28 +720,28 @@ static int copy_hardlink (const char *ds
  *
  *	Return 0 on success, -1 on error.
  */
-static int copy_special (const char *src, const char *dst,
+static int copy_special (const struct path_info *src, const struct path_info *dst,
                          bool reset_selinux,
-                         const struct stat *statp, const struct timeval mt[],
+                         const struct stat *statp, const struct timespec mt[],
                          uid_t old_uid, uid_t new_uid,
                          gid_t old_gid, gid_t new_gid)
 {
 	int err = 0;
 
 #ifdef WITH_SELINUX
-	if (set_selinux_file_context (dst) != 0) {
+	if (set_selinux_file_context (dst->full_path) != 0) {
 		return -1;
 	}
 #endif				/* WITH_SELINUX */
 
-	if (   (mknod (dst, statp->st_mode & ~07777, statp->st_rdev) != 0)
-	    || (chown_if_needed (dst, statp,
+	if (   (mknodat (dst->dirfd, dst->name, statp->st_mode & ~07777U, statp->st_rdev) != 0)
+	    || (chownat_if_needed (dst, statp,
 	                         old_uid, new_uid, old_gid, new_gid) != 0)
 #ifdef WITH_ACL
-	    || (   (perm_copy_file (src, dst, &ctx) != 0)
+	    || (   (perm_copy_path (src, dst, &ctx) != 0)
 	        && (errno != 0))
 #else				/* !WITH_ACL */
-	    || (chmod (dst, statp->st_mode & 07777) != 0)
+		|| (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0)
 #endif				/* !WITH_ACL */
 #ifdef WITH_ATTR
 	/*
@@ -707,10 +752,10 @@ static int copy_special (const char *src
 	 * additional logic so that no unexpected permissions result.
 	 */
 	    || (   !reset_selinux
-	        && (attr_copy_file (src, dst, NULL, &ctx) != 0)
+	        && (attr_copy_path (src, dst, NULL, &ctx) != 0)
 	        && (errno != 0))
 #endif				/* WITH_ATTR */
-	    || (utimes (dst, mt) != 0)) {
+		|| (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0)) {
 		err = -1;
 	}
 
@@ -718,6 +763,42 @@ static int copy_special (const char *src
 }
 
 /*
+ * full_write - write entire buffer
+ *
+ * Write up to count bytes from the buffer starting at buf to the
+ * file referred to by the file descriptor fd.
+ * Retry in case of a short write.
+ *
+ * Returns the number of bytes written on success, -1 on error.
+ */
+static ssize_t full_write(int fd, const void *buf, size_t count) {
+	ssize_t written = 0;
+
+	while (count > 0) {
+		ssize_t res;
+
+		res = write(fd, buf, count);
+		if (res < 0) {
+			if (errno == EINTR) {
+				continue;
+			}
+
+			return res;
+		}
+
+		if (res == 0) {
+			break;
+		}
+
+		written += res;
+		buf = (const unsigned char*)buf + res;
+		count -= (size_t)res;
+	}
+
+	return written;
+}
+
+/*
  * copy_file - copy a file
  *
  *	Copy a file from src to dst.
@@ -727,33 +808,33 @@ static int copy_special (const char *src
  *
  *	Return 0 on success, -1 on error.
  */
-static int copy_file (const char *src, const char *dst,
+static int copy_file (const struct path_info *src, const struct path_info *dst,
                       bool reset_selinux,
-                      const struct stat *statp, const struct timeval mt[],
+                      const struct stat *statp, const struct timespec mt[],
                       uid_t old_uid, uid_t new_uid,
                       gid_t old_gid, gid_t new_gid)
 {
 	int err = 0;
 	int ifd;
 	int ofd;
-	char buf[1024];
-	ssize_t cnt;
 
-	ifd = open (src, O_RDONLY);
+	ifd = openat (src->dirfd, src->name, O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
 	if (ifd < 0) {
 		return -1;
 	}
 #ifdef WITH_SELINUX
-	if (set_selinux_file_context (dst) != 0) {
+	if (set_selinux_file_context (dst->full_path) != 0) {
+		(void) close (ifd);
 		return -1;
 	}
 #endif				/* WITH_SELINUX */
-	ofd = open (dst, O_WRONLY | O_CREAT | O_TRUNC, statp->st_mode & 07777);
+	ofd = openat (dst->dirfd, dst->name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, statp->st_mode & 07777);
+
 	if (   (ofd < 0)
 	    || (fchown_if_needed (ofd, statp,
 	                          old_uid, new_uid, old_gid, new_gid) != 0)
 #ifdef WITH_ACL
-	    || (   (perm_copy_fd (src, ifd, dst, ofd, &ctx) != 0)
+		|| (   (perm_copy_fd (src->full_path, ifd, dst->full_path, ofd, &ctx) != 0)
 	        && (errno != 0))
 #else				/* !WITH_ACL */
 	    || (fchmod (ofd, statp->st_mode & 07777) != 0)
@@ -767,38 +848,49 @@ static int copy_file (const char *src, c
 	 * additional logic so that no unexpected permissions result.
 	 */
 	    || (   !reset_selinux
-	        && (attr_copy_fd (src, ifd, dst, ofd, NULL, &ctx) != 0)
+			&& (attr_copy_fd (src->full_path, ifd, dst->full_path, ofd, NULL, &ctx) != 0)
 	        && (errno != 0))
 #endif				/* WITH_ATTR */
 	   ) {
+		if (ofd >= 0) {
+			(void) close (ofd);
+		}
 		(void) close (ifd);
 		return -1;
 	}
 
-	while ((cnt = read (ifd, buf, sizeof buf)) > 0) {
-		if (write (ofd, buf, (size_t)cnt) != cnt) {
+	while (true) {
+		char buf[8192];
+		ssize_t cnt;
+
+		cnt = read (ifd, buf, sizeof buf);
+		if (cnt < 0) {
+			if (errno == EINTR) {
+				continue;
+			}
+			(void) close (ofd);
 			(void) close (ifd);
 			return -1;
 		}
+		if (cnt == 0) {
+			break;
 	}
 
+		if (full_write (ofd, buf, (size_t)cnt) < 0) {
+			(void) close (ofd);
 	(void) close (ifd);
-
-#ifdef HAVE_FUTIMES
-	if (futimes (ofd, mt) != 0) {
-		return -1;
+			return -1;
+		}
 	}
-#endif				/* HAVE_FUTIMES */
 
+	(void) close (ifd);
 	if (close (ofd) != 0) {
 		return -1;
 	}
 
-#ifndef HAVE_FUTIMES
-	if (utimes(dst, mt) != 0) {
+	if (utimensat (dst->dirfd, dst->name, mt, AT_SYMLINK_NOFOLLOW) != 0) {
 		return -1;
 	}
-#endif				/* !HAVE_FUTIMES */
 
 	return err;
 }
@@ -833,7 +925,71 @@ static int chown_function ## _if_needed
 	return chown_function (dst, tmpuid, tmpgid);                   \
 }
 
-def_chown_if_needed (chown, const char *)
-def_chown_if_needed (lchown, const char *)
 def_chown_if_needed (fchown, int)
 
+static int chownat_if_needed (const struct path_info *dst,
+							  const struct stat *statp,
+                              uid_t old_uid, uid_t new_uid,
+                              gid_t old_gid, gid_t new_gid)
+{
+	uid_t tmpuid = (uid_t) -1;
+	gid_t tmpgid = (gid_t) -1;
+
+	/* Use new_uid if old_uid is set to -1 or if the file was
+	 * owned by the user. */
+	if (((uid_t) -1 == old_uid) || (statp->st_uid == old_uid)) {
+		tmpuid = new_uid;
+	}
+	/* Otherwise, or if new_uid was set to -1, we keep the same
+	 * owner. */
+	if ((uid_t) -1 == tmpuid) {
+		tmpuid = statp->st_uid;
+	}
+
+	if (((gid_t) -1 == old_gid) || (statp->st_gid == old_gid)) {
+		tmpgid = new_gid;
+	}
+	if ((gid_t) -1 == tmpgid) {
+		tmpgid = statp->st_gid;
+	}
+
+	return fchownat (dst->dirfd, dst->name, tmpuid, tmpgid, AT_SYMLINK_NOFOLLOW);
+}
+
+/*
+ * copy_tree - copy files in a directory tree
+ *
+ *	copy_tree() walks a directory tree and copies ordinary files
+ *	as it goes.
+ *
+ *	When reset_selinux is enabled, extended attributes (and thus
+ *	SELinux attributes) are not copied.
+ *
+ *	old_uid and new_uid are used to set the ownership of the copied
+ *	files. Unless old_uid is set to -1, only the files owned by
+ *	old_uid have their ownership changed to new_uid. In addition, if
+ *	new_uid is set to -1, no ownership will be changed.
+ *
+ *	The same logic applies for the group-ownership and
+ *	old_gid/new_gid.
+ */
+int copy_tree (const char *src_root, const char *dst_root,
+               bool copy_root, bool reset_selinux,
+               uid_t old_uid, uid_t new_uid,
+               gid_t old_gid, gid_t new_gid)
+{
+	const struct path_info src = {
+		.full_path = src_root,
+		.dirfd = AT_FDCWD,
+		.name = src_root
+	};
+	const struct path_info dst = {
+		.full_path = dst_root,
+		.dirfd = AT_FDCWD,
+		.name = dst_root
+	};
+
+	return copy_tree_impl(&src, &dst, copy_root, reset_selinux,
+						  old_uid, new_uid, old_gid, new_gid);
+}
+
Index: shadow-4.2.1/lib/commonio.c
===================================================================
--- shadow-4.2.1.orig/lib/commonio.c
+++ shadow-4.2.1/lib/commonio.c
@@ -86,7 +86,6 @@ int lrename (const char *old, const char
 	int res;
 	char *r = NULL;
 
-#if defined(S_ISLNK)
 #ifndef __GLIBC__
 	char resolved_path[PATH_MAX];
 #endif				/* !__GLIBC__ */
@@ -103,7 +102,6 @@ int lrename (const char *old, const char
 			new = r;
 		}
 	}
-#endif				/* S_ISLNK */
 
 	res = rename (old, new);
 
Index: shadow-4.2.1/lib/defines.h
===================================================================
--- shadow-4.2.1.orig/lib/defines.h
+++ shadow-4.2.1/lib/defines.h
@@ -238,22 +238,6 @@ char *strchr (), *strrchr (), *strtok ()
 # endif
 #endif
 
-#ifndef S_ISLNK
-#define S_ISLNK(x) (0)
-#endif
-
-#if HAVE_LCHOWN
-#define LCHOWN lchown
-#else
-#define LCHOWN chown
-#endif
-
-#if HAVE_LSTAT
-#define LSTAT lstat
-#else
-#define LSTAT stat
-#endif
-
 #if HAVE_TERMIOS_H
 # include <termios.h>
 # define STTY(fd, termio) tcsetattr(fd, TCSANOW, termio)
Index: shadow-4.2.1/libmisc/chowndir.c
===================================================================
--- shadow-4.2.1.orig/libmisc/chowndir.c
+++ shadow-4.2.1/libmisc/chowndir.c
@@ -40,45 +40,28 @@
 #include "defines.h"
 #include <fcntl.h>
 #include <stdio.h>
-/*
- * chown_tree - change ownership of files in a directory tree
- *
- *	chown_dir() walks a directory tree and changes the ownership
- *	of all files owned by the provided user ID.
- *
- *	Only files owned (resp. group-owned) by old_uid (resp. by old_gid)
- *	will have their ownership (resp. group-ownership) modified, unless
- *	old_uid (resp. old_gid) is set to -1.
- *
- *	new_uid and new_gid can be set to -1 to indicate that no owner or
- *	group-owner shall be changed.
- */
-int chown_tree (const char *root,
+#include <unistd.h>
+
+static int chown_tree_at (int at_fd,
+                const char *path,
                 uid_t old_uid,
                 uid_t new_uid,
                 gid_t old_gid,
                 gid_t new_gid)
 {
-	char *new_name;
-	size_t new_name_len;
-	int rc = 0;
-	struct DIRECT *ent;
-	struct stat sb;
 	DIR *dir;
+	const struct dirent *ent;
+	struct stat dir_sb;
+	int dir_fd, rc = 0;
 
-	new_name = malloc (1024);
-	if (NULL == new_name) {
+	dir_fd = openat (at_fd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+	if (dir_fd < 0) {
 		return -1;
 	}
-	new_name_len = 1024;
 
-	/*
-	 * Make certain the directory exists.  This routine is called
-	 * directly by the invoker, or recursively.
-	 */
-
-	if (access (root, F_OK) != 0) {
-		free (new_name);
+	dir = fdopendir (dir_fd);
+	if (!dir) {
+		(void) close (dir_fd);
 		return -1;
 	}
 
@@ -89,16 +72,10 @@ int chown_tree (const char *root,
 	 * shall be changed.
 	 */
 
-	dir = opendir (root);
-	if (NULL == dir) {
-		free (new_name);
-		return -1;
-	}
-
 	while ((ent = readdir (dir))) {
-		size_t ent_name_len;
 		uid_t tmpuid = (uid_t) -1;
 		gid_t tmpgid = (gid_t) -1;
+		struct stat ent_sb;
 
 		/*
 		 * Skip the "." and ".." entries
@@ -109,47 +86,21 @@ int chown_tree (const char *root,
 			continue;
 		}
 
-		/*
-		 * Make the filename for both the source and the
-		 * destination files.
-		 */
-
-		ent_name_len = strlen (root) + strlen (ent->d_name) + 2;
-		if (ent_name_len > new_name_len) {
-			/*@only@*/char *tmp = realloc (new_name, ent_name_len);
-			if (NULL == tmp) {
-				rc = -1;
-				break;
-			}
-			new_name = tmp;
-			new_name_len = ent_name_len;
-		}
-
-		(void) snprintf (new_name, new_name_len, "%s/%s", root, ent->d_name);
-
-		/* Don't follow symbolic links! */
-		if (LSTAT (new_name, &sb) == -1) {
-			continue;
+		rc = fstatat (dirfd(dir), ent->d_name, &ent_sb, AT_SYMLINK_NOFOLLOW);
+		if (rc < 0) {
+			break;
 		}
 
-		if (S_ISDIR (sb.st_mode) && !S_ISLNK (sb.st_mode)) {
-
+		if (S_ISDIR (ent_sb.st_mode)) {
 			/*
 			 * Do the entire subdirectory.
 			 */
+			rc = chown_tree_at (dirfd(dir), ent->d_name, old_uid, new_uid, old_gid, new_gid);
 
-			rc = chown_tree (new_name, old_uid, new_uid,
-			                 old_gid, new_gid);
 			if (0 != rc) {
 				break;
 			}
 		}
-#ifndef HAVE_LCHOWN
-		/* don't use chown (follows symbolic links!) */
-		if (S_ISLNK (sb.st_mode)) {
-			continue;
-		}
-#endif
 		/*
 		 * By default, the IDs are not changed (-1).
 		 *
@@ -159,43 +110,64 @@ int chown_tree (const char *root,
 		 * If the file is not group-owned by the group, the
 		 * group-owner is not changed.
 		 */
-		if (((uid_t) -1 == old_uid) || (sb.st_uid == old_uid)) {
+		if (((uid_t) -1 == old_uid) || (ent_sb.st_uid == old_uid)) {
 			tmpuid = new_uid;
 		}
-		if (((gid_t) -1 == old_gid) || (sb.st_gid == old_gid)) {
+		if (((gid_t) -1 == old_gid) || (ent_sb.st_gid == old_gid)) {
 			tmpgid = new_gid;
 		}
 		if (((uid_t) -1 != tmpuid) || ((gid_t) -1 != tmpgid)) {
-			rc = LCHOWN (new_name, tmpuid, tmpgid);
+			rc = fchownat (dirfd(dir), ent->d_name, tmpuid, tmpgid, AT_SYMLINK_NOFOLLOW);
 			if (0 != rc) {
 				break;
 			}
 		}
 	}
 
-	free (new_name);
-	(void) closedir (dir);
-
 	/*
 	 * Now do the root of the tree
 	 */
 
-	if ((0 == rc) && (stat (root, &sb) == 0)) {
+	if ((0 == rc) && (fstat (dirfd(dir), &dir_sb) == 0)) {
 		uid_t tmpuid = (uid_t) -1;
 		gid_t tmpgid = (gid_t) -1;
-		if (((uid_t) -1 == old_uid) || (sb.st_uid == old_uid)) {
+		if (((uid_t) -1 == old_uid) || (dir_sb.st_uid == old_uid)) {
 			tmpuid = new_uid;
 		}
-		if (((gid_t) -1 == old_gid) || (sb.st_gid == old_gid)) {
+		if (((gid_t) -1 == old_gid) || (dir_sb.st_gid == old_gid)) {
 			tmpgid = new_gid;
 		}
 		if (((uid_t) -1 != tmpuid) || ((gid_t) -1 != tmpgid)) {
-			rc = LCHOWN (root, tmpuid, tmpgid);
+			rc = fchown (dirfd(dir), tmpuid, tmpgid);
 		}
 	} else {
 		rc = -1;
 	}
 
+	(void) closedir (dir);
+
 	return rc;
 }
 
+/*
+ * chown_tree - change ownership of files in a directory tree
+ *
+ *	chown_dir() walks a directory tree and changes the ownership
+ *	of all files owned by the provided user ID.
+ *
+ *	Only files owned (resp. group-owned) by old_uid (resp. by old_gid)
+ *	will have their ownership (resp. group-ownership) modified, unless
+ *	old_uid (resp. old_gid) is set to -1.
+ *
+ *	new_uid and new_gid can be set to -1 to indicate that no owner or
+ *	group-owner shall be changed.
+ */
+int chown_tree (const char *root,
+                uid_t old_uid,
+                uid_t new_uid,
+                gid_t old_gid,
+                gid_t new_gid)
+{
+	return chown_tree_at (AT_FDCWD, root, old_uid, new_uid, old_gid, new_gid);
+}
+
Index: shadow-4.2.1/libmisc/remove_tree.c
===================================================================
--- shadow-4.2.1.orig/libmisc/remove_tree.c
+++ shadow-4.2.1/libmisc/remove_tree.c
@@ -34,6 +34,7 @@
 
 #ident "$Id$"
 
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -44,36 +45,29 @@
 #include "prototypes.h"
 #include "defines.h"
 
-/*
- * remove_tree - delete a directory tree
- *
- *	remove_tree() walks a directory tree and deletes all the files
- *	and directories.
- *	At the end, it deletes the root directory itself.
- */
-
-int remove_tree (const char *root, bool remove_root)
+static int remove_tree_at (int at_fd, const char *path, bool remove_root)
 {
-	char *new_name = NULL;
-	int err = 0;
-	struct DIRECT *ent;
-	struct stat sb;
 	DIR *dir;
 
-	/*
-	 * Open the source directory and read each entry.  Every file
-	 * entry in the directory is copied with the UID and GID set
-	 * to the provided values.  As an added security feature only
-	 * regular files (and directories ...) are copied, and no file
-	 * is made set-ID.
-	 */
-	dir = opendir (root);
-	if (NULL == dir) {
+	const struct dirent *ent;
+	int dir_fd, rc = 0;
+
+	dir_fd = openat (at_fd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+	if (dir_fd < 0) {
 		return -1;
 	}
 
+	dir = fdopendir (dir_fd);
+	if (!dir) {
+		(void) close (dir_fd);
+		return -1;
+	}
+
+	/*
+	 * Open the source directory and delete each entry.
+	 */
 	while ((ent = readdir (dir))) {
-		size_t new_len = strlen (root) + strlen (ent->d_name) + 2;
+		struct stat ent_sb;
 
 		/*
 		 * Skip the "." and ".." entries
@@ -84,50 +78,49 @@ int remove_tree (const char *root, bool
 			continue;
 		}
 
-		/*
-		 * Make the filename for the current entry.
-		 */
 
-		free (new_name);
-		new_name = (char *) malloc (new_len);
-		if (NULL == new_name) {
-			err = -1;
+		rc = fstatat (dirfd(dir), ent->d_name, &ent_sb, AT_SYMLINK_NOFOLLOW);
+		if (rc < 0) {
 			break;
 		}
-		(void) snprintf (new_name, new_len, "%s/%s", root, ent->d_name);
-		if (LSTAT (new_name, &sb) == -1) {
-			continue;
-		}
 
-		if (S_ISDIR (sb.st_mode)) {
+		if (S_ISDIR (ent_sb.st_mode)) {
 			/*
 			 * Recursively delete this directory.
 			 */
-			if (remove_tree (new_name, true) != 0) {
-				err = -1;
+			if (remove_tree_at (dirfd(dir), ent->d_name, true) != 0) {
+				rc = -1;
 				break;
 			}
 		} else {
 			/*
 			 * Delete the file.
 			 */
-			if (unlink (new_name) != 0) {
-				err = -1;
+			if (unlinkat (dirfd(dir), ent->d_name, 0) != 0) {
+				rc = -1;
 				break;
 			}
 		}
 	}
-	if (NULL != new_name) {
-		free (new_name);
-	}
 	(void) closedir (dir);
 
-	if (remove_root && (0 == err)) {
-		if (rmdir (root) != 0) {
-			err = -1;
+	if (remove_root && (0 == rc)) {
+		if (unlinkat (at_fd, path, AT_REMOVEDIR) != 0) {
+			rc = -1;
 		}
 	}
 
-	return err;
+	return rc;
 }
 
+/*
+ * remove_tree - delete a directory tree
+ *
+ *	remove_tree() walks a directory tree and deletes all the files
+ *	and directories.
+ *	At the end, it deletes the root directory itself.
+ */
+int remove_tree (const char *root, bool remove_root)
+{
+	return remove_tree_at (AT_FDCWD, root, remove_root);
+}
