freebsd-nq/sys/compat/svr4/svr4_fcntl.c
Pawel Jakub Dawidek 7008be5bd7 Change the cap_rights_t type from uint64_t to a structure that we can extend
in the future in a backward compatible (API and ABI) way.

The cap_rights_t represents capability rights. We used to use one bit to
represent one right, but we are running out of spare bits. Currently the new
structure provides place for 114 rights (so 50 more than the previous
cap_rights_t), but it is possible to grow the structure to hold at least 285
rights, although we can make it even larger if 285 rights won't be enough.

The structure definition looks like this:

	struct cap_rights {
		uint64_t	cr_rights[CAP_RIGHTS_VERSION + 2];
	};

The initial CAP_RIGHTS_VERSION is 0.

The top two bits in the first element of the cr_rights[] array contain total
number of elements in the array - 2. This means if those two bits are equal to
0, we have 2 array elements.

The top two bits in all remaining array elements should be 0.
The next five bits in all array elements contain array index. Only one bit is
used and bit position in this five-bits range defines array index. This means
there can be at most five array elements in the future.

To define new right the CAPRIGHT() macro must be used. The macro takes two
arguments - an array index and a bit to set, eg.

	#define	CAP_PDKILL	CAPRIGHT(1, 0x0000000000000800ULL)

We still support aliases that combine few rights, but the rights have to belong
to the same array element, eg:

	#define	CAP_LOOKUP	CAPRIGHT(0, 0x0000000000000400ULL)
	#define	CAP_FCHMOD	CAPRIGHT(0, 0x0000000000002000ULL)

	#define	CAP_FCHMODAT	(CAP_FCHMOD | CAP_LOOKUP)

There is new API to manage the new cap_rights_t structure:

	cap_rights_t *cap_rights_init(cap_rights_t *rights, ...);
	void cap_rights_set(cap_rights_t *rights, ...);
	void cap_rights_clear(cap_rights_t *rights, ...);
	bool cap_rights_is_set(const cap_rights_t *rights, ...);

	bool cap_rights_is_valid(const cap_rights_t *rights);
	void cap_rights_merge(cap_rights_t *dst, const cap_rights_t *src);
	void cap_rights_remove(cap_rights_t *dst, const cap_rights_t *src);
	bool cap_rights_contains(const cap_rights_t *big, const cap_rights_t *little);

Capability rights to the cap_rights_init(), cap_rights_set(),
cap_rights_clear() and cap_rights_is_set() functions are provided by
separating them with commas, eg:

	cap_rights_t rights;

	cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FSTAT);

There is no need to terminate the list of rights, as those functions are
actually macros that take care of the termination, eg:

	#define	cap_rights_set(rights, ...)				\
		__cap_rights_set((rights), __VA_ARGS__, 0ULL)
	void __cap_rights_set(cap_rights_t *rights, ...);

Thanks to using one bit as an array index we can assert in those functions that
there are no two rights belonging to different array elements provided
together. For example this is illegal and will be detected, because CAP_LOOKUP
belongs to element 0 and CAP_PDKILL to element 1:

	cap_rights_init(&rights, CAP_LOOKUP | CAP_PDKILL);

Providing several rights that belongs to the same array's element this way is
correct, but is not advised. It should only be used for aliases definition.

This commit also breaks compatibility with some existing Capsicum system calls,
but I see no other way to do that. This should be fine as Capsicum is still
experimental and this change is not going to 9.x.

Sponsored by:	The FreeBSD Foundation
2013-09-05 00:09:56 +00:00

731 lines
15 KiB
C

/*-
* Copyright (c) 1998 Mark Newton
* Copyright (c) 1994, 1997 Christos Zoulas.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Christos Zoulas.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/capability.h>
#include <sys/systm.h>
#include <sys/file.h>
#include <sys/filedesc.h>
/*#include <sys/ioctl.h>*/
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/namei.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/stat.h>
#include <sys/syscallsubr.h>
#include <sys/unistd.h>
#include <sys/vnode.h>
#include <sys/sysproto.h>
#include <compat/svr4/svr4.h>
#include <compat/svr4/svr4_types.h>
#include <compat/svr4/svr4_signal.h>
#include <compat/svr4/svr4_proto.h>
#include <compat/svr4/svr4_util.h>
#include <compat/svr4/svr4_fcntl.h>
#include <security/mac/mac_framework.h>
static int svr4_to_bsd_flags(int);
static u_long svr4_to_bsd_cmd(u_long);
static int fd_revoke(struct thread *, int);
static int fd_truncate(struct thread *, int, struct flock *);
static int bsd_to_svr4_flags(int);
static void bsd_to_svr4_flock(struct flock *, struct svr4_flock *);
static void svr4_to_bsd_flock(struct svr4_flock *, struct flock *);
static void bsd_to_svr4_flock64(struct flock *, struct svr4_flock64 *);
static void svr4_to_bsd_flock64(struct svr4_flock64 *, struct flock *);
static u_long
svr4_to_bsd_cmd(cmd)
u_long cmd;
{
switch (cmd) {
case SVR4_F_DUPFD:
return F_DUPFD;
case SVR4_F_DUP2FD:
return F_DUP2FD;
case SVR4_F_GETFD:
return F_GETFD;
case SVR4_F_SETFD:
return F_SETFD;
case SVR4_F_GETFL:
return F_GETFL;
case SVR4_F_SETFL:
return F_SETFL;
case SVR4_F_GETLK:
return F_GETLK;
case SVR4_F_SETLK:
return F_SETLK;
case SVR4_F_SETLKW:
return F_SETLKW;
default:
return -1;
}
}
static int
svr4_to_bsd_flags(l)
int l;
{
int r = 0;
r |= (l & SVR4_O_RDONLY) ? O_RDONLY : 0;
r |= (l & SVR4_O_WRONLY) ? O_WRONLY : 0;
r |= (l & SVR4_O_RDWR) ? O_RDWR : 0;
r |= (l & SVR4_O_NDELAY) ? O_NONBLOCK : 0;
r |= (l & SVR4_O_APPEND) ? O_APPEND : 0;
r |= (l & SVR4_O_SYNC) ? O_FSYNC : 0;
r |= (l & SVR4_O_NONBLOCK) ? O_NONBLOCK : 0;
r |= (l & SVR4_O_PRIV) ? O_EXLOCK : 0;
r |= (l & SVR4_O_CREAT) ? O_CREAT : 0;
r |= (l & SVR4_O_TRUNC) ? O_TRUNC : 0;
r |= (l & SVR4_O_EXCL) ? O_EXCL : 0;
r |= (l & SVR4_O_NOCTTY) ? O_NOCTTY : 0;
return r;
}
static int
bsd_to_svr4_flags(l)
int l;
{
int r = 0;
r |= (l & O_RDONLY) ? SVR4_O_RDONLY : 0;
r |= (l & O_WRONLY) ? SVR4_O_WRONLY : 0;
r |= (l & O_RDWR) ? SVR4_O_RDWR : 0;
r |= (l & O_NDELAY) ? SVR4_O_NONBLOCK : 0;
r |= (l & O_APPEND) ? SVR4_O_APPEND : 0;
r |= (l & O_FSYNC) ? SVR4_O_SYNC : 0;
r |= (l & O_NONBLOCK) ? SVR4_O_NONBLOCK : 0;
r |= (l & O_EXLOCK) ? SVR4_O_PRIV : 0;
r |= (l & O_CREAT) ? SVR4_O_CREAT : 0;
r |= (l & O_TRUNC) ? SVR4_O_TRUNC : 0;
r |= (l & O_EXCL) ? SVR4_O_EXCL : 0;
r |= (l & O_NOCTTY) ? SVR4_O_NOCTTY : 0;
return r;
}
static void
bsd_to_svr4_flock(iflp, oflp)
struct flock *iflp;
struct svr4_flock *oflp;
{
switch (iflp->l_type) {
case F_RDLCK:
oflp->l_type = SVR4_F_RDLCK;
break;
case F_WRLCK:
oflp->l_type = SVR4_F_WRLCK;
break;
case F_UNLCK:
oflp->l_type = SVR4_F_UNLCK;
break;
default:
oflp->l_type = -1;
break;
}
oflp->l_whence = (short) iflp->l_whence;
oflp->l_start = (svr4_off_t) iflp->l_start;
oflp->l_len = (svr4_off_t) iflp->l_len;
oflp->l_sysid = 0;
oflp->l_pid = (svr4_pid_t) iflp->l_pid;
}
static void
svr4_to_bsd_flock(iflp, oflp)
struct svr4_flock *iflp;
struct flock *oflp;
{
switch (iflp->l_type) {
case SVR4_F_RDLCK:
oflp->l_type = F_RDLCK;
break;
case SVR4_F_WRLCK:
oflp->l_type = F_WRLCK;
break;
case SVR4_F_UNLCK:
oflp->l_type = F_UNLCK;
break;
default:
oflp->l_type = -1;
break;
}
oflp->l_whence = iflp->l_whence;
oflp->l_start = (off_t) iflp->l_start;
oflp->l_len = (off_t) iflp->l_len;
oflp->l_pid = (pid_t) iflp->l_pid;
oflp->l_sysid = iflp->l_sysid;
}
static void
bsd_to_svr4_flock64(iflp, oflp)
struct flock *iflp;
struct svr4_flock64 *oflp;
{
switch (iflp->l_type) {
case F_RDLCK:
oflp->l_type = SVR4_F_RDLCK;
break;
case F_WRLCK:
oflp->l_type = SVR4_F_WRLCK;
break;
case F_UNLCK:
oflp->l_type = SVR4_F_UNLCK;
break;
default:
oflp->l_type = -1;
break;
}
oflp->l_whence = (short) iflp->l_whence;
oflp->l_start = (svr4_off64_t) iflp->l_start;
oflp->l_len = (svr4_off64_t) iflp->l_len;
oflp->l_sysid = iflp->l_sysid;
oflp->l_pid = (svr4_pid_t) iflp->l_pid;
}
static void
svr4_to_bsd_flock64(iflp, oflp)
struct svr4_flock64 *iflp;
struct flock *oflp;
{
switch (iflp->l_type) {
case SVR4_F_RDLCK:
oflp->l_type = F_RDLCK;
break;
case SVR4_F_WRLCK:
oflp->l_type = F_WRLCK;
break;
case SVR4_F_UNLCK:
oflp->l_type = F_UNLCK;
break;
default:
oflp->l_type = -1;
break;
}
oflp->l_whence = iflp->l_whence;
oflp->l_start = (off_t) iflp->l_start;
oflp->l_len = (off_t) iflp->l_len;
oflp->l_pid = (pid_t) iflp->l_pid;
}
static int
fd_revoke(td, fd)
struct thread *td;
int fd;
{
struct vnode *vp;
struct mount *mp;
struct vattr vattr;
cap_rights_t rights;
int error, *retval;
retval = td->td_retval;
/*
* If we ever want to support Capsicum on SVR4 processes (unlikely)
* or FreeBSD grows a native frevoke() (more likely), we will need a
* CAP_FREVOKE here.
*
* In the meantime, use CAP_ALL(): if a SVR4 process wants to
* do an frevoke(), it needs to do it on either a regular file
* descriptor or a fully-privileged capability (which is effectively
* the same as a non-capability-restricted file descriptor).
*/
CAP_ALL(&rights);
if ((error = fgetvp(td, fd, &rights, &vp)) != 0)
return (error);
if (vp->v_type != VCHR && vp->v_type != VBLK) {
error = EINVAL;
goto out;
}
#ifdef MAC
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
error = mac_vnode_check_revoke(td->td_ucred, vp);
VOP_UNLOCK(vp, 0);
if (error)
goto out;
#endif
if ((error = VOP_GETATTR(vp, &vattr, td->td_ucred)) != 0)
goto out;
if (td->td_ucred->cr_uid != vattr.va_uid &&
(error = priv_check(td, PRIV_VFS_ADMIN)) != 0)
goto out;
if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0)
goto out;
if (vcount(vp) > 1)
VOP_REVOKE(vp, REVOKEALL);
vn_finished_write(mp);
out:
vrele(vp);
return error;
}
static int
fd_truncate(td, fd, flp)
struct thread *td;
int fd;
struct flock *flp;
{
off_t start, length;
struct file *fp;
struct vnode *vp;
struct vattr vattr;
int error, *retval;
struct ftruncate_args ft;
cap_rights_t rights;
retval = td->td_retval;
/*
* We only support truncating the file.
*/
error = fget(td, fd, cap_rights_init(&rights, CAP_FTRUNCATE), &fp);
if (error != 0)
return (error);
vp = fp->f_vnode;
if (fp->f_type != DTYPE_VNODE || vp->v_type == VFIFO) {
fdrop(fp, td);
return ESPIPE;
}
if ((error = VOP_GETATTR(vp, &vattr, td->td_ucred)) != 0) {
fdrop(fp, td);
return error;
}
length = vattr.va_size;
switch (flp->l_whence) {
case SEEK_CUR:
start = fp->f_offset + flp->l_start;
break;
case SEEK_END:
start = flp->l_start + length;
break;
case SEEK_SET:
start = flp->l_start;
break;
default:
fdrop(fp, td);
return EINVAL;
}
if (start + flp->l_len < length) {
/* We don't support free'ing in the middle of the file */
fdrop(fp, td);
return EINVAL;
}
ft.fd = fd;
ft.length = start;
error = sys_ftruncate(td, &ft);
fdrop(fp, td);
return (error);
}
int
svr4_sys_open(td, uap)
struct thread *td;
struct svr4_sys_open_args *uap;
{
struct proc *p = td->td_proc;
char *newpath;
int bsd_flags, error, retval;
CHECKALTEXIST(td, uap->path, &newpath);
bsd_flags = svr4_to_bsd_flags(uap->flags);
error = kern_open(td, newpath, UIO_SYSSPACE, bsd_flags, uap->mode);
free(newpath, M_TEMP);
if (error) {
/* uprintf("svr4_open(%s, 0x%0x, 0%o): %d\n", uap->path,
uap->flags, uap->mode, error);*/
return error;
}
retval = td->td_retval[0];
PROC_LOCK(p);
if (!(bsd_flags & O_NOCTTY) && SESS_LEADER(p) &&
!(p->p_flag & P_CONTROLT)) {
#if defined(NOTYET)
cap_rights_t rights;
struct file *fp;
error = fget(td, retval,
cap_rights_init(&rights, CAP_IOCTL), &fp);
PROC_UNLOCK(p);
/*
* we may have lost a race the above open() and
* another thread issuing a close()
*/
if (error)
return (EBADF); /* XXX: correct errno? */
/* ignore any error, just give it a try */
if (fp->f_type == DTYPE_VNODE)
fo_ioctl(fp, TIOCSCTTY, (caddr_t) 0, td->td_ucred,
td);
fdrop(fp, td);
} else {
PROC_UNLOCK(p);
}
#else
}
PROC_UNLOCK(p);
#endif
return error;
}
int
svr4_sys_open64(td, uap)
struct thread *td;
struct svr4_sys_open64_args *uap;
{
return svr4_sys_open(td, (struct svr4_sys_open_args *)uap);
}
int
svr4_sys_creat(td, uap)
struct thread *td;
struct svr4_sys_creat_args *uap;
{
char *newpath;
int error;
CHECKALTEXIST(td, uap->path, &newpath);
error = kern_open(td, newpath, UIO_SYSSPACE, O_WRONLY | O_CREAT |
O_TRUNC, uap->mode);
free(newpath, M_TEMP);
return (error);
}
int
svr4_sys_creat64(td, uap)
struct thread *td;
struct svr4_sys_creat64_args *uap;
{
return svr4_sys_creat(td, (struct svr4_sys_creat_args *)uap);
}
int
svr4_sys_llseek(td, uap)
struct thread *td;
struct svr4_sys_llseek_args *uap;
{
struct lseek_args ap;
ap.fd = uap->fd;
#if BYTE_ORDER == BIG_ENDIAN
ap.offset = (((u_int64_t) uap->offset1) << 32) |
uap->offset2;
#else
ap.offset = (((u_int64_t) uap->offset2) << 32) |
uap->offset1;
#endif
ap.whence = uap->whence;
return sys_lseek(td, &ap);
}
int
svr4_sys_access(td, uap)
struct thread *td;
struct svr4_sys_access_args *uap;
{
char *newpath;
int error;
CHECKALTEXIST(td, uap->path, &newpath);
error = kern_access(td, newpath, UIO_SYSSPACE, uap->amode);
free(newpath, M_TEMP);
return (error);
}
#if defined(NOTYET)
int
svr4_sys_pread(td, uap)
struct thread *td;
struct svr4_sys_pread_args *uap;
{
struct pread_args pra;
/*
* Just translate the args structure and call the NetBSD
* pread(2) system call (offset type is 64-bit in NetBSD).
*/
pra.fd = uap->fd;
pra.buf = uap->buf;
pra.nbyte = uap->nbyte;
pra.offset = uap->off;
return pread(td, &pra);
}
#endif
#if defined(NOTYET)
int
svr4_sys_pread64(td, v, retval)
struct thread *td;
void *v;
register_t *retval;
{
struct svr4_sys_pread64_args *uap = v;
struct sys_pread_args pra;
/*
* Just translate the args structure and call the NetBSD
* pread(2) system call (offset type is 64-bit in NetBSD).
*/
pra.fd = uap->fd;
pra.buf = uap->buf;
pra.nbyte = uap->nbyte;
pra.offset = uap->off;
return (sys_pread(td, &pra, retval));
}
#endif /* NOTYET */
#if defined(NOTYET)
int
svr4_sys_pwrite(td, uap)
struct thread *td;
struct svr4_sys_pwrite_args *uap;
{
struct pwrite_args pwa;
/*
* Just translate the args structure and call the NetBSD
* pwrite(2) system call (offset type is 64-bit in NetBSD).
*/
pwa.fd = uap->fd;
pwa.buf = uap->buf;
pwa.nbyte = uap->nbyte;
pwa.offset = uap->off;
return pwrite(td, &pwa);
}
#endif
#if defined(NOTYET)
int
svr4_sys_pwrite64(td, v, retval)
struct thread *td;
void *v;
register_t *retval;
{
struct svr4_sys_pwrite64_args *uap = v;
struct sys_pwrite_args pwa;
/*
* Just translate the args structure and call the NetBSD
* pwrite(2) system call (offset type is 64-bit in NetBSD).
*/
pwa.fd = uap->fd;
pwa.buf = uap->buf;
pwa.nbyte = uap->nbyte;
pwa.offset = uap->off;
return (sys_pwrite(td, &pwa, retval));
}
#endif /* NOTYET */
int
svr4_sys_fcntl(td, uap)
struct thread *td;
struct svr4_sys_fcntl_args *uap;
{
int cmd, error, *retval;
retval = td->td_retval;
cmd = svr4_to_bsd_cmd(uap->cmd);
switch (cmd) {
case F_DUPFD:
case F_DUP2FD:
case F_GETFD:
case F_SETFD:
return (kern_fcntl(td, uap->fd, cmd, (intptr_t)uap->arg));
case F_GETFL:
error = kern_fcntl(td, uap->fd, cmd, (intptr_t)uap->arg);
if (error)
return (error);
*retval = bsd_to_svr4_flags(*retval);
return (error);
case F_SETFL:
{
/*
* we must save the O_ASYNC flag, as that is
* handled by ioctl(_, I_SETSIG, _) emulation.
*/
int flags;
DPRINTF(("Setting flags %p\n", uap->arg));
error = kern_fcntl(td, uap->fd, F_GETFL, 0);
if (error)
return (error);
flags = *retval;
flags &= O_ASYNC;
flags |= svr4_to_bsd_flags((u_long) uap->arg);
return (kern_fcntl(td, uap->fd, F_SETFL, flags));
}
case F_GETLK:
case F_SETLK:
case F_SETLKW:
{
struct svr4_flock ifl;
struct flock fl;
error = copyin(uap->arg, &ifl, sizeof (ifl));
if (error)
return (error);
svr4_to_bsd_flock(&ifl, &fl);
error = kern_fcntl(td, uap->fd, cmd, (intptr_t)&fl);
if (error || cmd != F_GETLK)
return (error);
bsd_to_svr4_flock(&fl, &ifl);
return (copyout(&ifl, uap->arg, sizeof (ifl)));
}
case -1:
switch (uap->cmd) {
case SVR4_F_FREESP:
{
struct svr4_flock ifl;
struct flock fl;
error = copyin(uap->arg, &ifl,
sizeof ifl);
if (error)
return error;
svr4_to_bsd_flock(&ifl, &fl);
return fd_truncate(td, uap->fd, &fl);
}
case SVR4_F_GETLK64:
case SVR4_F_SETLK64:
case SVR4_F_SETLKW64:
{
struct svr4_flock64 ifl;
struct flock fl;
switch (uap->cmd) {
case SVR4_F_GETLK64:
cmd = F_GETLK;
break;
case SVR4_F_SETLK64:
cmd = F_SETLK;
break;
case SVR4_F_SETLKW64:
cmd = F_SETLKW;
break;
}
error = copyin(uap->arg, &ifl,
sizeof (ifl));
if (error)
return (error);
svr4_to_bsd_flock64(&ifl, &fl);
error = kern_fcntl(td, uap->fd, cmd,
(intptr_t)&fl);
if (error || cmd != F_GETLK)
return (error);
bsd_to_svr4_flock64(&fl, &ifl);
return (copyout(&ifl, uap->arg,
sizeof (ifl)));
}
case SVR4_F_FREESP64:
{
struct svr4_flock64 ifl;
struct flock fl;
error = copyin(uap->arg, &ifl,
sizeof ifl);
if (error)
return error;
svr4_to_bsd_flock64(&ifl, &fl);
return fd_truncate(td, uap->fd, &fl);
}
case SVR4_F_REVOKE:
return fd_revoke(td, uap->fd);
default:
return ENOSYS;
}
default:
return ENOSYS;
}
}