Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 31 Oct 2006 15:04:30 +0000 (GMT)
From:      Robert Watson <rwatson@FreeBSD.org>
To:        Pawel Jakub Dawidek <pjd@FreeBSD.org>
Cc:        arch@FreeBSD.org
Subject:   Re: New in-kernel privilege API: priv(9)
Message-ID:  <20061031144237.B87421@fledge.watson.org>
In-Reply-To: <20061031144050.GC13359@garage.freebsd.pl>
References:  <20061031092122.D96078@fledge.watson.org> <20061031144050.GC13359@garage.freebsd.pl>

next in thread | previous in thread | raw e-mail | index | archive | help

On Tue, 31 Oct 2006, Pawel Jakub Dawidek wrote:

> Here are few nits I found:

Thanks!  Comments below.

>> Index: sys/fs/hpfs/hpfs_vnops.c
>> ===================================================================
>> RCS file: /zoo/cvsup/FreeBSD-CVS/src/sys/fs/hpfs/hpfs_vnops.c,v
>> retrieving revision 1.68
>> diff -u -r1.68 hpfs_vnops.c
>> --- sys/fs/hpfs/hpfs_vnops.c	17 Jan 2006 17:29:01 -0000	1.68
>> +++ sys/fs/hpfs/hpfs_vnops.c	30 Oct 2006 17:07:55 -0000
>> @@ -501,11 +501,12 @@
>>  	if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) {
>>  		if (vp->v_mount->mnt_flag & MNT_RDONLY)
>>  			return (EROFS);
>> -		if (cred->cr_uid != hp->h_uid &&
>> -		    (error = suser_cred(cred, SUSER_ALLOWJAIL)) &&
>> -		    ((vap->va_vaflags & VA_UTIMES_NULL) == 0 ||
>> -		    (error = VOP_ACCESS(vp, VWRITE, cred, td))))
>> -			return (error);
>> +		if (vap->va_vaflags & VA_UTIMES_NULL) {
>> +			error = VOP_ACCESS(vp, VADMIN, cred, td);
>> +			if (error)
>> +				error = VOP_ACCESS(vp, VWRITE, cred, td);
>> +		} else
>> +			error = VOP_ACCESS(vp, VADMIN, cred, td);
>
> Eliminating privilege check here was intentional? Doesn't it change 
> functionality? I found the same check in few other places, so it's probably 
> intentional, but worth checking still.

Yeah, it took my quite a while to puzzle through what the security check here 
is supposed to accomplish, but once I did, the correct structure became more 
clear.  The key here is that VOP_ACCESS() will also perform a privilege check, 
so the use of privilege in the existing code appears to be redundant.  The 
logic here should essentially read:

- To update the time stamp to the current time (VA_UTIMES_NULL), you must
   either be the owner or have write access.  The correct error value is
   EACCES, so we try the write check only if the owner check fails, so that the
   error from the write check overrides the owner result.

- To update the time stamp to any other time, you must be the owner.  The
   error here is EPERM on failure.

Given this description, do you think the change is right?

>> --- sys/fs/msdosfs/msdosfs_vfsops.c	26 Sep 2006 04:12:45 -0000	1.153
>> +++ sys/fs/msdosfs/msdosfs_vfsops.c	30 Oct 2006 17:07:55 -0000
> [...]
>> @@ -293,17 +294,17 @@
>>  			 * If upgrade to read-write by non-root, then verify
>>  			 * that user has necessary permissions on the device.
>>  			 */
>> -			if (suser(td)) {
>> -				devvp = pmp->pm_devvp;
>> -				vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>> -				error = VOP_ACCESS(devvp, VREAD | VWRITE,
>> -						   td->td_ucred, td);
>> -				if (error) {
>> -					VOP_UNLOCK(devvp, 0, td);
>> -					return (error);
>> -				}
>> +			devvp = pmp->pm_devvp;
>> +			vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>> +			error = VOP_ACCESS(devvp, VREAD | VWRITE,
>> +			   td->td_ucred, td);
>> +			if (error)
>> +				error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>> +			if (error) {
>>  				VOP_UNLOCK(devvp, 0, td);
>> +				return (error);
>>  			}
>> +			VOP_UNLOCK(devvp, 0, td);
>
> This doesn't seem right to me. If VOP_ACCESS() returns an error if call 
> priv_check(), I think you wanted to call it when it return success, no?
>
> I'd change it to:
>
> 			devvp = pmp->pm_devvp;
> 			vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
> 			error = VOP_ACCESS(devvp, VREAD | VWRITE,
> 			   td->td_ucred, td);
> 			VOP_UNLOCK(devvp, 0, td);
> 			if (!error)
> 				error = priv_check(td, PRIV_VFS_MOUNT_PERM);
> 			if (error)
> 				return (error);

Again, this is a tricky case in the original code.  I believe the intended 
logic here, in English, is:

- In order to mount on a device vnode, you must be able to read and write to
   it.  This is subject to the normal definition of read and write privilege,
   so the superuser can override on that basis (as part of VOP_ACCESS).

- However, if you don't have read and write access, you can also use privilege
   (PRIV_VFS_MOUNT_PERM) to override that.

This is expressed in the original code by first checking for privilege, and if 
that fails, then looking for read/write access.  This doesn't make a lot of 
sense because the read/write check is also exempted by privilege, but was 
probably "a little faster" because suser() was cheaper than VOP_ACCESS().

The reason we call priv_check() in the error case is that we only want to 
check for privilege if the normal access control check fails, in which case we 
override the normal access control error check with the result of the 
privilege check.

Given this description, does the new code still make sense?

>> @@ -353,15 +354,15 @@
>>  	 * If mount by non-root, then verify that user has necessary
>>  	 * permissions on the device.
>>  	 */
>> -	if (suser(td)) {
>> -		accessmode = VREAD;
>> -		if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> -			accessmode |= VWRITE;
>> -		error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td);
>> -		if (error) {
>> -			vput(devvp);
>> -			return (error);
>> -		}
>> +	accessmode = VREAD;
>> +	if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> +		accessmode |= VWRITE;
>> +	error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td);
>> +	if (error)
>> +		error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>> +	if (error) {
>> +		vput(devvp);
>> +		return (error);
>
> Same here.

Ditto, although the underlying DAC logic here is a little different.  In this 
file system, we only require write access if it's a non-readonbly mount.  If 
VOP_ACCESS() fails, we fall back on privilege.

>> --- sys/fs/umapfs/umap_vfsops.c	26 Sep 2006 04:12:46 -0000	1.65
>> +++ sys/fs/umapfs/umap_vfsops.c	30 Oct 2006 17:07:55 -0000
>> @@ -88,8 +88,9 @@
>>  	/*
>>  	 * Only for root
>>  	 */
>> -	if ((error = suser(td)) != 0)
>> -		return (error);
>> +	error = priv_check(td, PRIV_VFS_MOUNT);
>> +	if (error)
>> +		return (ERROR);
>
> s/ERROR/error/

Err.  That's odd.  That should have been caught in build tests, maybe an edit 
error.  I will fix, thanks!

>
>> --- sys/gnu/fs/ext2fs/ext2_vfsops.c	26 Sep 2006 04:12:47 -0000	1.158
>> +++ sys/gnu/fs/ext2fs/ext2_vfsops.c	30 Oct 2006 17:07:55 -0000
> [...]
>> @@ -197,15 +198,16 @@
>>  			 * If upgrade to read-write by non-root, then verify
>>  			 * that user has necessary permissions on the device.
>>  			 */
>> -			if (suser(td)) {
>> -				vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>> -				if ((error = VOP_ACCESS(devvp, VREAD | VWRITE,
>> -				    td->td_ucred, td)) != 0) {
>> -					VOP_UNLOCK(devvp, 0, td);
>> -					return (error);
>> -				}
>> +			vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>> +			error = VOP_ACCESS(devvp, VREAD | VWRITE,
>> +			    td->td_ucred, td);
>> +			if (error)
>> +				error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>
> Another s/if (error)/if (!error)/

I think this is right -- we want to do the privilege check in the event the 
normal check fails, since we're granting extra rights, rather than mask the 
whole thing by result.  Notice that this is not the privilege to mount, it's 
the privilege to override device node protections.

>> @@ -259,15 +261,18 @@
>>  	/*
>>  	 * If mount by non-root, then verify that user has necessary
>>  	 * permissions on the device.
>> +	 *
>> +	 * XXXRW: VOP_ACCESS() enough?
>>  	 */
>> -	if (suser(td)) {
>> -		accessmode = VREAD;
>> -		if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> -			accessmode |= VWRITE;
>> -		if ((error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td)) != 0) {
>> -			vput(devvp);
>> -			return (error);
>> -		}
>> +	accessmode = VREAD;
>> +	if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> +		accessmode |= VWRITE;
>> +	error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td);
>> +	if (error)
>> +		error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>
> And again.

Think this one is right also.  If I'm thinking about this the wrong way, you 
should tell me :-)

>
>> --- sys/gnu/fs/reiserfs/reiserfs_vfsops.c	26 Sep 2006 04:12:47 -0000	1.6
>> +++ sys/gnu/fs/reiserfs/reiserfs_vfsops.c	30 Oct 2006 17:07:55 -0000
>> @@ -125,15 +125,15 @@
>>
>>  	/* If mount by non-root, then verify that user has necessary
>>  	 * permissions on the device. */
>> -	if (suser(td)) {
>> -		accessmode = VREAD;
>> -		if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> -			accessmode |= VWRITE;
>> -		if ((error = VOP_ACCESS(devvp,
>> -		    accessmode, td->td_ucred, td)) != 0) {
>> -			vput(devvp);
>> -			return (error);
>> -		}
>> +	accessmode = VREAD;
>> +	if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> +		accessmode |= VWRITE;
>> +	error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td);
>> +	if (error)
>> +		error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>
> One more.
>
>> --- sys/gnu/fs/xfs/FreeBSD/xfs_super.c	10 Jun 2006 19:02:13 -0000	1.4
>> +++ sys/gnu/fs/xfs/FreeBSD/xfs_super.c	30 Oct 2006 17:07:55 -0000
> [...]
>> @@ -149,14 +151,15 @@
>>  	vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>>
>>  	ronly = ((XFS_MTOVFS(mp)->vfs_flag & VFS_RDONLY) != 0);
>> -	if (suser(td)) {
>> -		accessmode = VREAD;
>> -		if (!ronly)
>> -			accessmode |= VWRITE;
>> -		if ((error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td))!= 0){
>> -			vput(devvp);
>> -			return (error);
>> -		}
>> +	accessmode = VREAD;
>> +	if (!ronly)
>> +		accessmode |= VWRITE;
>> +	error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td);
>> +	if (error)
>> +		error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>
> Another one.
>
>> --- sys/kern/kern_jail.c	22 Oct 2006 11:52:13 -0000	1.53
>> +++ sys/kern/kern_jail.c	30 Oct 2006 17:07:55 -0000
> [...]
>> @@ -523,6 +524,172 @@
>>  	}
>>  }
>>
>> +/*
>> + * Check with permission for a specific privilege is granted within jail.  We
>> + * have a specific list of accepted privileges; the rest are denied.
>> + */
>> +int
>> +prison_priv_check(struct ucred *cred, int priv)
>> +{
>> +
>> +	if (!(jailed(cred)))
>> +		return (0);
>
> Style: if (!jailed(cred))

Fixed, thanks!

>> --- sys/netatm/atm_usrreq.c	21 Jul 2006 17:11:13 -0000	1.27
>> +++ sys/netatm/atm_usrreq.c	30 Oct 2006 17:07:55 -0000
>> -		if (td && (suser(td) != 0))
>> -			ATM_RETERR(EPERM);
>> +		if (td != 0) {
>
> Style: s/0/NULL/

Also now fixed.

>> --- sys/netinet6/udp6_usrreq.c	7 Sep 2006 18:44:54 -0000	1.68
>> +++ sys/netinet6/udp6_usrreq.c	30 Oct 2006 17:07:56 -0000
> [...]
>> @@ -434,7 +435,8 @@
>>  	struct inpcb *inp;
>>  	int error;
>>
>> -	error = suser(req->td);
>> +	error = priv_check_cred(req->td->td_ucred, PRIV_NETINET_GETCRED,
>> +	    SUSER_ALLOWJAIL);
>
> Change from not allowing jailed root to allowing jailed root was
> intentional?

Yes -- I believe that we already perform any necessary checks as part of the 
socket visibility check later on in the function.  I'm not sure duplicating it 
is entirely bad, but I think it's probably better this way, especially if we 
plan to support IPv6 in jail in the future.

>> --- sys/ufs/ffs/ffs_vfsops.c	22 Oct 2006 11:52:19 -0000	1.321
>> +++ sys/ufs/ffs/ffs_vfsops.c	30 Oct 2006 17:07:56 -0000
> [...]
>> @@ -257,15 +258,16 @@
>>  			 * If upgrade to read-write by non-root, then verify
>>  			 * that user has necessary permissions on the device.
>>  			 */
>> -			if (suser(td)) {
>> -				vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>> -				if ((error = VOP_ACCESS(devvp, VREAD | VWRITE,
>> -				    td->td_ucred, td)) != 0) {
>> -					VOP_UNLOCK(devvp, 0, td);
>> -					return (error);
>> -				}
>> +			vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, td);
>> +			error = VOP_ACCESS(devvp, VREAD | VWRITE,
>> +			    td->td_ucred, td);
>> +			if (error)
>> +				error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>
> s/if (error)/if (!error)/

Another case of my thinking this is correct.  Notice that the logic originally 
there is on the same lines: only if the superuser check fails do we do the 
discretionary check.  Now we always do the discretionary check, and sometimes 
do the superuser check, but the idea is that either succeeding is sufficient 
to allo wit to pass.

>> @@ -364,14 +366,15 @@
>>  	 * If mount by non-root, then verify that user has necessary
>>  	 * permissions on the device.
>>  	 */
>> -	if (suser(td)) {
>> -		accessmode = VREAD;
>> -		if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> -			accessmode |= VWRITE;
>> -		if ((error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td))!= 0){
>> -			vput(devvp);
>> -			return (error);
>> -		}
>> +	accessmode = VREAD;
>> +	if ((mp->mnt_flag & MNT_RDONLY) == 0)
>> +		accessmode |= VWRITE;
>> +	error = VOP_ACCESS(devvp, accessmode, td->td_ucred, td);
>> +	if (error)
>> +		error = priv_check(td, PRIV_VFS_MOUNT_PERM);
>
> s/if (error)/if (!error)/

Ditto.

If you disagree with my reasoning above, do let me know -- I scratched my head 
in a number of places in the file system code trying to decide the intent of 
the checks, and changed the current expression in order to provide a more 
logical expression of the intent in order to change the privilege behavior.

Thanks!

Robert N M Watson
Computer Laboratory
University of Cambridge



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