File shadow-CVE-2013-4235.patch of Package shadow.35063
Fix for CVE-2013-4235.
Backport of:
* faeab50e710131816b261de66141524898c2c487 (main fix)
* dab764d0195fc16d1d39330eee8a33e8917826d8
* e666de721aedf6deae8b11bef2e0701cf110f307
* 83d42e9e884829be028b3d2b276dc35bfc8c30cf
* 479fc86fbe4add5ae0c66571965627c8fbac881d
* 3db58ddf6394dfd1a0fe81dcb94dc81fe9fe6d6a (partly)
* f3bdb28e57e5e38c1e89347976c7d61a181eec32 (regression fix)
* 10cd68e0f04b48363eb32d2c6e168b358fb27810 (regression fix)
* 74c17c71675a60edaa47b280b0b4e2190ddcaee0
which is our upstreamed useradd-userkeleton.patch
adapted to the new code.
* 46d305834115b8d43a73338d903a659be8c1ae25
And a fixup commit for our userskel patch.
It is realted to bsc#1228770
Index: shadow-4.8.1/libmisc/copydir.c
===================================================================
--- shadow-4.8.1.orig/libmisc/copydir.c
+++ shadow-4.8.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,22 @@ 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);
+
+ src_entry.full_path = src_name;
+ src_entry.dirfd = dirfd(dir);
+ src_entry.name = ent->d_name;
- err = copy_entry (src_name, dst_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 +382,7 @@ int copy_tree (const char *src_root, con
}
}
(void) closedir (dir);
+ (void) close (dst_fd);
if (set_orig) {
src_orig = NULL;
@@ -374,7 +429,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 +437,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 */
@@ -420,11 +475,10 @@ static int copy_entry (const char *src,
* If the destination already exists do nothing.
* This is after the copy_dir above to still iterate into subdirectories.
*/
- if (LSTAT (dst, &sb) != -1) {
+ if (fstatat(dst->dirfd, dst->name, &sb, AT_SYMLINK_NOFOLLOW) != -1) {
return 0;
}
-#ifdef S_IFLNK
/*
* Copy any symbolic links
*/
@@ -433,13 +487,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);
}
@@ -478,9 +531,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)
{
@@ -493,7 +546,7 @@ 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 */
@@ -502,19 +555,20 @@ static int copy_dir (const char *src, co
* If the destination is already a directory, don't change it
* but copy into it (recursively).
*/
- if (LSTAT (dst, &dst_sb) == 0 && S_ISDIR(dst_sb.st_mode)) {
- return (copy_tree (src, dst, false, reset_selinux,
- old_uid, new_uid, old_gid, new_gid) != 0);
+ if (fstatat(dst->dirfd, dst->name, &dst_sb, AT_SYMLINK_NOFOLLOW) == 0 && S_ISDIR(dst_sb.st_mode)) {
+ return (copy_tree_impl (src, dst, false, reset_selinux,
+ old_uid, new_uid, old_gid, new_gid) != 0);
}
- if ( (mkdir (dst, statp->st_mode) != 0)
- || (chown_if_needed (dst, statp,
+ 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
/*
@@ -525,19 +579,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
*
@@ -584,9 +637,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)
{
@@ -604,7 +657,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;
}
@@ -624,14 +677,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.
@@ -644,18 +697,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
@@ -664,13 +711,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;
}
@@ -694,28 +741,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
/*
@@ -726,10 +773,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;
}
@@ -737,6 +784,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.
@@ -746,33 +829,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)
@@ -786,38 +869,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;
}
@@ -852,7 +946,70 @@ 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.8.1/configure.ac
===================================================================
--- shadow-4.8.1.orig/configure.ac
+++ shadow-4.8.1/configure.ac
@@ -42,7 +42,7 @@ dnl shadow now uses the libc's shadow im
AC_CHECK_HEADER([shadow.h],,[AC_MSG_ERROR([You need a libc with shadow.h])])
AC_CHECK_FUNCS(l64a fchmod fchown fsync futimes getgroups gethostname getspnam \
- gettimeofday getusershell getutent initgroups lchown lckpwdf lstat \
+ gettimeofday getusershell getutent initgroups lckpwdf \
lutimes memcpy memset setgroups sigaction strchr updwtmp updwtmpx innetgr \
getpwnam_r getpwuid_r getgrnam_r getgrgid_r getspnam_r getaddrinfo \
ruserok)
Index: shadow-4.8.1/lib/commonio.c
===================================================================
--- shadow-4.8.1.orig/lib/commonio.c
+++ shadow-4.8.1/lib/commonio.c
@@ -85,7 +85,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__ */
@@ -102,7 +101,6 @@ int lrename (const char *old, const char
new = r;
}
}
-#endif /* S_ISLNK */
res = rename (old, new);
Index: shadow-4.8.1/lib/defines.h
===================================================================
--- shadow-4.8.1.orig/lib/defines.h
+++ shadow-4.8.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.8.1/libmisc/chowndir.c
===================================================================
--- shadow-4.8.1.orig/libmisc/chowndir.c
+++ shadow-4.8.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,24 @@ 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 (new_name, old_uid, new_uid,
- old_gid, new_gid);
+ rc = chown_tree_at (dirfd(dir), ent->d_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 +113,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.8.1/libmisc/remove_tree.c
===================================================================
--- shadow-4.8.1.orig/libmisc/remove_tree.c
+++ shadow-4.8.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;
+ const struct dirent *ent;
+ int dir_fd, rc = 0;
- /*
- * 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) {
+
+ 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,48 @@ 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);
+}