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
This commit is contained in:
Rick Macklem 2019-04-06 22:05:51 +00:00
parent d1139b5286
commit ce78460aeb
2 changed files with 198 additions and 15 deletions

View File

@ -1,7 +1,16 @@
# $FreeBSD$ # $FreeBSD$
.include <src.opts.mk>
PROG= nfsuserd PROG= nfsuserd
MAN= nfsuserd.8 MAN= nfsuserd.8
WARNS?= 3 WARNS?= 3
.if ${MK_INET_SUPPORT} != "no"
CFLAGS+= -DINET
.endif
.if ${MK_INET6_SUPPORT} != "no"
CFLAGS+= -DINET6
.endif
.include <bsd.prog.mk> .include <bsd.prog.mk>

View File

@ -40,6 +40,10 @@ __FBSDID("$FreeBSD$");
#include <sys/vnode.h> #include <sys/vnode.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <nfs/nfssvc.h> #include <nfs/nfssvc.h>
#include <rpc/rpc.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_getid(XDR *, caddr_t);
static bool_t xdr_getname(XDR *, caddr_t); static bool_t xdr_getname(XDR *, caddr_t);
static bool_t xdr_retval(XDR *, caddr_t); static bool_t xdr_retval(XDR *, caddr_t);
static int nfsbind_localhost(void);
#define MAXNAME 1024 #define MAXNAME 1024
#define MAXNFSUSERD 20 #define MAXNFSUSERD 20
@ -94,6 +99,10 @@ gid_t defaultgid = 65533;
int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0; int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0;
int defusertimeout = DEFUSERTIMEOUT, manage_gids = 0; int defusertimeout = DEFUSERTIMEOUT, manage_gids = 0;
pid_t slaves[MAXNFSUSERD]; pid_t slaves[MAXNFSUSERD];
static struct sockaddr_storage fromip;
#ifdef INET6
static struct in6_addr in6loopback = IN6ADDR_LOOPBACK_INIT;
#endif
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
@ -105,13 +114,20 @@ main(int argc, char *argv[])
struct group *grp; struct group *grp;
int sock, one = 1; int sock, one = 1;
SVCXPRT *udptransp; SVCXPRT *udptransp;
u_short portnum; struct nfsuserd_args nargs;
sigset_t signew; sigset_t signew;
char hostname[MAXHOSTNAMELEN + 1], *cp; char hostname[MAXHOSTNAMELEN + 1], *cp;
struct addrinfo *aip, hints; struct addrinfo *aip, hints;
static uid_t check_dups[MAXUSERMAX]; static uid_t check_dups[MAXUSERMAX];
gid_t grps[NGROUPS]; gid_t grps[NGROUPS];
int ngroup; int ngroup;
#ifdef INET
struct sockaddr_in *sin;
#endif
#ifdef INET6
struct sockaddr_in6 *sin6;
#endif
int s;
if (modfind("nfscommon") < 0) { if (modfind("nfscommon") < 0) {
/* Not present in kernel, try loading it */ /* 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_usermax = DEFUSERMAX;
nid.nid_usertimeout = defusertimeout; nid.nid_usertimeout = defusertimeout;
@ -245,11 +292,12 @@ main(int argc, char *argv[])
for (i = 0; i < nfsuserdcnt; i++) for (i = 0; i < nfsuserdcnt; i++)
slaves[i] = (pid_t)-1; slaves[i] = (pid_t)-1;
nargs.nuserd_family = fromip.ss_family;
/* /*
* Set up the service port to accept requests via UDP from * 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"); err(1, "cannot create udp socket");
/* /*
@ -272,11 +320,11 @@ main(int argc, char *argv[])
/* /*
* Tell the kernel what my port# is. * Tell the kernel what my port# is.
*/ */
portnum = htons(udptransp->xp_port); nargs.nuserd_port = htons(udptransp->xp_port);
#ifdef DEBUG #ifdef DEBUG
printf("portnum=0x%x\n", portnum); printf("portnum=0x%x\n", nargs.nuserd_port);
#else #else
if (nfssvc(NFSSVC_NFSUSERDPORT, (caddr_t)&portnum) < 0) { if (nfssvc(NFSSVC_NFSUSERDPORT | NFSSVC_NEWSTRUCT, &nargs) < 0) {
if (errno == EPERM) { if (errno == EPERM) {
fprintf(stderr, fprintf(stderr,
"Can't start nfsuserd when already running"); "Can't start nfsuserd when already running");
@ -457,27 +505,92 @@ nfsuserdsrv(struct svc_req *rqstp, SVCXPRT *transp)
struct passwd *pwd; struct passwd *pwd;
struct group *grp; struct group *grp;
int error; int error;
#if defined(INET) || defined(INET6)
u_short sport; u_short sport;
int ret;
#endif
struct info info; struct info info;
struct nfsd_idargs nid; struct nfsd_idargs nid;
u_int32_t saddr;
gid_t grps[NGROUPS]; gid_t grps[NGROUPS];
int ngroup; 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 * (Since a reserved port # at localhost implies a client with
* local root, there won't be a security breach. This is about * local root, there won't be a security breach. This is about
* the only case I can think of where a reserved port # means * the only case I can think of where a reserved port # means
* something.) * something.)
*/ */
sport = ntohs(transp->xp_raddr.sin_port); if (rqstp->rq_proc != NULLPROC) {
saddr = ntohl(transp->xp_raddr.sin_addr.s_addr); switch (fromip.ss_family) {
if ((rqstp->rq_proc != NULLPROC && sport >= IPPORT_RESERVED) || #ifdef INET
saddr != 0x7f000001) { case AF_INET:
syslog(LOG_ERR, "req from ip=0x%x port=%d\n", saddr, sport); if (transp->xp_rtaddr.len < sizeof(*sin)) {
svcerr_weakauth(transp); syslog(LOG_ERR, "xp_rtaddr too small");
return; 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) { switch (rqstp->rq_proc) {
case NULLPROC: case NULLPROC:
@ -718,6 +831,67 @@ cleanup_term(int signo __unused)
exit(0); 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 static void
usage(void) usage(void)
{ {