git: e395e354823b - main - mdo(1): Use setcred() to change credentials

From: Olivier Certner <olce_at_FreeBSD.org>
Date: Mon, 16 Dec 2024 14:46:12 UTC
The branch main has been updated by olce:

URL: https://cgit.FreeBSD.org/src/commit/?id=e395e354823b690ba19ecc8e3688bacec6f67ad3

commit e395e354823b690ba19ecc8e3688bacec6f67ad3
Author:     Olivier Certner <olce@FreeBSD.org>
AuthorDate: 2024-07-29 14:24:08 +0000
Commit:     Olivier Certner <olce@FreeBSD.org>
CommitDate: 2024-12-16 14:42:40 +0000

    mdo(1): Use setcred() to change credentials
    
    As this is the only system call that MAC/do currently supports, and the
    only one that really can be for transitions involving simultaneous
    changes of user and group IDs.
    
    Reviewed by:    bapt
    Approved by:    markj (mentor)
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D47621
---
 usr.bin/mdo/mdo.c | 42 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 35 insertions(+), 7 deletions(-)

diff --git a/usr.bin/mdo/mdo.c b/usr.bin/mdo/mdo.c
index 22e2838daa08..8435fc17f26f 100644
--- a/usr.bin/mdo/mdo.c
+++ b/usr.bin/mdo/mdo.c
@@ -5,6 +5,7 @@
  */
 
 #include <sys/limits.h>
+#include <sys/ucred.h>
 
 #include <err.h>
 #include <paths.h>
@@ -27,6 +28,8 @@ main(int argc, char **argv)
 {
 	struct passwd *pw;
 	const char *username = "root";
+	struct setcred wcred = SETCRED_INITIALIZER;
+	u_int setcred_flags = 0;
 	bool uidonly = false;
 	int ch;
 
@@ -50,20 +53,45 @@ main(int argc, char **argv)
 			const char *errp = NULL;
 			uid_t uid = strtonum(username, 0, UID_MAX, &errp);
 			if (errp != NULL)
-				err(EXIT_FAILURE, "%s", errp);
+				err(EXIT_FAILURE, "invalid user ID '%s'",
+				    username);
 			pw = getpwuid(uid);
 		}
 		if (pw == NULL)
 			err(EXIT_FAILURE, "invalid username '%s'", username);
 	}
+
+	wcred.sc_uid = wcred.sc_ruid = wcred.sc_svuid = pw->pw_uid;
+	setcred_flags |= SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID;
+
 	if (!uidonly) {
-		if (initgroups(pw->pw_name, pw->pw_gid) == -1)
-			err(EXIT_FAILURE, "failed to call initgroups");
-		if (setgid(pw->pw_gid) == -1)
-			err(EXIT_FAILURE, "failed to call setgid");
+		/*
+		 * If there are too many groups specified for some UID, setting
+		 * the groups will fail.  We preserve this condition by
+		 * allocating one more group slot than allowed, as
+		 * getgrouplist() itself is just some getter function and thus
+		 * doesn't (and shouldn't) check the limit, and to allow
+		 * setcred() to actually check for overflow.
+		 */
+		const long ngroups_alloc = sysconf(_SC_NGROUPS_MAX) + 2;
+		gid_t *const groups = malloc(sizeof(*groups) * ngroups_alloc);
+		int ngroups = ngroups_alloc;
+
+		if (groups == NULL)
+			err(EXIT_FAILURE, "cannot allocate memory for groups");
+
+		getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
+
+		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid = pw->pw_gid;
+		wcred.sc_supp_groups = groups + 1;
+		wcred.sc_supp_groups_nb = ngroups - 1;
+		setcred_flags |= SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID |
+		    SETCREDF_SUPP_GROUPS;
 	}
-	if (setuid(pw->pw_uid) == -1)
-		err(EXIT_FAILURE, "failed to call setuid");
+
+	if (setcred(setcred_flags, &wcred, sizeof(wcred)) != 0)
+		err(EXIT_FAILURE, "calling setcred() failed");
+
 	if (*argv == NULL) {
 		const char *sh = getenv("SHELL");
 		if (sh == NULL)