view usr/src/uts/common/fs/nfs/nfs_acl_vnops.c @ 13985:ad441dd34478

Back out hg changeset 829c00a55a37, bug 2986 -- introduces vulnerability Reviewed by: Richard Lowe <richlowe@richlowe.net> Reviewed by: Christopher Siden <christopher.siden@delphix.com> Approved by: Dan McDonald <danmcd@nexenta.com>
author Marcel Telka <marcel.telka@nexenta.com>
date Thu, 14 Mar 2013 17:58:04 -0400
parents 7e2aa87c24de
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/buf.h>
#include <sys/mman.h>
#include <sys/tiuser.h>
#include <sys/pathname.h>
#include <sys/dirent.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/unistd.h>
#include <sys/vmsystm.h>
#include <sys/fcntl.h>
#include <sys/flock.h>
#include <sys/swap.h>
#include <sys/errno.h>
#include <sys/sysmacros.h>
#include <sys/disp.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/vtrace.h>
#include <sys/pathconf.h>
#include <sys/dnlc.h>
#include <sys/acl.h>

#include <rpc/types.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <rpc/xdr.h>
#include <nfs/nfs.h>
#include <nfs/nfs_clnt.h>
#include <nfs/rnode.h>
#include <nfs/nfs_acl.h>

#include <vm/hat.h>
#include <vm/as.h>
#include <vm/page.h>
#include <vm/pvn.h>
#include <vm/seg.h>
#include <vm/seg_map.h>
#include <vm/seg_kmem.h>
#include <vm/seg_vn.h>
#include <vm/rm.h>

#include <fs/fs_subr.h>

/*
 * The order and contents of this structure must be kept in sync with that of
 * aclreqcnt_v2_tmpl in nfs_stats.c
 */
char *aclnames_v2[] = {
	"null", "getacl", "setacl", "getattr", "access", "getxattrdir"
};

/*
 * This table maps from NFS protocol number into call type.
 * Zero means a "Lookup" type call
 * One  means a "Read" type call
 * Two  means a "Write" type call
 * This is used to select a default time-out.
 */
uchar_t acl_call_type_v2[] = {
	0, 0, 1, 0, 0, 0
};

/*
 * Similar table, but to determine which timer to use
 * (only real reads and writes!)
 */
uchar_t acl_timer_type_v2[] = {
	0, 0, 0, 0, 0, 0
};

/*
 * This table maps from acl operation into a call type
 * for the semisoft mount option.
 * Zero means do not repeat operation.
 * One  means repeat.
 */
uchar_t acl_ss_call_type_v2[] = {
	0, 0, 1, 0, 0, 0
};

static int nfs_acl_dup_cache(vsecattr_t *, vsecattr_t *);
static void nfs_acl_dup_res(rnode_t *, vsecattr_t *);

/* ARGSUSED */
int
acl_getacl2(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr)
{
	int error;
	GETACL2args args;
	GETACL2res res;
	int doqueue;
	vattr_t va;
	rnode_t *rp;
	failinfo_t fi;
	hrtime_t t;

	rp = VTOR(vp);
	if (rp->r_secattr != NULL) {
		error = nfs_validate_caches(vp, cr);
		if (error)
			return (error);
		mutex_enter(&rp->r_statelock);
		if (rp->r_secattr != NULL) {
			if (nfs_acl_dup_cache(vsp, rp->r_secattr)) {
				mutex_exit(&rp->r_statelock);
				return (0);
			}
		}
		mutex_exit(&rp->r_statelock);
	}

	args.mask = vsp->vsa_mask;
	args.fh = *VTOFH(vp);
	fi.vp = vp;
	fi.fhp = (caddr_t)&args.fh;
	fi.copyproc = nfscopyfh;
	fi.lookupproc = nfslookup;
	fi.xattrdirproc = acl_getxattrdir2;

	res.resok.acl.vsa_aclentp = NULL;
	res.resok.acl.vsa_dfaclentp = NULL;

	doqueue = 1;

	t = gethrtime();

	error = acl2call(VTOMI(vp), ACLPROC2_GETACL,
	    xdr_GETACL2args, (caddr_t)&args,
	    xdr_GETACL2res, (caddr_t)&res, cr,
	    &doqueue, &res.status, 0, &fi);

	if (error)
		return (error);

	error = geterrno(res.status);
	if (!error) {
		(void) nfs_cache_fattr(vp, &res.resok.attr, &va, t, cr);
		nfs_acl_dup_res(rp, &res.resok.acl);
		*vsp = res.resok.acl;
	} else {
		PURGE_STALE_FH(error, vp, cr);
	}

	return (error);
}

/* ARGSUSED */
int
acl_setacl2(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr)
{
	int error;
	SETACL2args args;
	SETACL2res res;
	int doqueue;
	vattr_t va;
	rnode_t *rp;
	hrtime_t t;

	args.fh = *VTOFH(vp);
	args.acl = *vsp;

	doqueue = 1;

	t = gethrtime();

	error = acl2call(VTOMI(vp), ACLPROC2_SETACL,
	    xdr_SETACL2args, (caddr_t)&args,
	    xdr_SETACL2res, (caddr_t)&res, cr,
	    &doqueue, &res.status, 0, NULL);

	/*
	 * On success, adding the arguments to setsecattr into the cache have
	 * not proven adequate.  On error, we cannot depend on cache.
	 * Simply flush the cache to force the next getsecattr
	 * to go over the wire.
	 */
	rp = VTOR(vp);
	mutex_enter(&rp->r_statelock);
	if (rp->r_secattr != NULL) {
		nfs_acl_free(rp->r_secattr);
		rp->r_secattr = NULL;
	}
	mutex_exit(&rp->r_statelock);

	if (error)
		return (error);

	error = geterrno(res.status);
	if (!error) {
		(void) nfs_cache_fattr(vp, &res.resok.attr, &va, t, cr);
	} else {
		PURGE_STALE_FH(error, vp, cr);
	}

	return (error);
}

int
acl_getattr2_otw(vnode_t *vp, vattr_t *vap, cred_t *cr)
{
	int error;
	GETATTR2args args;
	GETATTR2res res;
	int doqueue;
	failinfo_t fi;
	hrtime_t t;

	args.fh = *VTOFH(vp);
	fi.vp = vp;
	fi.fhp = (caddr_t)&args.fh;
	fi.copyproc = nfscopyfh;
	fi.lookupproc = nfslookup;
	fi.xattrdirproc = acl_getxattrdir2;

	doqueue = 1;

	t = gethrtime();

	error = acl2call(VTOMI(vp), ACLPROC2_GETATTR,
	    xdr_GETATTR2args, (caddr_t)&args,
	    xdr_GETATTR2res, (caddr_t)&res, cr,
	    &doqueue, &res.status, 0, &fi);

	if (error)
		return (error);
	error = geterrno(res.status);

	if (!error) {
		error = nfs_cache_fattr(vp, &res.resok.attr, vap, t, cr);
	} else {
		PURGE_STALE_FH(error, vp, cr);
	}

	return (error);
}

/* ARGSUSED */
int
acl_access2(vnode_t *vp, int mode, int flags, cred_t *cr)
{
	int error;
	ACCESS2args args;
	ACCESS2res res;
	int doqueue;
	uint32 acc;
	rnode_t *rp;
	cred_t *cred, *ncr, *ncrfree = NULL;
	vattr_t va;
	failinfo_t fi;
	nfs_access_type_t cacc;
	hrtime_t t;

	acc = 0;
	if (mode & VREAD)
		acc |= ACCESS2_READ;
	if (mode & VWRITE) {
		if (vn_is_readonly(vp) && !IS_DEVVP(vp))
			return (EROFS);
		if (vp->v_type == VDIR)
			acc |= ACCESS2_DELETE;
		acc |= ACCESS2_MODIFY | ACCESS2_EXTEND;
	}
	if (mode & VEXEC) {
		if (vp->v_type == VDIR)
			acc |= ACCESS2_LOOKUP;
		else
			acc |= ACCESS2_EXECUTE;
	}

	rp = VTOR(vp);
	if (vp->v_type == VDIR) {
		args.access = ACCESS2_READ | ACCESS2_DELETE | ACCESS2_MODIFY |
		    ACCESS2_EXTEND | ACCESS2_LOOKUP;
	} else {
		args.access = ACCESS2_READ | ACCESS2_MODIFY | ACCESS2_EXTEND |
		    ACCESS2_EXECUTE;
	}
	args.fh = *VTOFH(vp);
	fi.vp = vp;
	fi.fhp = (caddr_t)&args.fh;
	fi.copyproc = nfscopyfh;
	fi.lookupproc = nfslookup;
	fi.xattrdirproc = acl_getxattrdir2;

	cred = cr;
	/*
	 * ncr and ncrfree both initially
	 * point to the memory area returned
	 * by crnetadjust();
	 * ncrfree not NULL when exiting means
	 * that we need to release it
	 */
	ncr = crnetadjust(cred);
	ncrfree = ncr;

tryagain:
	if (rp->r_acache != NULL) {
		cacc = nfs_access_check(rp, acc, cr);
		if (cacc == NFS_ACCESS_ALLOWED) {
			if (ncrfree != NULL)
				crfree(ncrfree);
			return (0);
		}
		if (cacc == NFS_ACCESS_DENIED) {
			/*
			 * If the cred can be adjusted, try again
			 * with the new cred.
			 */
			if (ncr != NULL) {
				cred = ncr;
				ncr = NULL;
				goto tryagain;
			}
			if (ncrfree != NULL)
				crfree(ncrfree);
			return (EACCES);
		}
	}

	doqueue = 1;

	t = gethrtime();

	error = acl2call(VTOMI(vp), ACLPROC2_ACCESS,
	    xdr_ACCESS2args, (caddr_t)&args,
	    xdr_ACCESS2res, (caddr_t)&res, cred,
	    &doqueue, &res.status, 0, &fi);

	if (error) {
		if (ncrfree != NULL)
			crfree(ncrfree);
		return (error);
	}

	error = geterrno(res.status);
	if (!error) {
		(void) nfs_cache_fattr(vp, &res.resok.attr, &va, t, cr);
		nfs_access_cache(rp, args.access, res.resok.access, cred);
		/*
		 * we just cached results with cred; if cred is the
		 * adjusted credentials from crnetadjust, we do not want
		 * to release them before exiting: hence setting ncrfree
		 * to NULL
		 */
		if (cred != cr)
			ncrfree = NULL;
		if ((acc & res.resok.access) != acc) {
			/*
			 * If the cred can be adjusted, try again
			 * with the new cred.
			 */
			if (ncr != NULL) {
				cred = ncr;
				ncr = NULL;
				goto tryagain;
			}
			error = EACCES;
		}
	} else {
		PURGE_STALE_FH(error, vp, cr);
	}

	if (ncrfree != NULL)
		crfree(ncrfree);

	return (error);
}

static int xattr_lookup_neg_cache = 1;

/*
 * Look up a hidden attribute directory over the wire; the vnode
 * we start with could be a file or directory.  We have to be
 * tricky in recording the name in the rnode r_path - we use the
 * magic name XATTR_RPATH and rely on code in failover_lookup() to
 * detect this and use this routine to do the same lookup on
 * remapping.  DNLC is easier: slashes are legal, so we use
 * XATTR_DIR_NAME as UFS does.
 */
int
acl_getxattrdir2(vnode_t *vp, vnode_t **vpp, bool_t create, cred_t *cr,
	int rfscall_flags)
{
	int error;
	GETXATTRDIR2args args;
	GETXATTRDIR2res res;
	int doqueue;
	failinfo_t fi;
	hrtime_t t;

	args.fh = *VTOFH(vp);
	args.create = create;

	fi.vp = vp;
	fi.fhp = NULL;		/* no need to update, filehandle not copied */
	fi.copyproc = nfscopyfh;
	fi.lookupproc = nfslookup;
	fi.xattrdirproc = acl_getxattrdir2;

	doqueue = 1;

	t = gethrtime();

	error = acl2call(VTOMI(vp), ACLPROC2_GETXATTRDIR,
	    xdr_GETXATTRDIR2args, (caddr_t)&args,
	    xdr_GETXATTRDIR2res, (caddr_t)&res, cr,
	    &doqueue, &res.status, rfscall_flags, &fi);

	if (!error) {
		error = geterrno(res.status);
		if (!error) {
			*vpp = makenfsnode(&res.resok.fh, &res.resok.attr,
			    vp->v_vfsp, t, cr, VTOR(vp)->r_path, XATTR_RPATH);
			mutex_enter(&(*vpp)->v_lock);
			(*vpp)->v_flag |= V_XATTRDIR;
			mutex_exit(&(*vpp)->v_lock);
			if (!(rfscall_flags & RFSCALL_SOFT))
				dnlc_update(vp, XATTR_DIR_NAME, *vpp);
		} else {
			PURGE_STALE_FH(error, vp, cr);
			if (error == ENOENT && xattr_lookup_neg_cache)
				dnlc_enter(vp, XATTR_DIR_NAME, DNLC_NO_VNODE);
		}
	}
	return (error);
}

/*
 * The order and contents of this structure must be kept in sync with that of
 * aclreqcnt_v3_tmpl in nfs_stats.c
 */
char *aclnames_v3[] = {
	"null", "getacl", "setacl", "getxattrdir"
};

/*
 * This table maps from NFS protocol number into call type.
 * Zero means a "Lookup" type call
 * One  means a "Read" type call
 * Two  means a "Write" type call
 * This is used to select a default time-out.
 */
uchar_t acl_call_type_v3[] = {
	0, 0, 1, 0
};

/*
 * This table maps from acl operation into a call type
 * for the semisoft mount option.
 * Zero means do not repeat operation.
 * One  means repeat.
 */
uchar_t acl_ss_call_type_v3[] = {
	0, 0, 1, 0
};

/*
 * Similar table, but to determine which timer to use
 * (only real reads and writes!)
 */
uchar_t acl_timer_type_v3[] = {
	0, 0, 0, 0
};

/* ARGSUSED */
int
acl_getacl3(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr)
{
	int error;
	GETACL3args args;
	GETACL3res res;
	int doqueue;
	rnode_t *rp;
	failinfo_t fi;
	hrtime_t t;

	rp = VTOR(vp);
	if (rp->r_secattr != NULL) {
		error = nfs3_validate_caches(vp, cr);
		if (error)
			return (error);
		mutex_enter(&rp->r_statelock);
		if (rp->r_secattr != NULL) {
			if (nfs_acl_dup_cache(vsp, rp->r_secattr)) {
				mutex_exit(&rp->r_statelock);
				return (0);
			}
		}
		mutex_exit(&rp->r_statelock);
	}

	args.mask = vsp->vsa_mask;
	args.fh = *VTOFH3(vp);
	fi.vp = vp;
	fi.fhp = (caddr_t)&args.fh;
	fi.copyproc = nfs3copyfh;
	fi.lookupproc = nfs3lookup;
	fi.xattrdirproc = acl_getxattrdir3;

	res.resok.acl.vsa_aclentp = NULL;
	res.resok.acl.vsa_dfaclentp = NULL;

	doqueue = 1;

	t = gethrtime();

	error = acl3call(VTOMI(vp), ACLPROC3_GETACL,
	    xdr_GETACL3args, (caddr_t)&args,
	    xdr_GETACL3res, (caddr_t)&res, cr,
	    &doqueue, &res.status, 0, &fi);

	if (error)
		return (error);

	error = geterrno3(res.status);

	if (!error) {
		nfs3_cache_post_op_attr(vp, &res.resok.attr, t, cr);
		nfs_acl_dup_res(rp, &res.resok.acl);
		*vsp = res.resok.acl;
	} else {
		nfs3_cache_post_op_attr(vp, &res.resfail.attr, t, cr);
		PURGE_STALE_FH(error, vp, cr);
	}

	return (error);
}

/* ARGSUSED */
int
acl_setacl3(vnode_t *vp, vsecattr_t *vsp, int flag, cred_t *cr)
{
	int error;
	SETACL3args args;
	SETACL3res res;
	rnode_t *rp;
	int doqueue;
	hrtime_t t;

	args.fh = *VTOFH3(vp);
	args.acl = *vsp;

	doqueue = 1;

	t = gethrtime();

	error = acl3call(VTOMI(vp), ACLPROC3_SETACL,
	    xdr_SETACL3args, (caddr_t)&args,
	    xdr_SETACL3res, (caddr_t)&res, cr,
	    &doqueue, &res.status, 0, NULL);

	/*
	 * On success, adding the arguments to setsecattr into the cache have
	 * not proven adequate.  On error, we cannot depend on cache.
	 * Simply flush the cache to force the next getsecattr
	 * to go over the wire.
	 */
	rp = VTOR(vp);
	mutex_enter(&rp->r_statelock);
	if (rp->r_secattr != NULL) {
		nfs_acl_free(rp->r_secattr);
		rp->r_secattr = NULL;
	}
	mutex_exit(&rp->r_statelock);

	if (error)
		return (error);

	error = geterrno3(res.status);
	if (!error) {
		nfs3_cache_post_op_attr(vp, &res.resok.attr, t, cr);
	} else {
		nfs3_cache_post_op_attr(vp, &res.resfail.attr, t, cr);
		PURGE_STALE_FH(error, vp, cr);
	}

	return (error);
}

int
acl_getxattrdir3(vnode_t *vp, vnode_t **vpp, bool_t create, cred_t *cr,
	int rfscall_flags)
{
	int error;
	GETXATTRDIR3args args;
	GETXATTRDIR3res res;
	int doqueue;
	struct vattr vattr;
	vnode_t *nvp;
	failinfo_t fi;
	hrtime_t t;

	args.fh = *VTOFH3(vp);
	args.create = create;

	fi.vp = vp;
	fi.fhp = (caddr_t)&args.fh;
	fi.copyproc = nfs3copyfh;
	fi.lookupproc = nfs3lookup;
	fi.xattrdirproc = acl_getxattrdir3;

	doqueue = 1;

	t = gethrtime();

	error = acl3call(VTOMI(vp), ACLPROC3_GETXATTRDIR,
	    xdr_GETXATTRDIR3args, (caddr_t)&args,
	    xdr_GETXATTRDIR3res, (caddr_t)&res, cr,
	    &doqueue, &res.status, rfscall_flags, &fi);

	if (error)
		return (error);

	error = geterrno3(res.status);
	if (!error) {
		if (res.resok.attr.attributes) {
			nvp = makenfs3node(&res.resok.fh,
			    &res.resok.attr.attr,
			    vp->v_vfsp, t, cr, VTOR(vp)->r_path, XATTR_RPATH);
		} else {
			nvp = makenfs3node(&res.resok.fh, NULL,
			    vp->v_vfsp, t, cr, VTOR(vp)->r_path, XATTR_RPATH);
			if (nvp->v_type == VNON) {
				vattr.va_mask = AT_TYPE;
				error = nfs3getattr(nvp, &vattr, cr);
				if (error) {
					VN_RELE(nvp);
					return (error);
				}
				nvp->v_type = vattr.va_type;
			}
		}
		mutex_enter(&nvp->v_lock);
		nvp->v_flag |= V_XATTRDIR;
		mutex_exit(&nvp->v_lock);
		if (!(rfscall_flags & RFSCALL_SOFT))
			dnlc_update(vp, XATTR_DIR_NAME, nvp);
		*vpp = nvp;
	} else {
		PURGE_STALE_FH(error, vp, cr);
		if (error == ENOENT && xattr_lookup_neg_cache)
			dnlc_enter(vp, XATTR_DIR_NAME, DNLC_NO_VNODE);
	}

	return (error);
}

void
nfs_acl_free(vsecattr_t *vsp)
{

	if (vsp->vsa_aclentp != NULL) {
		kmem_free(vsp->vsa_aclentp, vsp->vsa_aclcnt *
		    sizeof (aclent_t));
	}
	if (vsp->vsa_dfaclentp != NULL) {
		kmem_free(vsp->vsa_dfaclentp, vsp->vsa_dfaclcnt *
		    sizeof (aclent_t));
	}
	kmem_free(vsp, sizeof (*vsp));
}

static int
nfs_acl_dup_cache(vsecattr_t *vsp, vsecattr_t *rvsp)
{
	size_t aclsize;

	if ((rvsp->vsa_mask & vsp->vsa_mask) != vsp->vsa_mask)
		return (0);

	if (vsp->vsa_mask & VSA_ACL) {
		ASSERT(rvsp->vsa_mask & VSA_ACLCNT);
		aclsize = rvsp->vsa_aclcnt * sizeof (aclent_t);
		vsp->vsa_aclentp = kmem_alloc(aclsize, KM_SLEEP);
		bcopy(rvsp->vsa_aclentp, vsp->vsa_aclentp, aclsize);
	}
	if (vsp->vsa_mask & VSA_ACLCNT)
		vsp->vsa_aclcnt = rvsp->vsa_aclcnt;
	if (vsp->vsa_mask & VSA_DFACL) {
		ASSERT(rvsp->vsa_mask & VSA_DFACLCNT);
		aclsize = rvsp->vsa_dfaclcnt * sizeof (aclent_t);
		vsp->vsa_dfaclentp = kmem_alloc(aclsize, KM_SLEEP);
		bcopy(rvsp->vsa_dfaclentp, vsp->vsa_dfaclentp, aclsize);
	}
	if (vsp->vsa_mask & VSA_DFACLCNT)
		vsp->vsa_dfaclcnt = rvsp->vsa_dfaclcnt;

	return (1);
}

static void
nfs_acl_dup_res_impl(kmutex_t *statelock, vsecattr_t **rspp, vsecattr_t *vsp)
{
	size_t aclsize;
	vsecattr_t *rvsp;

	mutex_enter(statelock);
	if (*rspp != NULL)
		rvsp = *rspp;
	else {
		rvsp = kmem_zalloc(sizeof (*rvsp), KM_NOSLEEP);
		if (rvsp == NULL) {
			mutex_exit(statelock);
			return;
		}
		*rspp = rvsp;
	}

	if (vsp->vsa_mask & VSA_ACL) {
		if (rvsp->vsa_aclentp != NULL &&
		    rvsp->vsa_aclcnt != vsp->vsa_aclcnt) {
			aclsize = rvsp->vsa_aclcnt * sizeof (aclent_t);
			kmem_free(rvsp->vsa_aclentp, aclsize);
			rvsp->vsa_aclentp = NULL;
		}
		if (vsp->vsa_aclcnt > 0) {
			aclsize = vsp->vsa_aclcnt * sizeof (aclent_t);
			if (rvsp->vsa_aclentp == NULL) {
				rvsp->vsa_aclentp = kmem_alloc(aclsize,
				    KM_SLEEP);
			}
			bcopy(vsp->vsa_aclentp, rvsp->vsa_aclentp, aclsize);
		}
		rvsp->vsa_aclcnt = vsp->vsa_aclcnt;
		rvsp->vsa_mask |= VSA_ACL | VSA_ACLCNT;
	}
	if (vsp->vsa_mask & VSA_ACLCNT) {
		if (rvsp->vsa_aclentp != NULL &&
		    rvsp->vsa_aclcnt != vsp->vsa_aclcnt) {
			aclsize = rvsp->vsa_aclcnt * sizeof (aclent_t);
			kmem_free(rvsp->vsa_aclentp, aclsize);
			rvsp->vsa_aclentp = NULL;
			rvsp->vsa_mask &= ~VSA_ACL;
		}
		rvsp->vsa_aclcnt = vsp->vsa_aclcnt;
		rvsp->vsa_mask |= VSA_ACLCNT;
	}
	if (vsp->vsa_mask & VSA_DFACL) {
		if (rvsp->vsa_dfaclentp != NULL &&
		    rvsp->vsa_dfaclcnt != vsp->vsa_dfaclcnt) {
			aclsize = rvsp->vsa_dfaclcnt * sizeof (aclent_t);
			kmem_free(rvsp->vsa_dfaclentp, aclsize);
			rvsp->vsa_dfaclentp = NULL;
		}
		if (vsp->vsa_dfaclcnt > 0) {
			aclsize = vsp->vsa_dfaclcnt * sizeof (aclent_t);
			if (rvsp->vsa_dfaclentp == NULL) {
				rvsp->vsa_dfaclentp = kmem_alloc(aclsize,
				    KM_SLEEP);
			}
			bcopy(vsp->vsa_dfaclentp, rvsp->vsa_dfaclentp, aclsize);
		}
		rvsp->vsa_dfaclcnt = vsp->vsa_dfaclcnt;
		rvsp->vsa_mask |= VSA_DFACL | VSA_DFACLCNT;
	}
	if (vsp->vsa_mask & VSA_DFACLCNT) {
		if (rvsp->vsa_dfaclentp != NULL &&
		    rvsp->vsa_dfaclcnt != vsp->vsa_dfaclcnt) {
			aclsize = rvsp->vsa_dfaclcnt * sizeof (aclent_t);
			kmem_free(rvsp->vsa_dfaclentp, aclsize);
			rvsp->vsa_dfaclentp = NULL;
			rvsp->vsa_mask &= ~VSA_DFACL;
		}
		rvsp->vsa_dfaclcnt = vsp->vsa_dfaclcnt;
		rvsp->vsa_mask |= VSA_DFACLCNT;
	}
	mutex_exit(statelock);
}

static void
nfs_acl_dup_res(rnode_t *rp, vsecattr_t *vsp)
{
	nfs_acl_dup_res_impl(&rp->r_statelock, &rp->r_secattr, vsp);
}