Subject: Re: security/6594: the default "nobody" credentials (32767:9999) do not match mountd's default (-2:-2)
To: NetBSD GNATS submissions and followups <gnats-bugs@gnats.netbsd.org>
From: Greg A. Woods <woods@weird.com>
List: netbsd-bugs
Date: 09/07/2002 15:36:49
The following more complete patch has been tested (minimally) and should
replace the previous patch I submitted to this PR.

Note also the related fix in PR#18222 that is needed to allow the
default -2/-2 user to be represented in /etc/master.passwd et al.

These diffs are against the following revisions:

	$ ident mountd.c exports.5
	mountd.c:
	     $NetBSD: mountd.c,v 1.77 2001/04/24 15:04:27 fvdl Exp $
	     $NetBSD: mountd.c,v 1.77 2001/04/24 15:04:27 fvdl Exp $
	
	exports.5:
	     $NetBSD: exports.5,v 1.20 2001/04/03 11:27:01 wiz Exp $

-- 
								Greg A. Woods

+1 416 218-0098;            <g.a.woods@ieee.org>;           <woods@robohack.ca>
Planix, Inc. <woods@planix.com>; VE3TCP; Secrets of the Weird <woods@weird.com>

Index: exports.5
===================================================================
RCS file: /cvs/NetBSD/src/usr.sbin/mountd/exports.5,v
retrieving revision 1.1.1.6
diff -c -r1.1.1.6 exports.5
*** exports.5	12 Jun 2001 21:26:50 -0000	1.1.1.6
--- exports.5	7 Sep 2002 18:57:03 -0000
***************
*** 33,39 ****
  .\"
  .\"     @(#)exports.5	8.3 (Berkeley) 3/29/95
  .\"
! .Dd March 29, 1995
  .Dt EXPORTS 5
  .Os
  .Sh NAME
--- 33,39 ----
  .\"
  .\"     @(#)exports.5	8.3 (Berkeley) 3/29/95
  .\"
! .Dd September 7, 2002
  .Dt EXPORTS 5
  .Os
  .Sh NAME
***************
*** 91,97 ****
  can still access the whole filesystem via individual RPCs if it
  wanted to, even if just one subdirectory has been mounted.
  The pathnames must not have any symbolic links in them and should not have
! any "." or ".." components.
  Mount points for a filesystem may appear on multiple lines each with
  different sets of hosts and export options.
  .Pp
--- 91,101 ----
  can still access the whole filesystem via individual RPCs if it
  wanted to, even if just one subdirectory has been mounted.
  The pathnames must not have any symbolic links in them and should not have
! any
! .Dq \&.
! or
! .Dq \&..
! components.
  Mount points for a filesystem may appear on multiple lines each with
  different sets of hosts and export options.
  .Pp
***************
*** 106,125 ****
  .Sm off
  .Fl maproot No = Sy user
  .Sm on
! The credential of the specified user is used for remote access by root.
! The credential includes all the groups to which the user is a member
! on the local machine (see
  .Xr id 1 ).
  The user may be specified by name or number.
  .Pp
  .Sm off
  .Fl maproot No = Sy user:group1:group2:...
  .Sm on
! The colon separated list is used to specify the precise credential
! to be used for remote access by root.
  The elements of the list may be either names or numbers.
! Note that user: should be used to distinguish a credential containing
! no groups from a complete credential for that user.
  .Pp
  .Sm off
  .Fl mapall No = Sy user
--- 110,133 ----
  .Sm off
  .Fl maproot No = Sy user
  .Sm on
! The credential of the specified user is used for remote access by a
! client's superuser.  The credential includes all the groups to which the
! user is a member of on the local machine (see
  .Xr id 1 ).
  The user may be specified by name or number.
  .Pp
  .Sm off
  .Fl maproot No = Sy user:group1:group2:...
  .Sm on
! A colon separated list of a user and specific listed groups can be given
! to specify the precise credential and override the default groups the
! local user is a member of.
  The elements of the list may be either names or numbers.
! Note that
! .Dq user:
! can be used to distinguish a credential containing no groups from a
! complete credential for that user which would include the default groups
! that local user is a member of.
  .Pp
  .Sm off
  .Fl mapall No = Sy user
***************
*** 128,135 ****
  .Sm off
  .Fl mapall No = Sy user:group1:group2:...
  .Sm on
! specifies a mapping for all client uids (including root)
! using the same semantics as
  .Fl maproot .
  .Pp
  The option
--- 136,143 ----
  .Sm off
  .Fl mapall No = Sy user:group1:group2:...
  .Sm on
! specifies a mapping for all client user-IDs (including the remote
! superuser) using the same semantics as
  .Fl maproot .
  .Pp
  The option
***************
*** 138,158 ****
  .Fl maproot
  in an effort to be backward compatible with older export file formats.
  .Pp
! In the absence of
  .Fl maproot
  and
  .Fl mapall
! options, remote accesses by root will result in using a credential of -2:-2.
! All other users will be mapped to their remote credential.
! If a
! .Fl maproot
! option is given,
! remote access by root will be mapped to that credential instead of -2:-2.
! If a
  .Fl mapall
! option is given,
! all users (including root) will be mapped to that credential in
! place of their own.
  .Pp
  The
  .Fl kerb
--- 146,166 ----
  .Fl maproot
  in an effort to be backward compatible with older export file formats.
  .Pp
! In the absence of both
  .Fl maproot
  and
  .Fl mapall
! options, remote accesses by any client's superuser will use the
! credentials of the local
! .Dq nfsroot
! user, if one is defined in
! .Pa /etc/master.passwd ,
! or if not then they will use the credentials of
! .Dq -2:-2 .
! All other users will be mapped to their remote credential.  If a
  .Fl mapall
! option is given, all users (including remote superusers) will be mapped
! to that credential in place of their own.
  .Pp
  The
  .Fl kerb
***************
*** 334,339 ****
--- 342,348 ----
  .El
  .Sh SEE ALSO
  .Xr netgroup 5 ,
+ .Xr passwd 5 ,
  .Xr mountd 8 ,
  .Xr nfsd 8 ,
  .Xr showmount 8
Index: mountd.c
===================================================================
RCS file: /cvs/NetBSD/src/usr.sbin/mountd/mountd.c,v
retrieving revision 1.1.1.8
diff -c -r1.1.1.8 mountd.c
*** mountd.c	12 Jun 2001 21:26:50 -0000	1.1.1.8
--- mountd.c	7 Sep 2002 19:25:52 -0000
***************
*** 235,244 ****
  static struct mountlist *mlhead;
  static struct grouplist *grphead;
  static char    *exname;
  static struct ucred def_anon = {
  	1,
! 	(uid_t) - 2,
! 	(gid_t) - 2,
  	0,
  	{}
  };
--- 235,264 ----
  static struct mountlist *mlhead;
  static struct grouplist *grphead;
  static char    *exname;
+ 
+ #define DEF_ANON_USER	"nfsanon"	/* XXX should be just nobody?  But then
+ 					 * that would conflict with the many
+ 					 * other locally running applications
+ 					 * that assume they can use "nobody" as
+ 					 * their default user (eg. Apache,
+ 					 * squid, etc., etc.), and which
+ 					 * actually write to files as "nobody",
+ 					 * files which remote superusers should
+ 					 * not have access to!
+ 					 */
+ /*
+  * If the DEF_ANON_USER doesn't exist then assume default maproot to good
+  * old-fashioned "-2", though be warned that very few systems actually define a
+  * user-id with these values of uid/gid (esp. since they are not allowed by the
+  * old [GU]ID_MAX in <sys/syslimits.h>)
+  */
+ #define DEF_ANON_GID	(-2)
+ #define DEF_ANON_UID	(-2)
+ 
  static struct ucred def_anon = {
  	1,
! 	(uid_t) DEF_ANON_UID,
! 	(gid_t) DEF_ANON_UID,
  	0,
  	{}
  };
***************
*** 310,316 ****
  		exname = *argv;
  	else
  		exname = _PATH_EXPORTS;
! 	openlog("mountd", LOG_PID, LOG_DAEMON);
  
  	s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
  	if (s < 0)
--- 330,336 ----
  		exname = *argv;
  	else
  		exname = _PATH_EXPORTS;
! 	openlog("mountd", LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON);
  
  	s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
  	if (s < 0)
***************
*** 929,935 ****
  	FILE *exp_file;
  	char *line;
  	size_t lineno = 0, len;
! 
  
  	/*
  	 * First, get rid of the old list
--- 949,955 ----
  	FILE *exp_file;
  	char *line;
  	size_t lineno = 0, len;
! 	struct passwd *pw;
  
  	/*
  	 * First, get rid of the old list
***************
*** 953,958 ****
--- 973,1005 ----
  	grphead = NULL;
  
  	/*
+ 	 * reset the def_anon credential
+ 	 */
+ 	if ((pw = getpwnam(DEF_ANON_USER))) {
+ 		int ngroups, groups[NGROUPS + 1];
+ 
+ 		def_anon.cr_uid = pw->pw_uid;
+ 		ngroups = NGROUPS + 1;
+ 		if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups))
+ 			syslog(LOG_ERR, "Too many groups for user: %s", DEF_ANON_USER);
+ 		/*
+ 		 * Convert from int's to gid_t's and compress out duplicate
+ 		 */
+ 		def_anon.cr_ngroups = ngroups - 1;
+ 		def_anon.cr_gid = groups[0];
+ 		for (i = 1; i < ngroups; i++)
+ 			def_anon.cr_groups[i - 1] = groups[i];
+ 	} else {
+ 		syslog(LOG_INFO,
+ 		       "no default NFS anonymous user \"%s\" defined, using uid/gid of -2/-2",
+ 		       DEF_ANON_USER);
+ 		def_anon.cr_uid = DEF_ANON_UID;
+ 		def_anon.cr_gid = DEF_ANON_UID;
+ 		def_anon.cr_ngroups = 0;
+ 		for (i = 0; i < NGROUPS; i++)
+ 			def_anon.cr_groups[i] = 0;
+ 	}
+ 	/*
  	 * And delete exports that are in the kernel for all local
  	 * file systems.
  	 * XXX: Should know how to handle all local exportable file systems
***************
*** 2151,2162 ****
  		memset(&hints, 0, sizeof hints);
  		hints.ai_family = AF_UNSPEC;
  		hints.ai_flags = AI_NUMERICHOST;
! 		if (getaddrinfo(cp, NULL, &hints, &ai) == 0)
  			sa = ai->ai_addr;
! 		else
  			goto fail;
! 	} else
  		goto fail;
  
  	/*
  	 * Only allow /pref notation for v6 addresses.
--- 2198,2215 ----
  		memset(&hints, 0, sizeof hints);
  		hints.ai_family = AF_UNSPEC;
  		hints.ai_flags = AI_NUMERICHOST;
! 		if ((ecode = getaddrinfo(cp, NULL, &hints, &ai)) == 0)
  			sa = ai->ai_addr;
! 		else {
! 			if (debug)
! 				fprintf(stderr, "get_net: getaddrinfo() failed: %s.\n", gai_strerror(ecode));
  			goto fail;
! 		}
! 	} else {
! 		if (debug)
! 			fprintf(stderr, "get_net: getnetbyname() failed, unrecognized net name format.\n");
  		goto fail;
+ 	}
  
  	/*
  	 * Only allow /pref notation for v6 addresses.
***************
*** 2166,2181 ****
  
  	ecode = getnameinfo(sa, sa->sa_len, netname, sizeof netname,
  	    NULL, 0, ninumeric);
! 	if (ecode != 0)
  		goto fail;
  
  	if (maskflg)
  		net->nt_len = countones(sa);
  	else {
  		if (opt_flags & OP_MASKLEN) {
! 			preflen = strtol(prefp, NULL, 10);
! 			if (preflen == LONG_MIN && errno == ERANGE)
  				goto fail;
  			net->nt_len = (int)preflen;
  			*p = '/';
  		}
--- 2219,2240 ----
  
  	ecode = getnameinfo(sa, sa->sa_len, netname, sizeof netname,
  	    NULL, 0, ninumeric);
! 	if (ecode != 0) {
! 		if (debug)
! 			fprintf(stderr, "get_net: getnameinfo() failed: %s.\n", gai_strerror(ecode));
  		goto fail;
+ 	}
  
  	if (maskflg)
  		net->nt_len = countones(sa);
  	else {
  		if (opt_flags & OP_MASKLEN) {
! 			preflen = strtol(prefp, (char **) NULL, 10);
! 			if (preflen == LONG_MIN && errno == ERANGE) {
! 				if (debug)
! 					fprintf(stderr, "get_net: %s out of range.\n", prefp);
  				goto fail;
+ 			}
  			net->nt_len = (int)preflen;
  			*p = '/';
  		}
***************
*** 2258,2267 ****
  	/*
  	 * Set up the unprivileged user.
  	 */
! 	cr->cr_ref = 1;
! 	cr->cr_uid = -2;
! 	cr->cr_gid = -2;
! 	cr->cr_ngroups = 0;
  	/*
  	 * Get the user's password table entry.
  	 */
--- 2317,2323 ----
  	/*
  	 * Set up the unprivileged user.
  	 */
! 	*cr = def_anon;
  	/*
  	 * Get the user's password table entry.
  	 */
***************
*** 2282,2288 ****
  		cr->cr_uid = pw->pw_uid;
  		ngroups = NGROUPS + 1;
  		if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups))
! 			syslog(LOG_ERR, "Too many groups");
  		/*
  		 * Convert from int's to gid_t's and compress out duplicate
  		 */
--- 2338,2344 ----
  		cr->cr_uid = pw->pw_uid;
  		ngroups = NGROUPS + 1;
  		if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups))
! 			syslog(LOG_ERR, "Too many groups for user: %s", name);
  		/*
  		 * Convert from int's to gid_t's and compress out duplicate
  		 */