From 7bba45c0804fe11ec4643203240320e1b258f04a Mon Sep 17 00:00:00 2001 From: zhangshaoning Date: Fri, 24 Mar 2023 14:20:28 +0800 Subject: [PATCH] backport CVE-2013-4235 --- backport-CVE-2013-4235.patch | 1181 ++++++++++++++++++++++++++++++++++ shadow.spec | 6 +- 2 files changed, 1186 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2013-4235.patch diff --git a/backport-CVE-2013-4235.patch b/backport-CVE-2013-4235.patch new file mode 100644 index 0000000..63433e1 --- /dev/null +++ b/backport-CVE-2013-4235.patch @@ -0,0 +1,1181 @@ +From e382ca85a75d0d033b9c113ca355d4824783c81d Mon Sep 17 00:00:00 2001 +From: zhangxingrong +Date: Thu, 23 Mar 2023 20:21:15 +0800 +Subject: [PATCH] shadow-CVE-2013-4235 + +--- + configure.ac | 2 +- + lib/commonio.c | 2 - + lib/defines.h | 16 -- + libmisc/chowndir.c | 136 ++++++-------- + libmisc/copydir.c | 416 ++++++++++++++++++++++++++++-------------- + libmisc/remove_tree.c | 90 +++++---- + 6 files changed, 376 insertions(+), 286 deletions(-) + +diff --git a/configure.ac b/configure.ac +index e51417e..3e71737 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -42,7 +42,7 @@ dnl shadow now uses the libc's shadow implementation + 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) +diff --git a/lib/commonio.c b/lib/commonio.c +index 5ebd383..eac1b24 100644 +--- a/lib/commonio.c ++++ b/lib/commonio.c +@@ -89,7 +89,6 @@ int lrename (const char *old, const char *new) + int res; + char *r = NULL; + +-#if defined(S_ISLNK) + #ifndef __GLIBC__ + char resolved_path[PATH_MAX]; + #endif /* !__GLIBC__ */ +@@ -106,7 +105,6 @@ int lrename (const char *old, const char *new) + new = r; + } + } +-#endif /* S_ISLNK */ + + res = rename (old, new); + +diff --git a/lib/defines.h b/lib/defines.h +index 72fa52f..efeee72 100644 +--- a/lib/defines.h ++++ b/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 + # define STTY(fd, termio) tcsetattr(fd, TCSANOW, termio) +diff --git a/libmisc/chowndir.c b/libmisc/chowndir.c +index c4c504a..f5f872c 100644 +--- a/libmisc/chowndir.c ++++ b/libmisc/chowndir.c +@@ -40,45 +40,29 @@ + #include "defines.h" + #include + #include +-/* +- * 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 ++ ++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; +- +- new_name = malloc (1024); +- if (NULL == new_name) { ++ const struct dirent *ent; ++ struct stat dir_sb; ++ 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; + } +- 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; + } + +@@ -88,17 +72,10 @@ int chown_tree (const char *root, + * recursively. If not, it is checked to see if an ownership + * 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 +@@ -108,48 +85,22 @@ int chown_tree (const char *root, + || (strcmp (ent->d_name, "..") == 0)) { + 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; ++ ++ rc = fstatat (dirfd(dir), ent->d_name, &ent_sb, AT_SYMLINK_NOFOLLOW); ++ if (rc < 0) { ++ break; + } + +- (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; +- } +- +- 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 +110,62 @@ 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); ++} +diff --git a/libmisc/copydir.c b/libmisc/copydir.c +index e6aac6e..d62bf9b 100644 +--- a/libmisc/copydir.c ++++ b/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, +- bool reset_selinux, +- const struct stat *statp, const struct timeval mt[], +- uid_t old_uid, uid_t new_uid, ++static int copy_dir (const struct path_info *src, const struct path_info *dst, ++ bool reset_selinux, ++ 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, +- unused bool reset_selinux, +- const struct stat *statp, const struct timeval mt[], +- uid_t old_uid, uid_t new_uid, ++static int copy_symlink (const struct path_info *src, const struct path_info *dst, ++ unused bool reset_selinux, ++ 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, +- unused bool reset_selinux, +- struct link_name *lp); +-static int copy_special (const char *src, const char *dst, +- bool reset_selinux, +- const struct stat *statp, const struct timeval mt[], ++static int copy_hardlink (const struct path_info *dst, ++ unused bool reset_selinux, ++ struct link_name *lp); ++static int copy_special (const struct path_info *src, const struct path_info *dst, ++ bool reset_selinux, ++ 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); +@@ -134,10 +135,61 @@ static void error_acl (struct error_context *ctx, const char *fmt, ...) + } + + static struct error_context ctx = { +- error_acl ++ error_acl, NULL, NULL + }; + #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_CLOEXEC); ++ if (src_fd < 0) { ++ return -1; ++ } ++ ++ dst_fd = openat(dst->dirfd, dst->name, O_RDONLY | O_NOFOLLOW | 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_CLOEXEC); ++ if (src_fd < 0) { ++ return -1; ++ } ++ ++ dst_fd = openat(dst->dirfd, dst->name, O_RDONLY | O_NOFOLLOW | 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,34 @@ static /*@exposed@*/ /*@null@*/struct link_name *check_link (const char *name, c + 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 dirent *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); + } + +@@ -263,9 +298,15 @@ int copy_tree (const char *src_root, const char *dst_root, + * after the home directory is created, or recursively after the + * target is created. It assumes the target directory exists. + */ ++ ++ src_fd = openat (src->dirfd, src->name, O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_CLOEXEC); ++ if (src_fd < 0) { ++ return -1; ++ } + +- if ( (access (src_root, F_OK) != 0) +- || (access (dst_root, F_OK) != 0)) { ++ 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 +317,16 @@ int copy_tree (const char *src_root, const char *dst_root, + * regular files (and directories ...) are copied, and no file + * is made set-ID. + */ +- dir = opendir (src_root); ++ dir = opendir (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 +339,8 @@ int copy_tree (const char *src_root, const char *dst_root, + 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 +352,22 @@ int copy_tree (const char *src_root, const char *dst_root, + * 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; + +- err = copy_entry (src_name, dst_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,7 +381,8 @@ int copy_tree (const char *src_root, const char *dst_root, + } + } + (void) closedir (dir); +- ++ (void) close (dst_fd); ++ + if (set_orig) { + src_orig = NULL; + dst_orig = NULL; +@@ -374,7 +428,7 @@ int copy_tree (const char *src_root, const char *dst_root, + * 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, const char *dst, + 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, const char *dst, + 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, const char *dst, + 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,10 +522,10 @@ static int copy_entry (const char *src, const char *dst, + * + * 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[], +- uid_t old_uid, uid_t new_uid, ++ 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; +@@ -484,15 +536,15 @@ static int copy_dir (const char *src, const char *dst, + */ + + #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, ++ 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) +@@ -506,19 +558,19 @@ static int copy_dir (const char *src, const char *dst, + * 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 +617,9 @@ static /*@null@*/char *readlink_malloc (const char *filename) + * + * 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) + { +@@ -584,8 +636,7 @@ static int copy_symlink (const char *src, const char *dst, + * name will be replaced with the original + * destination directory name. + */ +- +- oldlink = readlink_malloc (src); ++ oldlink = readlink_malloc (src->full_path); + if (NULL == oldlink) { + return -1; + } +@@ -605,13 +656,13 @@ static int copy_symlink (const char *src, const char *dst, + } + + #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, ++ 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 +@@ -625,18 +676,12 @@ static int copy_symlink (const char *src, const char *dst, + } + 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, const char *dst, + * + * 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 (link (lp->ln_name, dst) != 0) { ++ if (linkat (AT_FDCWD, lp->ln_name, dst->dirfd, dst->name, 0) != 0) { + return -1; + } + +@@ -675,28 +720,27 @@ static int copy_hardlink (const char *dst, + * + * 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,16 +751,52 @@ static int copy_special (const char *src, const char *dst, + * 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; + } + + return err; + } + ++/* ++ * 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 + * +@@ -727,33 +807,31 @@ static int copy_special (const char *src, const char *dst, + * + * 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) { + 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 +845,44 @@ static int copy_file (const char *src, const char *dst, + * 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 */ + ) { + (void) close (ifd); + return -1; + } ++ while (true) { ++ char buf[8192]; ++ ssize_t cnt; + +- while ((cnt = read (ifd, buf, sizeof buf)) > 0) { +- if (write (ofd, buf, (size_t)cnt) != cnt) { ++ cnt = read (ifd, buf, sizeof buf); ++ if (cnt < 0) { ++ if (errno == EINTR) { ++ continue; ++ } + (void) close (ifd); + return -1; + } +- } +- +- (void) close (ifd); + +-#ifdef HAVE_FUTIMES +- if (futimes (ofd, mt) != 0) { +- return -1; ++ if (cnt == 0) { ++ break; ++ } ++ if (full_write (ofd, buf, (size_t)cnt) < 0) { ++ (void) close (ofd); ++ (void) close (ifd); ++ 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 +917,69 @@ static int chown_function ## _if_needed (type_dst dst, \ + 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); ++} +diff --git a/libmisc/remove_tree.c b/libmisc/remove_tree.c +index b2794ab..4781ede 100644 +--- a/libmisc/remove_tree.c ++++ b/libmisc/remove_tree.c +@@ -34,6 +34,7 @@ + + #ident "$Id$" + ++#include + #include + #include + #include +@@ -44,37 +45,28 @@ + #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; + } + +- while ((ent = readdir (dir))) { +- size_t new_len = strlen (root) + strlen (ent->d_name) + 2; ++ dir = fdopendir (dir_fd); ++ if (!dir) { ++ (void) close (dir_fd); ++ return -1; ++ } + ++ /* ++ * Open the source directory and delete each entry. ++ */ ++ while ((ent = readdir (dir))) { ++ struct stat ent_sb; + /* + * Skip the "." and ".." entries + */ +@@ -84,50 +76,50 @@ int remove_tree (const char *root, bool remove_root) + 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); + } + +-- +2.27.0 + diff --git a/shadow.spec b/shadow.spec index 82056c8..9bf1597 100644 --- a/shadow.spec +++ b/shadow.spec @@ -1,6 +1,6 @@ Name: shadow Version: 4.8.1 -Release: 5 +Release: 6 Epoch: 2 License: BSD and GPLv2+ Summary: Tools for managing accounts and shadow password files @@ -24,6 +24,7 @@ Patch7: shadow-4.1.5.1-var-lock.patch Patch8: shadow-utils-fix-lock-file-residue.patch Patch9: shadow-add-sm3-crypt-support.patch Patch10: groupdel-fix-SIGSEGV-when-passwd-does-not-exist.patch +Patch11: backport-CVE-2013-4235.patch BuildRequires: gcc, libselinux-devel, audit-libs-devel, libsemanage-devel BuildRequires: libacl-devel, libattr-devel gdb @@ -170,6 +171,9 @@ done %{_mandir}/*/* %changelog +* Fri Mar 24 2023 zhangshaoning - 2:4.8.1-6 +- backport CVE-2013-4235 + * Fri Jan 28 2022 panxiaohe - 2:4.8.1-5 - groupdel: fix SIGSEGV when passwd does not exist -- Gitee