Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 03 Sep 2019 14:06:28 -0000
From:      Rick Macklem <rmacklem@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r345994 - head/usr.sbin/nfsuserd
Message-ID:  <201904062205.x36M5pxA050022@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: rmacklem
Date: Sat Apr  6 22:05:51 2019
New Revision: 345994
URL: https://svnweb.freebsd.org/changeset/base/345994

Log:
  Fix nfsuserd so that it handles the mapped localhost address when jails
  are enabled.
  
  The nfsuserd(8) daemon does not function correctly when jails are enabled,
  since localhost gets mapped to another IP address and, as such, the upcall
  RPC fails.
  This patch fixes the problem by doing a getsockname(2) of a socket mapped
  to localhost to find out what the correct address is for the comparison
  test with the upcall's from IP address.
  This patch also adds INET6 support and the required #ifdef's for INET and
  INET6. It now uses INET6 by default for the upcalls, if the kernel has
  INET6 support and the daemon is also built with INET6 support.
  
  Tested by:	freebsd@danielengel.com (earlier version)
  PR:		205193
  Reviewed by:	bz, rgrimes
  MFC after:	2 weeks
  Differential Revision:	https://reviews.freebsd.org/D19218

Modified:
  head/usr.sbin/nfsuserd/Makefile
  head/usr.sbin/nfsuserd/nfsuserd.c

Modified: head/usr.sbin/nfsuserd/Makefile
==============================================================================
--- head/usr.sbin/nfsuserd/Makefile	Sat Apr  6 21:56:24 2019	(r345993)
+++ head/usr.sbin/nfsuserd/Makefile	Sat Apr  6 22:05:51 2019	(r345994)
@@ -1,7 +1,16 @@
 # $FreeBSD$
 
+.include <src.opts.mk>
+
 PROG=	nfsuserd
 MAN=	nfsuserd.8
 WARNS?=	3
+
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
 
 .include <bsd.prog.mk>

Modified: head/usr.sbin/nfsuserd/nfsuserd.c
==============================================================================
--- head/usr.sbin/nfsuserd/nfsuserd.c	Sat Apr  6 21:56:24 2019	(r345993)
+++ head/usr.sbin/nfsuserd/nfsuserd.c	Sat Apr  6 22:05:51 2019	(r345994)
@@ -40,6 +40,10 @@ __FBSDID("$FreeBSD$");
 #include <sys/vnode.h>
 #include <sys/wait.h>
 
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
 #include <nfs/nfssvc.h>
 
 #include <rpc/rpc.h>
@@ -72,6 +76,7 @@ static void	nfsuserdsrv(struct svc_req *, SVCXPRT *);
 static bool_t	xdr_getid(XDR *, caddr_t);
 static bool_t	xdr_getname(XDR *, caddr_t);
 static bool_t	xdr_retval(XDR *, caddr_t);
+static int	nfsbind_localhost(void);
 
 #define	MAXNAME		1024
 #define	MAXNFSUSERD	20
@@ -94,6 +99,10 @@ gid_t defaultgid = 65533;
 int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0;
 int defusertimeout = DEFUSERTIMEOUT, manage_gids = 0;
 pid_t slaves[MAXNFSUSERD];
+static struct sockaddr_storage fromip;
+#ifdef INET6
+static struct in6_addr in6loopback = IN6ADDR_LOOPBACK_INIT;
+#endif
 
 int
 main(int argc, char *argv[])
@@ -105,13 +114,20 @@ main(int argc, char *argv[])
 	struct group *grp;
 	int sock, one = 1;
 	SVCXPRT *udptransp;
-	u_short portnum;
+	struct nfsuserd_args nargs;
 	sigset_t signew;
 	char hostname[MAXHOSTNAMELEN + 1], *cp;
 	struct addrinfo *aip, hints;
 	static uid_t check_dups[MAXUSERMAX];
 	gid_t grps[NGROUPS];
 	int ngroup;
+#ifdef INET
+	struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+	struct sockaddr_in6 *sin6;
+#endif
+	int s;
 
 	if (modfind("nfscommon") < 0) {
 		/* Not present in kernel, try loading it */
@@ -144,6 +160,37 @@ main(int argc, char *argv[])
 			}
 		}
 	}
+
+	/*
+	 * See if this server handles IPv4 or IPv6 and set up the default
+	 * localhost address.
+	 */
+	s = -1;
+#ifdef INET6
+	s = socket(PF_INET6, SOCK_DGRAM, 0);
+	if (s >= 0) {
+		fromip.ss_family = AF_INET6;
+		fromip.ss_len = sizeof(struct sockaddr_in6);
+		sin6 = (struct sockaddr_in6 *)&fromip;
+		sin6->sin6_addr = in6loopback;
+		close(s);
+	}
+#endif	/* INET6 */
+#ifdef INET
+	if (s < 0) {
+		s = socket(PF_INET, SOCK_DGRAM, 0);
+		if (s >= 0) {
+			fromip.ss_family = AF_INET;
+			fromip.ss_len = sizeof(struct sockaddr_in);
+			sin = (struct sockaddr_in *)&fromip;
+			sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+			close(s);
+		}
+	}
+#endif	/* INET */
+	if (s < 0)
+		err(1, "Can't create a inet/inet6 socket");
+
 	nid.nid_usermax = DEFUSERMAX;
 	nid.nid_usertimeout = defusertimeout;
 
@@ -245,11 +292,12 @@ main(int argc, char *argv[])
 	for (i = 0; i < nfsuserdcnt; i++)
 		slaves[i] = (pid_t)-1;
 
+	nargs.nuserd_family = fromip.ss_family;
 	/*
 	 * Set up the service port to accept requests via UDP from
-	 * localhost (127.0.0.1).
+	 * localhost (INADDR_LOOPBACK or IN6ADDR_LOOPBACK_INIT).
 	 */
-	if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+	if ((sock = socket(nargs.nuserd_family, SOCK_DGRAM, IPPROTO_UDP)) < 0)
 		err(1, "cannot create udp socket");
 
 	/*
@@ -272,11 +320,11 @@ main(int argc, char *argv[])
 	/*
 	 * Tell the kernel what my port# is.
 	 */
-	portnum = htons(udptransp->xp_port);
+	nargs.nuserd_port = htons(udptransp->xp_port);
 #ifdef DEBUG
-	printf("portnum=0x%x\n", portnum);
+	printf("portnum=0x%x\n", nargs.nuserd_port);
 #else
-	if (nfssvc(NFSSVC_NFSUSERDPORT, (caddr_t)&portnum) < 0) {
+	if (nfssvc(NFSSVC_NFSUSERDPORT | NFSSVC_NEWSTRUCT, &nargs) < 0) {
 		if (errno == EPERM) {
 			fprintf(stderr,
 			    "Can't start nfsuserd when already running");
@@ -457,27 +505,92 @@ nfsuserdsrv(struct svc_req *rqstp, SVCXPRT *transp)
 	struct passwd *pwd;
 	struct group *grp;
 	int error;
+#if defined(INET) || defined(INET6)
 	u_short sport;
+	int ret;
+#endif
 	struct info info;
 	struct nfsd_idargs nid;
-	u_int32_t saddr;
 	gid_t grps[NGROUPS];
 	int ngroup;
+#ifdef INET
+	struct sockaddr_in *fromsin, *sin;
+#endif
+#ifdef INET6
+	struct sockaddr_in6 *fromsin6, *sin6;
+	char buf[INET6_ADDRSTRLEN];
+#endif
 
 	/*
-	 * Only handle requests from 127.0.0.1 on a reserved port number.
+	 * Only handle requests from localhost on a reserved port number.
+	 * If the upcall is from a different address, call nfsbind_localhost()
+	 * to check for a remapping of localhost, due to jails.
 	 * (Since a reserved port # at localhost implies a client with
 	 *  local root, there won't be a security breach. This is about
 	 *  the only case I can think of where a reserved port # means
 	 *  something.)
 	 */
-	sport = ntohs(transp->xp_raddr.sin_port);
-	saddr = ntohl(transp->xp_raddr.sin_addr.s_addr);
-	if ((rqstp->rq_proc != NULLPROC && sport >= IPPORT_RESERVED) ||
-	    saddr != 0x7f000001) {
-		syslog(LOG_ERR, "req from ip=0x%x port=%d\n", saddr, sport);
-		svcerr_weakauth(transp);
-		return;
+	if (rqstp->rq_proc != NULLPROC) {
+		switch (fromip.ss_family) {
+#ifdef INET
+		case AF_INET:
+			if (transp->xp_rtaddr.len < sizeof(*sin)) {
+				syslog(LOG_ERR, "xp_rtaddr too small");
+				svcerr_weakauth(transp);
+				return;
+			}
+			sin = (struct sockaddr_in *)transp->xp_rtaddr.buf;
+			fromsin = (struct sockaddr_in *)&fromip;
+			sport = ntohs(sin->sin_port);
+			if (sport >= IPPORT_RESERVED) {
+				syslog(LOG_ERR, "not a reserved port#");
+				svcerr_weakauth(transp);
+				return;
+			}
+			ret = 1;
+			if (sin->sin_addr.s_addr != fromsin->sin_addr.s_addr)
+				ret = nfsbind_localhost();
+			if (ret == 0 || sin->sin_addr.s_addr !=
+			    fromsin->sin_addr.s_addr) {
+				syslog(LOG_ERR, "bad from ip %s",
+				    inet_ntoa(sin->sin_addr));
+				svcerr_weakauth(transp);
+				return;
+			}
+			break;
+#endif	/* INET */
+#ifdef INET6
+		case AF_INET6:
+			if (transp->xp_rtaddr.len < sizeof(*sin6)) {
+				syslog(LOG_ERR, "xp_rtaddr too small");
+				svcerr_weakauth(transp);
+				return;
+			}
+			sin6 = (struct sockaddr_in6 *)transp->xp_rtaddr.buf;
+			fromsin6 = (struct sockaddr_in6 *)&fromip;
+			sport = ntohs(sin6->sin6_port);
+			if (sport >= IPV6PORT_RESERVED) {
+				syslog(LOG_ERR, "not a reserved port#");
+				svcerr_weakauth(transp);
+				return;
+			}
+			ret = 1;
+			if (!IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
+			    &fromsin6->sin6_addr))
+				ret = nfsbind_localhost();
+			if (ret == 0 || !IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
+			    &fromsin6->sin6_addr)) {
+				if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf,
+				    INET6_ADDRSTRLEN) != NULL)
+					syslog(LOG_ERR, "bad from ip %s", buf);
+				else
+					syslog(LOG_ERR, "bad from ip6 addr");
+				svcerr_weakauth(transp);
+				return;
+			}
+			break;
+#endif	/* INET6 */
+		}
 	}
 	switch (rqstp->rq_proc) {
 	case NULLPROC:
@@ -716,6 +829,67 @@ cleanup_term(int signo __unused)
 		exit(1);
 	}
 	exit(0);
+}
+
+/*
+ * Get the IP address that the localhost address maps to.
+ * This is needed when jails map localhost to another IP address.
+ */
+static int
+nfsbind_localhost(void)
+{
+#ifdef INET
+	struct sockaddr_in sin;
+#endif
+#ifdef INET6
+	struct sockaddr_in6 sin6;
+#endif
+	socklen_t slen;
+	int ret, s;
+
+	switch (fromip.ss_family) {
+#ifdef INET6
+	case AF_INET6:
+		s = socket(PF_INET6, SOCK_DGRAM, 0);
+		if (s < 0)
+			return (0);
+		memset(&sin6, 0, sizeof(sin6));
+		sin6.sin6_len = sizeof(sin6);
+		sin6.sin6_family = AF_INET6;
+		sin6.sin6_addr = in6loopback;
+		sin6.sin6_port = 0;
+		ret = bind(s, (struct sockaddr *)&sin6, sizeof(sin6));
+		if (ret < 0) {
+			close(s);
+			return (0);
+		}
+		break;
+#endif	/* INET6 */
+#ifdef INET
+	case AF_INET:
+		s = socket(PF_INET, SOCK_DGRAM, 0);
+		if (s < 0)
+			return (0);
+		memset(&sin, 0, sizeof(sin));
+		sin.sin_len = sizeof(sin);
+		sin.sin_family = AF_INET;
+		sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+		sin.sin_port = 0;
+		ret = bind(s, (struct sockaddr *)&sin, sizeof(sin));
+		if (ret < 0) {
+			close(s);
+			return (0);
+		}
+		break;
+#endif	/* INET */
+	}
+	memset(&fromip, 0, sizeof(fromip));
+	slen = sizeof(fromip);
+	ret = getsockname(s, (struct sockaddr *)&fromip, &slen);
+	close(s);
+	if (ret < 0)
+		return (0);
+	return (1);
 }
 
 static void





Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201904062205.x36M5pxA050022>