Blob Blame History Raw
From 71b0389fbb31833d827f5f0fec18880c2f602753 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Thu, 19 May 2022 13:52:22 +0300
Subject: [PATCH 1/2] mkhomedir: add support for pre-CVE-2020-10737 behavior

Pre-CVE-2020-10737 behavior was used to allow creating home directories
on NFS mounts when non-Kerberos authentication method is in use. This is
exactly the case where a race condition addressed by the CVE-2020-10737
fix could have happened. However, there are legit use cases where this
setup is needed.

Add '-f' option to mkhomedir helper to activate previous behavior. In
order to enable it, a change to oddjobd-mkhomedir.conf configuration
file is needed by explicitly adding '-f' option to the executable file
definition.

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2050079

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
---
 src/mkhomedir.c                 | 16 +++++++++++++---
 src/oddjobd-mkhomedir.conf.5.in |  9 +++++++++
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/src/mkhomedir.c b/src/mkhomedir.c
index be85959..ac813a9 100644
--- a/src/mkhomedir.c
+++ b/src/mkhomedir.c
@@ -53,9 +53,11 @@ static const char *skel;
 static const char *skel_dir;
 static struct passwd *pwd;
 static mode_t override_umask;
+static int owner_mkdir_first = 0;
 
 #define FLAG_POPULATE	(1 << 0)
 #define FLAG_QUIET	(1 << 1)
+#define FLAG_OWNER_MKDIR_FIRST (1 << 2)
 
 /* Given the path of an item somewhere in the skeleton directory, create as
  * identical as possible a copy in the destination tree. */
@@ -158,7 +160,7 @@ copy_single_item(const char *source, const struct stat *sb,
 		 * target user just yet to avoid potential race conditions
 		 * involving symlink attacks when we copy over the skeleton
 		 * tree. */
-		if (status->level == 0) {
+		if (status->level == 0 && !owner_mkdir_first) {
 			uid = 0;
 			gid = 0;
 		}
@@ -222,6 +224,9 @@ mkhomedir(const char *user, int flags)
 		       pwd->pw_dir);
 		return HANDLER_INVALID_INVOCATION;
 	}
+	if (flags & FLAG_OWNER_MKDIR_FIRST) {
+		owner_mkdir_first = 1;
+	}
 	if ((lstat(pwd->pw_dir, &st) == -1) && (errno == ENOENT)) {
 		/* Figure out which location we're using as a
 		 * template. */
@@ -237,7 +242,7 @@ mkhomedir(const char *user, int flags)
 				int res = nftw(get_skel_dir(), copy_single_item, 5,
 					       FTW_PHYS);
 				/* only now give ownership to the target user */
-				if (res == 0) {
+				if (res == 0 && !owner_mkdir_first) {
 					res = chown(pwd->pw_dir, pwd->pw_uid, pwd->pw_gid);
 				}
 
@@ -317,8 +322,11 @@ main(int argc, char **argv)
 	umask(override_umask);
 	skel_dir = "/etc/skel";
 
-	while ((i = getopt(argc, argv, "nqs:u:")) != -1) {
+	while ((i = getopt(argc, argv, "nqfs:u:")) != -1) {
 		switch (i) {
+		case 'f':
+			flags |= FLAG_OWNER_MKDIR_FIRST;
+			break;
 		case 'n':
 			flags &= ~FLAG_POPULATE;
 			break;
@@ -339,6 +347,8 @@ main(int argc, char **argv)
 			break;
 		default:
 			fprintf(stderr, "Valid options:\n"
+				"-f\tCreate home directory initially owned by user, "
+				"not root. See man page for security issues.\n"
 				"-n\tDo not populate home directories, "
 				"just create them.\n"
 				"-q\tDo not print messages when creating "
diff --git a/src/oddjobd-mkhomedir.conf.5.in b/src/oddjobd-mkhomedir.conf.5.in
index d7a2429..6e35ad5 100644
--- a/src/oddjobd-mkhomedir.conf.5.in
+++ b/src/oddjobd-mkhomedir.conf.5.in
@@ -10,6 +10,15 @@ directory.
 
 The mkhomedir helper itself accepts these options:
 .TP
+-f
+Restore behavior before CVE-2020-10737 was fixed: create the home directory
+with user's ownership directly rather than create it as a root and only after
+populating it change to the user's ownership. The former behavior is insecure
+but may be used to allow creation of NFS-mounted home directories when
+non-Kerberos authentication is in use. It is prone for a race condition that
+could be exploited in the NFS-mounted home directories use case. To avoid
+CVE-2020-10737, do not use \fB-f\fR option in production environments.
+.TP
 -q
 Refrain from outputting the usual "Creating home directory..." message when it
 creates a home directory.
-- 
2.38.1


From b800e25258353dbb1a88506123c21ac3298fd2d0 Mon Sep 17 00:00:00 2001
From: Carlos Santos <casantos@redhat.com>
Date: Tue, 18 Oct 2022 08:59:16 -0300
Subject: [PATCH 2/2] Always set the home directory permissions according to
 HOME_MODE

Currently the home directory permissions are set by taking the /etc/skel
mode and masking it with HOME_MODE:

    override_umask = 0777 & ~get_umask(&configured_umask, "HOME_MODE");
    stat(skel, &sb); /* performed by nftw() */
    oddjob_selinux_mkdir(newpath, sb->st_mode & ~override_umask, uid, gid);

The problem is that when HOME_MODE is more permissive than /etc/skel,
the masking will not produce the desired result, e.g.

    skel_mode = 0755
    HOME_MODE = 0775
    override_umask = 0777 & ~HOME_MODE /* 0002 */
    mode = skel_mode & ~override_umask /* 0755 & 0775 = 0755 */

In order to fix the problem, always use 0777 & ~override_umask for the
top home directory.

Signed-off-by: Carlos Santos <casantos@redhat.com>
Fixes: https://pagure.io/oddjob/issue/17
---
 src/mkhomedir.c | 45 +++++++++++++++++++++------------------------
 1 file changed, 21 insertions(+), 24 deletions(-)

diff --git a/src/mkhomedir.c b/src/mkhomedir.c
index ac813a9..932918f 100644
--- a/src/mkhomedir.c
+++ b/src/mkhomedir.c
@@ -67,6 +67,7 @@ copy_single_item(const char *source, const struct stat *sb,
 {
 	uid_t uid = pwd->pw_uid;
 	gid_t gid = pwd->pw_gid;
+	mode_t mode = sb->st_mode & ~override_umask;
 	int sfd, dfd, i, res;
 	char target[PATH_MAX + 1], newpath[PATH_MAX + 1];
 	unsigned char buf[BUFSIZ];
@@ -112,8 +113,7 @@ copy_single_item(const char *source, const struct stat *sb,
 			oddjob_set_selinux_file_creation_context(newpath,
 								 sb->st_mode |
 								 S_IFREG);
-			dfd = open(newpath, O_WRONLY | O_CREAT | O_EXCL,
-				   sb->st_mode & ~override_umask);
+			dfd = open(newpath, O_WRONLY | O_CREAT | O_EXCL, mode);
 			if (dfd != -1) {
 				while ((i = read(sfd, buf, sizeof(buf))) > 0) {
 					retry_write(dfd, buf, i);
@@ -156,20 +156,22 @@ copy_single_item(const char *source, const struct stat *sb,
 		}
 		return 0;
 	case FTW_D:
-		/* It's the home directory itself. Don't give it to the
-		 * target user just yet to avoid potential race conditions
-		 * involving symlink attacks when we copy over the skeleton
-		 * tree. */
-		if (status->level == 0 && !owner_mkdir_first) {
-			uid = 0;
-			gid = 0;
-		}
-
 		/* It's a directory.  Make one with the same name and
 		 * permissions, but owned by the target user. */
-		res = oddjob_selinux_mkdir(newpath,
-					   sb->st_mode & ~override_umask,
-					   uid, gid);
+		if (status->level == 0) {
+			/* It's the home directory itself.  Use the configured
+			 * (or overriden) mode, not the source mode & umask. */
+			mode = 0777 & ~override_umask;
+
+			/* Don't give it to the target user just yet to avoid
+			 * potential race conditions involving symlink attacks
+			 * when we copy over the skeleton tree. */
+			if (!owner_mkdir_first) {
+				uid = 0;
+				gid = 0;
+			}
+		}
+		res = oddjob_selinux_mkdir(newpath, mode, uid, gid);
 
 		/* on unexpected errors, or if the home directory itself
 		 * suddenly already exists, abort the copy operation. */
@@ -248,12 +250,8 @@ mkhomedir(const char *user, int flags)
 
 				return res;
 			} else {
-				if (stat(skel, &st) != 0) {
-					st.st_mode = S_IRWXU;
-				}
 				if ((oddjob_selinux_mkdir(pwd->pw_dir,
-							  st.st_mode &
-							  ~override_umask,
+							  0777 & ~override_umask,
 							  pwd->pw_uid,
 							  pwd->pw_gid) != 0) &&
 				    (errno != EEXIST)) {
@@ -269,11 +267,11 @@ mkhomedir(const char *user, int flags)
 }
 
 static mode_t
-get_umask(int *configured, const char *variable)
+get_umask(int *configured, const char *variable, mode_t default_value)
 {
 	FILE *fp;
 	char buf[BUFSIZ], *p, *end;
-	mode_t mask = umask(0777);
+	mode_t mask = default_value;
 	long tmp;
 	size_t vlen = strlen(variable);
 
@@ -315,11 +313,10 @@ main(int argc, char **argv)
 
 	openlog(PACKAGE "-mkhomedir", LOG_PID, LOG_DAEMON);
 	/* Unlike UMASK, HOME_MODE is the file mode, so needs to be reverted */
-	override_umask = 0777 & ~get_umask(&configured_umask, "HOME_MODE");
+	override_umask = 0777 & ~get_umask(&configured_umask, "HOME_MODE", 0);
 	if (configured_umask == 0) {
-		override_umask = get_umask(&configured_umask, "UMASK");
+		override_umask = get_umask(&configured_umask, "UMASK", 022);
 	}
-	umask(override_umask);
 	skel_dir = "/etc/skel";
 
 	while ((i = getopt(argc, argv, "nqfs:u:")) != -1) {
-- 
2.38.1