bin/187310: [patch] pw command segfaults when the -V parameter is used on commands that alter groups

Kim Shrier fbsdbugs at westryn.net
Thu Mar 6 05:50:00 UTC 2014


>Number:         187310
>Category:       bin
>Synopsis:       [patch] pw command segfaults when the -V parameter is used on commands that alter groups
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Thu Mar 06 05:50:00 UTC 2014
>Closed-Date:
>Last-Modified:
>Originator:     Kim Shrier
>Release:        10.0 Release amd64
>Organization:
>Environment:
FreeBSD snorri.lab.westryn.net 10.0-RELEASE FreeBSD 10.0-RELEASE #1 r261308M: Sun Feb 23 18:45:19 MST 2014     carol at snorri.lab.westryn.net:/usr/obj/usr/src/sys/SNORRI_02  amd64
>Description:
When specifying an alternate location of the etc directory using the -V command line parameter to /usr/sbin/pw, the pw command segfaults when it does anything that updates groups
>How-To-Repeat:
Create a master.passwd and group file in a directory other than /etc.  For the sake of this description, I'll use /tmp/pw_problem/etc.

mkdir -p /tmp/pw_problem/etc
cd /tmp/pw_problem/etc

Create a master.passwd file in this directory that contains a line like:

bob:*:1001:1001::0:0:Robert:/home/bob:/bin/sh

Create a group file in this directory that contains a line like:

bob:*:1001:

Run pwd_mkdb.

pwd_mkdb -p -d /tmp/pw_problem/etc master.passwd

Now, try to delete the user with pw

pw -V /tmp/pw_problem/etc userdel bob
Segmentation fault (core dumped)

>Fix:
The problem is that the gr_mem member of the group struct is dereferenced without first checking to see if it is NULL.  This occurs in both /usr/src/usr.sbin/pw/pw_group.c and /usr/src/usr.sbin/pw/pw_user.c.

The reason this happens only when the -V parameter is used is because pw uses different routines based on whether or not the -V parameter is present.  When it isn't specified, it uses getgrent, getgrgid, and getgrnam from libc.  When -V is specified, it uses vgetgrent, vgetgrgid, and vgetgrnam which uses code from pw_vpw.c which is part of the source for pw.  These three routines call vnextgrent which eventually calls gr_scan from libutil.  Looking at the source in libutil, it is possible for the group structure returned by gr_scan to have a NULL gr_mem.  Other code in libutil deals with this possibility.  The pw code does not.

I am attaching a patch file that I made against head.

The rcsid for pw_group is:
static const char rcsid[] =
  "$FreeBSD: head/usr.sbin/pw/pw_group.c 244738 2012-12-27 14:44:13Z bapt $";

The rcsid for pw_user is:
static const char rcsid[] =
  "$FreeBSD: head/usr.sbin/pw/pw_user.c 252688 2013-07-04 07:59:11Z des $";


Patch attached with submission follows:

--- usr.sbin/pw/pw_group.c.orig	2014-03-05 21:12:10.000000000 -0700
+++ usr.sbin/pw/pw_group.c	2014-03-05 21:22:03.000000000 -0700
@@ -227,10 +227,12 @@
 		else if (arg->ch == 'm') {
 			int	k = 0;
 
-			while (grp->gr_mem[k] != NULL) {
-				if (extendarray(&members, &grmembers, i + 2) != -1)
-					members[i++] = grp->gr_mem[k];
-				k++;
+			if (grp->gr_mem != NULL) {
+				while (grp->gr_mem[k] != NULL) {
+					if (extendarray(&members, &grmembers, i + 2) != -1)
+						members[i++] = grp->gr_mem[k];
+					k++;
+				}
 			}
 		}
 
@@ -311,6 +313,9 @@
 	int k;
 	struct passwd *pwd;
 
+	if (grp->gr_mem == NULL)
+		return;
+
 	k = 0;
 	while (grp->gr_mem[k] != NULL) {
 		matchFound = false;
@@ -415,8 +420,10 @@
 		printf("Group Name: %-15s   #%lu\n"
 		       "   Members: ",
 		       grp->gr_name, (long) grp->gr_gid);
-		for (i = 0; grp->gr_mem[i]; i++)
-			printf("%s%s", i ? "," : "", grp->gr_mem[i]);
+		if (grp->gr_mem != NULL) {
+			for (i = 0; grp->gr_mem[i]; i++)
+				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
+		}
 		fputs("\n\n", stdout);
 	}
 	return EXIT_SUCCESS;
--- usr.sbin/pw/pw_user.c.orig	2014-03-05 21:12:10.000000000 -0700
+++ usr.sbin/pw/pw_user.c	2014-03-05 21:21:43.000000000 -0700
@@ -425,19 +425,21 @@
 			}
 
 			grp = GETGRNAM(a_name->val);
-			if (grp != NULL && *grp->gr_mem == NULL)
+			if (grp != NULL && (grp->gr_mem == NULL || *grp->gr_mem == NULL))
 				delgrent(GETGRNAM(a_name->val));
 			SETGRENT();
 			while ((grp = GETGRENT()) != NULL) {
 				int i;
 				char group[MAXLOGNAME];
-				for (i = 0; grp->gr_mem[i] != NULL; i++) {
-					if (!strcmp(grp->gr_mem[i], a_name->val)) {
-						while (grp->gr_mem[i] != NULL) {
-							grp->gr_mem[i] = grp->gr_mem[i+1];
-						}	
-						strlcpy(group, grp->gr_name, MAXLOGNAME);
-						chggrent(group, grp);
+				if (grp->gr_mem != NULL) {
+					for (i = 0; grp->gr_mem[i] != NULL; i++) {
+						if (!strcmp(grp->gr_mem[i], a_name->val)) {
+							while (grp->gr_mem[i] != NULL) {
+								grp->gr_mem[i] = grp->gr_mem[i+1];
+							}	
+							strlcpy(group, grp->gr_name, MAXLOGNAME);
+							chggrent(group, grp);
+						}
 					}
 				}
 			}
@@ -908,7 +910,7 @@
 				errx(EX_NOUSER, "group `%s' is not defined", a_gid->val);
 		}
 		gid = grp->gr_gid;
-	} else if ((grp = GETGRNAM(nam)) != NULL && grp->gr_mem[0] == NULL) {
+	} else if ((grp = GETGRNAM(nam)) != NULL && (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) {
 		gid = grp->gr_gid;  /* Already created? Use it anyway... */
 	} else {
 		struct cargs    grpargs;
@@ -1182,14 +1184,17 @@
 		while ((grp=GETGRENT()) != NULL)
 		{
 			int     i = 0;
-			while (grp->gr_mem[i] != NULL)
+			if (grp->gr_mem != NULL)
 			{
-				if (strcmp(grp->gr_mem[i], pwd->pw_name)==0)
+				while (grp->gr_mem[i] != NULL)
 				{
-					printf(j++ == 0 ? "    Groups: %s" : ",%s", grp->gr_name);
-					break;
+					if (strcmp(grp->gr_mem[i], pwd->pw_name)==0)
+					{
+						printf(j++ == 0 ? "    Groups: %s" : ",%s", grp->gr_name);
+						break;
+					}
+					++i;
 				}
-				++i;
 			}
 		}
 		ENDGRENT();


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list