Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 17 May 2011 13:36:43 +0400
From:      Sergey Kandaurov <pluknet@gmail.com>
To:        Rick Macklem <rmacklem@uoguelph.ca>
Cc:        freebsd-fs@freebsd.org
Subject:   [old nfsclient] different nmount() args passed from mount vs. mount_nfs
Message-ID:  <BANLkTi=g=Y8=Ma=N1Ma_9-D0nP%2B673ay9Q@mail.gmail.com>

next in thread | raw e-mail | index | archive | help
Hi.

First, sorry for the long mail. I just tried to describe in full details.

When mounting nfs with some options, I found that /sbin/mount and
/sbin/mount_nfs pass options to nmount() differently, which results
in bad things (TM). I traced the options and here they are:

>From mount(8) -> mount_nfs(8):
"rw"       -> ""
"addr"     -> {something valid }
"fh"       -> 5
"sec"      -> "sys"
"nfsv3"    -> 0x0 => NFSMNT_NFSV3
"hostname" -> "dev2.mail:/home/svn/freebsd/head"
"fstype"   -> "oldnfs"
"fspath"   -> "/usr/src"
"errmsg"   -> ""
(nil)

>From pre-r221124 mount(8):
= "fstype"   -> "oldnfs"
"hostname" -> "dev2.mail"
= "fspath"   -> "/usr/src"
"from"     -> "dev2.mail:/home/svn/freebsd/head"
= "errmsg"   -> ""
(nil)

Note, that pre-r221124 mount(8) knows nothing about oldnfs.

1. "hostname" option is passed differently from mount(8) and mount_nfs(8).
When I force to mount oldnfs file system with mount(8) directly (to not
bypass the nmount(2) call to mount_nfs(8)), I get this error:
./mount -t oldnfs dev2.mail:/home/svn/freebsd/head /usr/src
mount: dev2.mail:/home/svn/freebsd/head Invalid hostname: Invalid argument

Hmm.. this may be because mount(8) passes value in $hostname:$path format
(see the traces above). It might be due to different old nfsclient way to parse
args, but I am not sure, I can be wrong. Anyway, it does not matter now.

The actual problem manifests when running the command with pre-r221124
mount(8) binary. It knows nothing about "oldnfs" and (attention!)
calls nmount(2)
directly instead of bypassing the call to the mount_nfs(8) binary as
usually done,
and this is the place where the "unsanitized nmount(2) args" problem is hidden.
[New mount knows about "oldnfs" and passes the call to mount_oldnfs(8) that
prepares all the nmount(2) args to correctly hide the problem.]

To prove it, that is how old and new mount(8) work differently:
1) new mount(8) as of current
mount -d -t oldnfs dev2.mail:/home/svn/freebsd/head /usr/src
exec: mount_oldnfs dev2.mail:/home/svn/freebsd/head /usr/src
2) old mount(8) as of pre-r221124
./mount -d -t oldnfs dev2.mail:/home/svn/freebsd/head /usr/src
mount -t oldnfs dev2.mail:/home/svn/freebsd/head /usr/src


Ok, back to the first paragraph: a different "hostname" mount option.
When I first faced with this, I tried to specify value for "hostname"
explicitly. Here it comes:
./mount -t oldnfs -o hostname=dev2.mail
dev2.mail:/home/svn/freebsd/head /usr/src
[CABOOM!]
It just crashed. Do not do this :)

Fatal trap 12: page fault while in kernel mode
cpuid = 0; apic id = 00
fault virtual address   = 0x1
fault code              = supervisor read data, page not present
instruction pointer     = 0x20:0xffffffff805da299
stack pointer           = 0x28:0xffffff807bef6240
frame pointer           = 0x28:0xffffff807bef62a0
code segment            = base 0x0, limit 0xfffff, type 0x1b
                        = DPL 0, pres 1, long 1, def32 0, gran 1
processor eflags        = interrupt enabled, resume, IOPL = 0
current process         = 2541 (mount)
db> bt
Tracing pid 2541 tid 100076 td 0xfffffe0001ace460
nfs_connect() at 0xffffffff805da299 = nfs_connect+0x79
nfs_request() at 0xffffffff805da978 = nfs_request+0x398
nfs_getattr() at 0xffffffff805e2a6c = nfs_getattr+0x2bc
VOP_GETATTR_APV() at 0xffffffff806f4283 = VOP_GETATTR_APV+0xd3
mountnfs() at 0xffffffff805de739 = mountnfs+0x329
nfs_mount() at 0xffffffff805dffc7 = nfs_mount+0xcf7
vfs_donmount() at 0xffffffff804d46ff = vfs_donmount+0x82f
nmount() at 0xffffffff804d54f3 = nmount+0x63
syscallenter() at 0xffffffff804861cb = syscallenter+0x1cb
syscall() at 0xffffffff806ae710 = syscall+0x60
Xfast_syscall() at 0xffffffff8069922d = Xfast_syscall+0xdd
--- syscall (378, FreeBSD ELF64, nmount), rip = 0x800ab444c, rsp =
0x7fffffffca48, rbp = 0x801009058 ---


As you might see from above nmount(2) args traces, mount(8) itself doesn't
pass the "addr" option to the nmount(2) syscall while nfs_mount() expects to
receive it, which is the problem.
Later deep in nmount(2) in /sys/nfsclient/nfs_krpc.c it tries to dereference
addr value and page faults here in nfs_connect() :

                vers = NFS_VER3;
        else if (nmp->nm_flag & NFSMNT_NFSV4)
                vers = NFS_VER4;
XXX saddr is NULL, the next line will crash
        if (saddr->sa_family == AF_INET)
                if (nmp->nm_sotype == SOCK_DGRAM)
                        nconf = getnetconfigent("udp");

I think that nfsclient, probably in sys/nfsclient/nfs_vfsops.c:mount_nfs(),
should handle a missing value for "addr" and/or "fh" mount options.
It doesn't check it currently:

% static int
% nfs_mount(struct mount *mp)
% {
%         struct nfs_args args = {
% [...]
%             .addr = NULL,
%         };
%        int error, ret, has_nfs_args_opt;
%        int has_addr_opt, has_fh_opt, has_hostname_opt;
%        struct sockaddr *nam;

addr is initialized with NULL. num used later as a pointer to args.addr value.

%        if ((mp->mnt_flag & (MNT_ROOTFS | MNT_UPDATE)) == MNT_ROOTFS) {
%                error = nfs_mountroot(mp);
%                goto out;
%        }

We do not try to mount root, this is not ours.

%        if (vfs_getopt(mp->mnt_optnew, "nfs_args", NULL, NULL) == 0) {
[...]
%                has_nfs_args_opt = 1;
%        }

We do not use old mount(2) interface, not ours.

%        if (vfs_getopt(mp->mnt_optnew, "nfsv3", NULL, NULL) == 0)
%                args.flags |= NFSMNT_NFSV3;

mount(8) doesn't pass nfsv3 option, so NFSMNT_NFSV3 isn't set.

%        if (vfs_getopt(mp->mnt_optnew, "addr", (void **)&args.addr,
%                &args.addrlen) == 0) {
%                has_addr_opt = 1;
%                if (args.addrlen > SOCK_MAXADDRLEN) {
%                        error = ENAMETOOLONG;
%                        goto out;
%                }
%                nam = malloc(args.addrlen, M_SONAME,
%                    M_WAITOK);
%                bcopy(args.addr, nam, args.addrlen);
%                nam->sa_len = args.addrlen;
%        }

mount(8) doesn't pass addr option, so args.addr isn't set, hence
struct sockaddr *nam is also NULL, has_addr_opt is 0.

%        if (vfs_getopt(mp->mnt_optnew, "hostname", (void **)&args.hostname,
%                NULL) == 0) {
%                has_hostname_opt = 1;
%        }
%        if (args.hostname == NULL) {
%                vfs_mount_error(mp, "Invalid hostname");
%                error = EINVAL;
%                goto out;
%        }

I don't know why I got here the error. I didn't analyze it deep though.
"mount: dev2.mail:/home/svn/freebsd/head Invalid hostname: Invalid argument"

%        if (mp->mnt_flag & MNT_UPDATE) {
[...]

That's not update case, it's not ours.

%        if (has_nfs_args_opt) {

has_nfs_args_opt is 0, as we don't use legacy mount(2) interface, see above.
So, the whole block is ignored. Though, see below.

%                /*
%                 * In the 'nfs_args' case, the pointers in the args
%                 * structure are in userland - we copy them in here.
%                 */
%                if (!has_fh_opt) {
%                        error = copyin((caddr_t)args.fh, (caddr_t)nfh,
%                            args.fhsize);
%                        if (error) {
%                                goto out;
%                        }
%                        args.fh = nfh;
%                }

has_fh_opt is 0, as mount(8) didn't pass "fh" to nmount(2),
though this part is not executed anyway.

%                if (!has_hostname_opt) {
%                        error = copyinstr(args.hostname, hst, MNAMELEN-1, &len)
%                        if (error) {
%                                goto out;
%                        }
%                        bzero(&hst[len], MNAMELEN - len);
%                        args.hostname = hst;

has_hostname_opt is 1, as mount(8) passes "hostname" to nmount(2),
though this part is not executed anyway.

%                }
%                if (!has_addr_opt) {
%                        /* sockargs() call must be after above copyin() calls *
%                        printf("args.addr: %p\n", args.addr);
%                        error = getsockaddr(&nam, (caddr_t)args.addr,
%                            args.addrlen);
%                        printf("error: %d\n", error);
%                        if (error) {
%                                goto out;
%                        }
%                }

has_addr_opt is 0, as mount(8) didn't pass "addr" to nmount(2),
though this part is not executed anyway.

%        }
%        error = mountnfs(&args, mp, nam, args.hostname, &vp,
%            curthread->td_ucred, negnametimeo);

mountnfs() is called with nam == NULL, then it crashes deep in
/sys/nfsclient/nfs_krpc.c:nfs_connect().


Also compare ddb backtrace with one from new mount(8)
which bypasses the call to mount_nfs(8). I got it by adding
kdb_enter() just before NULL pointer dereference.

db> bt
Tracing pid 2143 tid 100117 td 0xfffffe0001c58000
kdb_enter() at 0xffffffff80477d1b = kdb_enter+0x3b
nfs_connect() at 0xffffffff805da7e8 = nfs_connect+0x88
nfs_request() at 0xffffffff805daec8 = nfs_request+0x398
nfs_fsinfo() at 0xffffffff805ddec0 = nfs_fsinfo+0xd0
mountnfs() at 0xffffffff805ded44 = mountnfs+0x3e4
nfs_mount() at 0xffffffff805e051f = nfs_mount+0xcff
vfs_donmount() at 0xffffffff804d5092 = vfs_donmount+0xc92
nmount() at 0xffffffff804d5a33 = nmount+0x63
syscallenter() at 0xffffffff804866eb = syscallenter+0x1cb
syscall() at 0xffffffff806aec90 = syscall+0x60
Xfast_syscall() at 0xffffffff806997ad = Xfast_syscall+0xdd
--- syscall (378, FreeBSD ELF64, nmount), rip = 0x8008a544c, rsp =
0x7fffffffd258, rbp = 0x7fffffffd30c ---


Two backtraces different slightly because of NFSMNT_NFSV3 is not set
in the old mount(8) case. From sys/nfsclient/nfs_vfsops.c:mountnfs()

        if (argp->flags & NFSMNT_NFSV3)
                nfs_fsinfo(nmp, *vpp, curthread->td_ucred, curthread);
        else
                VOP_GETATTR(*vpp, &attrs, curthread->td_ucred);


-- 
wbr,
pluknet



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?BANLkTi=g=Y8=Ma=N1Ma_9-D0nP%2B673ay9Q>