diff -Nru shadow-4.8.1/debian/changelog shadow-4.8.1/debian/changelog --- shadow-4.8.1/debian/changelog 2022-03-14 08:59:13.000000000 +0000 +++ shadow-4.8.1/debian/changelog 2022-11-24 12:05:18.000000000 +0000 @@ -1,3 +1,28 @@ +shadow (1:4.8.1-2ubuntu2.1) jammy-security; urgency=medium + + * SECURITY UPDATE: race condition when copying and removing directory trees + - debian/patches/CVE-2013-4235-pre1.patch: add nofollow to opens. + - debian/patches/CVE-2013-4235-pre2.patch: prepare context for actual file + type (set_selinux_file_context). + - debian/patches/CVE-2013-4235-1.patch: avoid races in chown_tree(). + - debian/patches/CVE-2013-4235-2.patch: avoid races in remove_tree(). + - debian/patches/CVE-2013-4235-3.patch: require symlink support. + - debian/patches/CVE-2013-4235-4.patch: fail if regular file pre-exists in + copy_tree(). + - debian/patches/CVE-2013-4235-5.patch: more robust file content copy in + copy_tree(). + - debian/patches/CVE-2013-4235-6.patch: address minor compiler warnings. + - debian/patches/CVE-2013-4235-7.patch: avoid races in copy_tree(). + - debian/patches/CVE-2013-4235-post1.patch: use fchmodat instead of chmod + (copy_tree). + - debian/patches/CVE-2013-4235-post2.patch: do not block on fifos + (copy_tree). + - debian/patches/CVE-2013-4235-post3.patch: carefully treat permissions + (copy_tree). + - CVE-2013-4235 + + -- Camila Camargo de Matos Thu, 24 Nov 2022 09:05:18 -0300 + shadow (1:4.8.1-2ubuntu2) jammy; urgency=medium [ Michael Vogt ] diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-1.patch shadow-4.8.1/debian/patches/CVE-2013-4235-1.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-1.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,233 @@ +[Ubuntu note: since commit 5450f9a90 and its parent commit have not + been applied to this version of the code, this patch was backported + in order to use the 'DIRECT' macro (in places where this usage is + justified and makes sense in the code) as it was doing before the + 5450f9a90 change was made. This avoids any possible issues, since + the macro still exists here.] + +Backport of: + +From 83d42e9e884829be028b3d2b276dc35bfc8c30cf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:19 +0200 +Subject: [PATCH] Avoid races in chown_tree() + +Use *at() functions to pin the directory operating in to avoid being +redirected by unprivileged users replacing parts of paths by symlinks to +privileged files. +--- + libmisc/chowndir.c | 130 +++++++++++++++++---------------------------- + 1 file changed, 49 insertions(+), 81 deletions(-) + +Index: shadow-4.2/libmisc/chowndir.c +=================================================================== +--- shadow-4.2.orig/libmisc/chowndir.c ++++ shadow-4.2/libmisc/chowndir.c +@@ -40,45 +40,27 @@ + #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; ++ const struct DIRECT *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; + } + +@@ -88,68 +70,34 @@ 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 + */ +- + if ( (strcmp (ent->d_name, ".") == 0) + || (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 +107,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 -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-2.patch shadow-4.8.1/debian/patches/CVE-2013-4235-2.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-2.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,160 @@ +[Ubuntu note: since commit 5450f9a90 and its parent commit have not + been applied to this version of the code, this patch was backported + in order to use the 'DIRECT' macro (in places where this usage is + justified and makes sense in the code) as it was doing before the + 5450f9a90 change was made. This avoids any possible issues, since + the macro still exists here.] + +Backport of: + +From 479fc86fbe4add5ae0c66571965627c8fbac881d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:22 +0200 +Subject: [PATCH] Avoid races in remove_tree() + +Use *at() functions to pin the directory operating in to avoid being +redirected by unprivileged users replacing parts of paths by symlinks to +privileged files. +--- + libmisc/remove_tree.c | 87 +++++++++++++++++++------------------------ + 1 file changed, 39 insertions(+), 48 deletions(-) + +Index: shadow-4.2/libmisc/remove_tree.c +=================================================================== +--- shadow-4.2.orig/libmisc/remove_tree.c ++++ shadow-4.2/libmisc/remove_tree.c +@@ -34,6 +34,7 @@ + + #ident "$Id$" + ++#include + #include + #include + #include +@@ -44,36 +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 DIRECT *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 +77,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); + } + diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-3.patch shadow-4.8.1/debian/patches/CVE-2013-4235-3.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-3.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,137 @@ +[Ubuntu note: removed changes made to 'configure.ac' file] + +Backport of: + +From e0d33fe77cee9364fffbfa58c499b459040d4c7f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:24 +0200 +Subject: [PATCH] Require symlink support + +Require lstat(2), lchown(2), S_IFLNK and S_ISLNK from POSIX.1-2001. + +Already unconditionally used in lib/tcbfuncs.c and lib/run_part.c. +--- + configure.ac | 2 +- + lib/commonio.c | 2 -- + lib/defines.h | 16 ---------------- + libmisc/copydir.c | 10 ++-------- + 4 files changed, 3 insertions(+), 27 deletions(-) + +diff --git a/lib/commonio.c b/lib/commonio.c +index 9e0fde600..80288d644 100644 +Index: shadow-4.8.1/lib/commonio.c +=================================================================== +--- shadow-4.8.1.orig/lib/commonio.c ++++ shadow-4.8.1/lib/commonio.c +@@ -88,7 +88,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__ */ +@@ -105,7 +104,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 + # define STTY(fd, termio) tcsetattr(fd, TCSANOW, termio) +Index: shadow-4.8.1/libmisc/copydir.c +=================================================================== +--- shadow-4.8.1.orig/libmisc/copydir.c ++++ shadow-4.8.1/libmisc/copydir.c +@@ -78,14 +78,12 @@ static int copy_dir (const char *src, co + const struct stat *statp, const struct timeval 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, + 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); +@@ -243,7 +241,7 @@ int copy_tree (const char *src_root, con + return -1; + } + +- if (LSTAT (src_root, &sb) == -1) { ++ if (lstat (src_root, &sb) == -1) { + return -1; + } + +@@ -384,7 +382,7 @@ static int copy_entry (const char *src, + struct link_name *lp; + struct timeval mt[2]; + +- if (LSTAT (src, &sb) == -1) { ++ if (lstat (src, &sb) == -1) { + /* If we cannot stat the file, do not care. */ + } else { + #ifdef HAVE_STRUCT_STAT_ST_ATIM +@@ -416,7 +414,6 @@ static int copy_entry (const char *src, + old_uid, new_uid, old_gid, new_gid); + } + +-#ifdef S_IFLNK + /* + * Copy any symbolic links + */ +@@ -425,7 +422,6 @@ 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 +@@ -518,7 +514,6 @@ static int copy_dir (const char *src, co + return err; + } + +-#ifdef S_IFLNK + /* + * readlink_malloc - wrapper for readlink + * +@@ -636,7 +631,6 @@ static int copy_symlink (const char *src + + return 0; + } +-#endif /* S_IFLNK */ + + /* + * copy_hardlink - copy a hardlink diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-4.patch shadow-4.8.1/debian/patches/CVE-2013-4235-4.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-4.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-4.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,24 @@ +From 14fcd7b412a7a13973a9453fd97f60fc277ebd0f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:26 +0200 +Subject: [PATCH] Fail if regular file pre-exists in copy_tree() + +Similar to the default behavior of mkdir(2), symlink(2), link(2) and +mknod(2). +--- + libmisc/copydir.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: shadow-4.2/libmisc/copydir.c +=================================================================== +--- shadow-4.2.orig/libmisc/copydir.c ++++ shadow-4.2/libmisc/copydir.c +@@ -742,7 +742,7 @@ static int copy_file (const char *src, c + return -1; + } + #endif /* WITH_SELINUX */ +- ofd = open (dst, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, statp->st_mode & 07777); ++ ofd = open (dst, 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) diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-5.patch shadow-4.8.1/debian/patches/CVE-2013-4235-5.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-5.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-5.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,100 @@ +[Ubuntu note: minor changes had to be made to the original patch due + to this version of shadow not including commit e65cc6aeb.] + +Backport of: + +From e666de721aedf6deae8b11bef2e0701cf110f307 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:27 +0200 +Subject: [PATCH] More robust file content copy in copy_tree() + +Bail out on read(2) failure, continue on EINTR, support short writes and +increase chunk size. +--- + libmisc/copydir.c | 58 +++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 54 insertions(+), 4 deletions(-) + +Index: shadow-4.2/libmisc/copydir.c +=================================================================== +--- shadow-4.2.orig/libmisc/copydir.c ++++ shadow-4.2/libmisc/copydir.c +@@ -712,6 +712,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. +@@ -730,8 +766,6 @@ static int copy_file (const char *src, c + int err = 0; + int ifd; + int ofd; +- char buf[1024]; +- ssize_t cnt; + + ifd = open (src, O_RDONLY|O_NOFOLLOW); + if (ifd < 0) { +@@ -769,8 +803,25 @@ static int copy_file (const char *src, c + 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); + return -1; + } diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-6.patch shadow-4.8.1/debian/patches/CVE-2013-4235-6.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-6.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-6.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,42 @@ +From 3db58ddf6394dfd1a0fe81dcb94dc81fe9fe6d6a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:29 +0200 +Subject: [PATCH] Address minor compiler warnings + + copydir.c:666:44: warning: unsigned conversion from 'int' to '__mode_t' {aka 'unsigned int'} changes value from '-4096' to '4294963200' [-Wsign-conversion] + 666 | if ( (mknod (dst, statp->st_mode & ~07777, statp->st_rdev) != 0) + | ^ + + copydir.c:116:1: warning: missing initializer for field 'quote' of 'struct error_context' [-Wmissing-field-initializers] + 116 | }; + | ^ + In file included from copydir.c:27: + /usr/include/attr/error_context.h:30:23: note: 'quote' declared here + 30 | const char *(*quote) (struct error_context *, const char *); + | ^~~~~ +--- + libmisc/copydir.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +Index: shadow-4.2/libmisc/copydir.c +=================================================================== +--- shadow-4.2.orig/libmisc/copydir.c ++++ shadow-4.2/libmisc/copydir.c +@@ -132,7 +132,7 @@ static void error_acl (struct error_cont + } + + static struct error_context ctx = { +- error_acl ++ error_acl, NULL, NULL + }; + #endif /* WITH_ACL || WITH_ATTR */ + +@@ -683,7 +683,7 @@ static int copy_special (const char *src + } + #endif /* WITH_SELINUX */ + +- if ( (mknod (dst, statp->st_mode & ~07777, statp->st_rdev) != 0) ++ if ( (mknod (dst, statp->st_mode & ~07777U, statp->st_rdev) != 0) + || (chown_if_needed (dst, statp, + old_uid, new_uid, old_gid, new_gid) != 0) + #ifdef WITH_ACL diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-7.patch shadow-4.8.1/debian/patches/CVE-2013-4235-7.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-7.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-7.patch 2022-11-24 11:56:44.000000000 +0000 @@ -0,0 +1,663 @@ +[Ubuntu note: since commit 5450f9a90 and its parent commit have not + been applied to this version of the code, this patch was backported + in order to use the 'DIRECT' macro (in places where this usage is + justified and makes sense in the code) as it was doing before the + 5450f9a90 change was made. This avoids any possible issues, since + the macro still exists here. Additionally, commit 79157cbad8 is + also not present in this version of shadow, meaning that 'Prog' is + still used instead of 'log_get_progname()' in fprintf calls. The + backport adjusts in order to consider this.] + +Backport of: + +From 6b228b2ba5a24f48bf6e74710cbd9582b157bde5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 5 Aug 2022 17:57:32 +0200 +Subject: [PATCH] Avoid races in copy_tree() + +Use *at() functions to pin the directory operating in to avoid being +redirected by unprivileged users replacing parts of paths by symlinks to +privileged files. + +Introduce a path_info struct with the full path and dirfd and name +information for *at() functions, since the full path is needed for link +resolution, SELinux label lookup and ACL attributes. +--- + libmisc/copydir.c | 330 ++++++++++++++++++++++++++++++---------------- + 1 file changed, 218 insertions(+), 112 deletions(-) + +Index: shadow-4.8.1/libmisc/copydir.c +=================================================================== +--- shadow-4.8.1.orig/libmisc/copydir.c ++++ shadow-4.8.1/libmisc/copydir.c +@@ -69,40 +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); + 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); +-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); +@@ -136,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_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 + */ +@@ -208,51 +262,36 @@ 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); + } + +@@ -262,8 +301,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; + } + +@@ -274,14 +319,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) { +@@ -294,8 +341,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); +@@ -307,12 +354,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; ++ ++ dst_entry.full_path = dst_name; ++ dst_entry.dirfd = dst_fd; ++ dst_entry.name = ent->d_name; + +- err = copy_entry (src_name, dst_name, ++ err = copy_entry (&src_entry, &dst_entry, + reset_selinux, + old_uid, new_uid, + old_gid, new_gid); +@@ -326,6 +383,7 @@ int copy_tree (const char *src_root, con + } + } + (void) closedir (dir); ++ (void) close (dst_fd); + + if (set_orig) { + src_orig = NULL; +@@ -372,7 +430,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) +@@ -380,32 +438,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 */ + +@@ -427,7 +485,7 @@ static int copy_entry (const char *src, + * 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); + } + +@@ -466,9 +524,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) + { +@@ -480,15 +538,15 @@ static int copy_dir (const char *src, co + */ + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (dst, S_IFDIR) != 0) { ++ if (set_selinux_file_context (dst->full_path, S_IFDIR) != 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) +@@ -502,12 +560,12 @@ 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; + } + +@@ -560,9 +618,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) + { +@@ -580,7 +638,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; + } +@@ -600,13 +658,13 @@ static int copy_symlink (const char *src + } + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (dst, S_IFLNK) != 0) { ++ if (set_selinux_file_context (dst->full_path, S_IFLNK) != 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 +@@ -620,14 +678,9 @@ 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; + } +@@ -639,13 +692,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; + } + +@@ -669,28 +722,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, statp->st_mode & S_IFMT) != 0) { ++ if (set_selinux_file_context (dst->full_path, statp->st_mode & S_IFMT) != 0) { + return -1; + } + #endif /* WITH_SELINUX */ + +- if ( (mknod (dst, statp->st_mode & ~07777U, 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 + /* +@@ -701,10 +754,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; + } + +@@ -757,9 +810,9 @@ static ssize_t full_write(int fd, const + * + * 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) + { +@@ -767,21 +820,21 @@ static int copy_file (const char *src, c + int ifd; + int ofd; + +- ifd = open (src, O_RDONLY|O_NOFOLLOW); ++ 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, S_IFREG) != 0) { ++ if (set_selinux_file_context (dst->full_path, S_IFREG) != 0) { + return -1; + } + #endif /* WITH_SELINUX */ +- ofd = open (dst, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 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) +@@ -795,7 +848,7 @@ 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 */ + ) { +@@ -828,22 +881,13 @@ static int copy_file (const char *src, c + } + + (void) close (ifd); +- +-#ifdef HAVE_FUTIMES +- if (futimes (ofd, mt) != 0) { +- return -1; +- } +-#endif /* HAVE_FUTIMES */ +- + 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; + } +@@ -878,7 +922,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); ++} diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-post1.patch shadow-4.8.1/debian/patches/CVE-2013-4235-post1.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-post1.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-post1.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,24 @@ +From f3bdb28e57e5e38c1e89347976c7d61a181eec32 Mon Sep 17 00:00:00 2001 +From: Samanta Navarro +Date: Sun, 4 Sep 2022 11:54:19 +0000 +Subject: [PATCH] copy_tree: use fchmodat instead of chmod + +Fixes regression introduced in faeab50e710131816b261de66141524898c2c487 +for setups configured without acl support. +--- + libmisc/copydir.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: shadow-4.2/libmisc/copydir.c +=================================================================== +--- shadow-4.2.orig/libmisc/copydir.c ++++ shadow-4.2/libmisc/copydir.c +@@ -566,7 +566,7 @@ static int copy_dir (const struct path_i + || ( (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 + /* diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-post2.patch shadow-4.8.1/debian/patches/CVE-2013-4235-post2.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-post2.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-post2.patch 2022-11-24 11:55:31.000000000 +0000 @@ -0,0 +1,50 @@ +From 10cd68e0f04b48363eb32d2c6e168b358fb27810 Mon Sep 17 00:00:00 2001 +From: Samanta Navarro +Date: Sun, 4 Sep 2022 11:58:03 +0000 +Subject: [PATCH] copy_tree: do not block on fifos + +Fixes regression introduced in faeab50e710131816b261de66141524898c2c487. + +If a directory contains fifos, then openat blocks until the other side +of the fifo is connected as well. + +This means that users can prevent "usermod -m" from completing if their +home directories contain at least one fifo. +--- + libmisc/copydir.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +Index: shadow-4.2/libmisc/copydir.c +=================================================================== +--- shadow-4.2.orig/libmisc/copydir.c ++++ shadow-4.2/libmisc/copydir.c +@@ -146,12 +146,12 @@ static int perm_copy_path(const struct p + { + int src_fd, dst_fd, ret; + +- src_fd = openat(src->dirfd, src->name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); ++ 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_CLOEXEC); ++ 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; +@@ -172,12 +172,12 @@ static int attr_copy_path(const struct p + { + int src_fd, dst_fd, ret; + +- src_fd = openat(src->dirfd, src->name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); ++ 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_CLOEXEC); ++ 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; diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-post3.patch shadow-4.8.1/debian/patches/CVE-2013-4235-post3.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-post3.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-post3.patch 2022-11-24 12:01:37.000000000 +0000 @@ -0,0 +1,92 @@ +From cde221b8587193f9dc300c0799a530e846c75961 Mon Sep 17 00:00:00 2001 +From: Samanta Navarro +Date: Sat, 10 Sep 2022 11:58:15 +0000 +Subject: [PATCH] copy_tree: carefully treat permissions + +The setuid, setgid, and sticky bits are not copied during copy_tree. + +Also start with very restrictive permissions before setting ownerships. + +This prevents situations in which users in a group with less permissions +than others could win a race in opening the file before permissions are +removed again. + +Proof of concept: + +$ echo $HOME +/home/uwu +$ install -o uwu -g fandom -m 604 /dev/null /home/uwu/owo +$ ls -l /home/uwu/owo +-rw----r-- 1 uwu fandom 0 Sep 4 00:00 /home/uwu/owo + +If /tmp is on another filesystem, then "usermod -md /tmp/uwu uwu" leads +to this temporary situation: + +$ ls -l /tmp/uwu/owo +-rw----r-- 1 root root 0 Sep 4 00:00 /tmp/uwu/owo + +This means that between openat and chownat_if_needed a user of group +fandom could open /tmp/uwu/owo and read the content when it is finally +written into the file. +--- + libmisc/copydir.c | 19 ++++++++----------- + 1 file changed, 8 insertions(+), 11 deletions(-) + +Index: shadow-4.8.1/libmisc/copydir.c +=================================================================== +--- shadow-4.8.1.orig/libmisc/copydir.c ++++ shadow-4.8.1/libmisc/copydir.c +@@ -542,15 +542,14 @@ static int copy_dir (const struct path_i + return -1; + } + #endif /* WITH_SELINUX */ +- if ( (mkdirat (dst->dirfd, dst->name, statp->st_mode) != 0) ++ if ( (mkdirat (dst->dirfd, dst->name, 0700) != 0) + || (chownat_if_needed (dst, statp, + old_uid, new_uid, old_gid, new_gid) != 0) ++ || (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0) + #ifdef WITH_ACL + || ( (perm_copy_path (src, dst, &ctx) != 0) + && (errno != 0)) +-#else /* !WITH_ACL */ +- || (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0) +-#endif /* !WITH_ACL */ ++#endif /* WITH_ACL */ + #ifdef WITH_ATTR + /* + * If the third parameter is NULL, all extended attributes +@@ -739,12 +738,11 @@ static int copy_special (const struct pa + 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) ++ || (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0) + #ifdef WITH_ACL + || ( (perm_copy_path (src, dst, &ctx) != 0) + && (errno != 0)) +-#else /* !WITH_ACL */ +- || (fchmodat (dst->dirfd, dst->name, statp->st_mode & 07777, AT_SYMLINK_NOFOLLOW) != 0) +-#endif /* !WITH_ACL */ ++#endif /* WITH_ACL */ + #ifdef WITH_ATTR + /* + * If the third parameter is NULL, all extended attributes +@@ -829,16 +827,15 @@ static int copy_file (const struct path_ + return -1; + } + #endif /* WITH_SELINUX */ +- ofd = openat (dst->dirfd, dst->name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, statp->st_mode & 07777); ++ ofd = openat (dst->dirfd, dst->name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0600); + if ( (ofd < 0) + || (fchown_if_needed (ofd, statp, + old_uid, new_uid, old_gid, new_gid) != 0) ++ || (fchmod (ofd, statp->st_mode & 07777) != 0) + #ifdef WITH_ACL + || ( (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) +-#endif /* !WITH_ACL */ ++#endif /* WITH_ACL */ + #ifdef WITH_ATTR + /* + * If the third parameter is NULL, all extended attributes diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-pre1.patch shadow-4.8.1/debian/patches/CVE-2013-4235-pre1.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-pre1.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-pre1.patch 2022-11-24 11:55:32.000000000 +0000 @@ -0,0 +1,31 @@ +From b4472167c2f5057d56686d3349a9b55fc508efe6 Mon Sep 17 00:00:00 2001 +From: ed neville +Date: Fri, 31 Dec 2021 22:40:13 +0000 +Subject: [PATCH] Adding nofollow to opens + +--- + libmisc/copydir.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +Index: shadow-4.2/libmisc/copydir.c +=================================================================== +--- shadow-4.2.orig/libmisc/copydir.c ++++ shadow-4.2/libmisc/copydir.c +@@ -739,7 +739,7 @@ static int copy_file (const char *src, c + char buf[1024]; + ssize_t cnt; + +- ifd = open (src, O_RDONLY); ++ ifd = open (src, O_RDONLY|O_NOFOLLOW); + if (ifd < 0) { + return -1; + } +@@ -748,7 +748,7 @@ static int copy_file (const char *src, c + return -1; + } + #endif /* WITH_SELINUX */ +- ofd = open (dst, O_WRONLY | O_CREAT | O_TRUNC, statp->st_mode & 07777); ++ ofd = open (dst, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, statp->st_mode & 07777); + if ( (ofd < 0) + || (fchown_if_needed (ofd, statp, + old_uid, new_uid, old_gid, new_gid) != 0) diff -Nru shadow-4.8.1/debian/patches/CVE-2013-4235-pre2.patch shadow-4.8.1/debian/patches/CVE-2013-4235-pre2.patch --- shadow-4.8.1/debian/patches/CVE-2013-4235-pre2.patch 1970-01-01 00:00:00.000000000 +0000 +++ shadow-4.8.1/debian/patches/CVE-2013-4235-pre2.patch 2022-11-24 11:55:32.000000000 +0000 @@ -0,0 +1,153 @@ +[Ubuntu note: this patch had to be backported due to the code in this + release still using the deprecated function 'matchpathcon', which was + replaced by the more modern selabel interface in one of the commits + from https://github.com/shadow-maint/shadow/pull/323/commits. Issue + 322, which presented the problem of there having no mode matching + when calls to set_selinux_file_context were made, mentions function + 'matchpathcon' instead of the ones introduced by PR 323. It also + requests that the 'mode' bit be set when this function is called. + Therefore, changes made here try to adapt the changes from the + original commit into the old code and call 'matchpathcon' with the + mode matching bit set.] + +Backport of: + +From eb1d2de0e9bd9e1262080312f0bca9c7e6a36d94 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= +Date: Fri, 9 Apr 2021 18:21:00 +0200 +Subject: [PATCH] set_selinux_file_context(): prepare context for actual file + type +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Search the SELinux selabel database for the file type to be created. +Not specifying the file mode can cause an incorrect file context to be +returned. + +Also prepare contexts in commonio_close() for the generic database +filename, not with the backup suffix appended, to ensure the desired +file context after the final rename. + +Closes: #322 + +Signed-off-by: Christian Göttsche +Acked-by: James Carter +--- + lib/commonio.c | 4 ++-- + lib/prototypes.h | 2 +- + lib/selinux.c | 4 ++-- + libmisc/copydir.c | 8 ++++---- + src/useradd.c | 4 ++-- + 5 files changed, 11 insertions(+), 11 deletions(-) + +Index: shadow-4.8.1/lib/commonio.c +=================================================================== +--- shadow-4.8.1.orig/lib/commonio.c ++++ shadow-4.8.1/lib/commonio.c +@@ -967,7 +967,7 @@ int commonio_close (struct commonio_db * + snprintf (buf, sizeof buf, "%s-", db->filename); + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (buf) != 0) { ++ if (set_selinux_file_context (db->filename, S_IFREG) != 0) { + errors++; + } + #endif +@@ -1011,7 +1011,7 @@ int commonio_close (struct commonio_db * + snprintf (buf, sizeof buf, "%s+", db->filename); + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (buf) != 0) { ++ if (set_selinux_file_context (db->filename, S_IFREG) != 0) { + errors++; + } + #endif +Index: shadow-4.8.1/lib/prototypes.h +=================================================================== +--- shadow-4.8.1.orig/lib/prototypes.h ++++ shadow-4.8.1/lib/prototypes.h +@@ -334,7 +334,7 @@ extern /*@observer@*/const char *crypt_m + + /* selinux.c */ + #ifdef WITH_SELINUX +-extern int set_selinux_file_context (const char *dst_name); ++extern int set_selinux_file_context (const char *dst_name, mode_t mode); + extern int reset_selinux_file_context (void); + extern int check_selinux_permit (const char *perm_name); + #endif +Index: shadow-4.8.1/lib/selinux.c +=================================================================== +--- shadow-4.8.1.orig/lib/selinux.c ++++ shadow-4.8.1/lib/selinux.c +@@ -51,7 +51,7 @@ static bool selinux_enabled; + * Callers may have to Reset SELinux to create files with default + * contexts with reset_selinux_file_context + */ +-int set_selinux_file_context (const char *dst_name) ++int set_selinux_file_context (const char *dst_name, mode_t mode) + { + /*@null@*/security_context_t scontext = NULL; + +@@ -62,7 +62,7 @@ int set_selinux_file_context (const char + + if (selinux_enabled) { + /* Get the default security context for this file */ +- if (matchpathcon (dst_name, 0, &scontext) < 0) { ++ if (matchpathcon (dst_name, mode, &scontext) < 0) { + if (security_getenforce () != 0) { + return 1; + } +Index: shadow-4.8.1/libmisc/copydir.c +=================================================================== +--- shadow-4.8.1.orig/libmisc/copydir.c ++++ shadow-4.8.1/libmisc/copydir.c +@@ -484,7 +484,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, S_IFDIR) != 0) { + return -1; + } + #endif /* WITH_SELINUX */ +@@ -605,7 +605,7 @@ static int copy_symlink (const char *src + } + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (dst) != 0) { ++ if (set_selinux_file_context (dst, S_IFLNK) != 0) { + free (oldlink); + return -1; + } +@@ -684,7 +684,7 @@ static int copy_special (const char *src + int err = 0; + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (dst) != 0) { ++ if (set_selinux_file_context (dst, statp->st_mode & S_IFMT) != 0) { + return -1; + } + #endif /* WITH_SELINUX */ +@@ -744,7 +744,7 @@ static int copy_file (const char *src, c + return -1; + } + #ifdef WITH_SELINUX +- if (set_selinux_file_context (dst) != 0) { ++ if (set_selinux_file_context (dst, S_IFREG) != 0) { + return -1; + } + #endif /* WITH_SELINUX */ +Index: shadow-4.8.1/src/useradd.c +=================================================================== +--- shadow-4.8.1.orig/src/useradd.c ++++ shadow-4.8.1/src/useradd.c +@@ -2106,7 +2106,7 @@ static void create_home (void) + ++bhome; + + #ifdef WITH_SELINUX +- if (set_selinux_file_context (prefix_user_home) != 0) { ++ if (set_selinux_file_context (prefix_user_home, S_IFDIR) != 0) { + fprintf (stderr, + _("%s: cannot set SELinux context for home directory %s\n"), + Prog, user_home); diff -Nru shadow-4.8.1/debian/patches/series shadow-4.8.1/debian/patches/series --- shadow-4.8.1/debian/patches/series 2021-11-11 15:42:38.000000000 +0000 +++ shadow-4.8.1/debian/patches/series 2022-11-24 11:55:42.000000000 +0000 @@ -21,3 +21,15 @@ 1014_extrausers_delgroup.patch 1015_add_zsys_support.patch 1016_extrausers_gpasswd.patch +CVE-2013-4235-pre1.patch +CVE-2013-4235-pre2.patch +CVE-2013-4235-1.patch +CVE-2013-4235-2.patch +CVE-2013-4235-3.patch +CVE-2013-4235-4.patch +CVE-2013-4235-5.patch +CVE-2013-4235-6.patch +CVE-2013-4235-7.patch +CVE-2013-4235-post1.patch +CVE-2013-4235-post2.patch +CVE-2013-4235-post3.patch