freebsd-dev/sys/miscfs/kernfs/kernfs_vnops.c
Robert Watson 012c643d3e o Restructure vaccess() so as to check for DAC permission to modify the
object before falling back on privilege.  Make vaccess() accept an
  additional optional argument, privused, to determine whether
  privilege was required for vaccess() to return 0.  Add commented
  out capability checks for reference.  Rename some variables to make
  it more clear which modes/uids/etc are associated with the object,
  and which with the access mode.
o Update file system use of vaccess() to pass NULL as the optional
  privused argument.  Once additional patches are applied, suser()
  will no longer set ASU, so privused will permit passing of
  privilege information up the stack to the caller.

Reviewed by:	bde, green, phk, -security, others
Obtained from:	TrustedBSD Project
2000-08-29 14:45:49 +00:00

611 lines
14 KiB
C

/*
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software donated to Berkeley by
* Jan-Simon Pendry.
*
* 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 the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
*
* @(#)kernfs_vnops.c 8.15 (Berkeley) 5/21/95
* $FreeBSD$
*/
/*
* Kernel parameter filesystem (/kern)
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/vmmeter.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/dirent.h>
#include <sys/resource.h>
#include <miscfs/kernfs/kernfs.h>
#define KSTRING 256 /* Largest I/O available via this filesystem */
#define UIO_MX 32
#define READ_MODE (S_IRUSR|S_IRGRP|S_IROTH)
#define WRITE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
#define DIR_MODE (S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
static struct kern_target {
u_char kt_type;
u_char kt_namlen;
char *kt_name;
void *kt_data;
#define KTT_NULL 1
#define KTT_TIME 5
#define KTT_INT 17
#define KTT_STRING 31
#define KTT_HOSTNAME 47
#define KTT_BOOTFILE 49
#define KTT_AVENRUN 53
u_char kt_tag;
u_char kt_vtype;
mode_t kt_mode;
} kern_targets[] = {
/* NOTE: The name must be less than UIO_MX-16 chars in length */
#define N(s) sizeof(s)-1, s
/* name data tag type ro/rw */
{ DT_DIR, N("."), 0, KTT_NULL, VDIR, DIR_MODE },
{ DT_DIR, N(".."), 0, KTT_NULL, VDIR, DIR_MODE },
{ DT_REG, N("boottime"), &boottime.tv_sec, KTT_INT, VREG, READ_MODE },
{ DT_REG, N("copyright"), copyright, KTT_STRING, VREG, READ_MODE },
{ DT_REG, N("hostname"), 0, KTT_HOSTNAME, VREG, WRITE_MODE },
{ DT_REG, N("bootfile"), 0, KTT_BOOTFILE, VREG, READ_MODE },
{ DT_REG, N("hz"), &hz, KTT_INT, VREG, READ_MODE },
{ DT_REG, N("loadavg"), 0, KTT_AVENRUN, VREG, READ_MODE },
{ DT_REG, N("pagesize"), &cnt.v_page_size, KTT_INT, VREG, READ_MODE },
{ DT_REG, N("physmem"), &physmem, KTT_INT, VREG, READ_MODE },
{ DT_REG, N("time"), 0, KTT_TIME, VREG, READ_MODE },
{ DT_REG, N("version"), version, KTT_STRING, VREG, READ_MODE },
#undef N
};
static int nkern_targets = sizeof(kern_targets) / sizeof(kern_targets[0]);
static int kernfs_access __P((struct vop_access_args *ap));
static int kernfs_badop __P((void));
static int kernfs_getattr __P((struct vop_getattr_args *ap));
static int kernfs_inactive __P((struct vop_inactive_args *ap));
static int kernfs_lookup __P((struct vop_lookup_args *ap));
static int kernfs_print __P((struct vop_print_args *ap));
static int kernfs_read __P((struct vop_read_args *ap));
static int kernfs_readdir __P((struct vop_readdir_args *ap));
static int kernfs_reclaim __P((struct vop_reclaim_args *ap));
static int kernfs_setattr __P((struct vop_setattr_args *ap));
static int kernfs_write __P((struct vop_write_args *ap));
static int kernfs_xread __P((struct kern_target *kt, char *buf, int len,
int *lenp));
static int kernfs_xwrite __P((struct kern_target *kt, char *buf, int len));
static int
kernfs_xread(kt, buf, len, lenp)
struct kern_target *kt;
char *buf;
int len;
int *lenp;
{
switch (kt->kt_tag) {
case KTT_TIME: {
struct timeval tv;
microtime(&tv);
snprintf(buf, len, "%ld %ld\n", tv.tv_sec, tv.tv_usec);
break;
}
case KTT_INT: {
int *ip = kt->kt_data;
snprintf(buf, len, "%d\n", *ip);
break;
}
case KTT_STRING: {
char *cp = kt->kt_data;
int xlen = strlen(cp) + 1;
if (xlen >= len)
return (EINVAL);
bcopy(cp, buf, xlen);
break;
}
case KTT_HOSTNAME: {
char *cp = hostname;
int xlen = strlen(hostname);
if (xlen >= (len-2))
return (EINVAL);
bcopy(cp, buf, xlen);
buf[xlen] = '\n';
buf[xlen+1] = '\0';
break;
}
case KTT_BOOTFILE: {
char *cp = kernelname;
int xlen = strlen(cp) + 1;
if (xlen >= (len-2))
return (EINVAL);
bcopy(cp, buf, xlen);
buf[xlen] = '\n';
buf[xlen+1] = '\0';
break;
}
case KTT_AVENRUN:
snprintf(buf, len, "%lu %lu %lu %ld\n",
(u_long)averunnable.ldavg[0], (u_long)averunnable.ldavg[1],
(u_long)averunnable.ldavg[2], averunnable.fscale);
break;
default:
return (EIO);
}
*lenp = strlen(buf);
return (0);
}
static int
kernfs_xwrite(kt, buf, len)
struct kern_target *kt;
char *buf;
int len;
{
switch (kt->kt_tag) {
case KTT_HOSTNAME:
/* XXX BOGUS !!! no check for the length */
if (buf[len-1] == '\n')
--len;
bcopy(buf, hostname, len);
hostname[len] = '\0';
return (0);
default:
return (EIO);
}
}
/*
* vp is the current namei directory
* ndp is the name to locate in that directory...
*/
static int
kernfs_lookup(ap)
struct vop_lookup_args /* {
struct vnode * a_dvp;
struct vnode ** a_vpp;
struct componentname * a_cnp;
} */ *ap;
{
struct componentname *cnp = ap->a_cnp;
struct vnode **vpp = ap->a_vpp;
struct vnode *dvp = ap->a_dvp;
char *pname = cnp->cn_nameptr;
struct proc *p = cnp->cn_proc;
struct kern_target *kt;
struct vnode *fvp;
int error, i;
#ifdef DEBUG
printf("kernfs_lookup(%p)\n", (void *)ap);
printf("kernfs_lookup(dp = %p, vpp = %p, cnp = %p)\n",
(void *)dvp, (void *)vpp, (void *)ap->a_cnp);
printf("kernfs_lookup(%s)\n", pname);
#endif
*vpp = NULLVP;
if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)
return (EROFS);
VOP_UNLOCK(dvp, 0, p);
if (cnp->cn_namelen == 1 && *pname == '.') {
*vpp = dvp;
VREF(dvp);
vn_lock(dvp, LK_SHARED | LK_RETRY, p);
return (0);
}
#if 0
if (cnp->cn_namelen == 4 && bcmp(pname, "root", 4) == 0) {
*vpp = rootdir;
VREF(rootdir);
vn_lock(rootdir, LK_SHARED | LK_RETRY, p)
return (0);
}
#endif
for (kt = kern_targets, i = 0; i < nkern_targets; kt++, i++) {
if (cnp->cn_namelen == kt->kt_namlen &&
bcmp(kt->kt_name, pname, cnp->cn_namelen) == 0)
goto found;
}
#ifdef DEBUG
printf("kernfs_lookup: i = %d, failed", i);
#endif
vn_lock(dvp, LK_SHARED | LK_RETRY, p);
return (cnp->cn_nameiop == LOOKUP ? ENOENT : EROFS);
found:
#ifdef DEBUG
printf("kernfs_lookup: allocate new vnode\n");
#endif
if ((error = getnewvnode(VT_KERNFS, dvp->v_mount, kernfs_vnodeop_p,
&fvp)) != 0) {
vn_lock(dvp, LK_SHARED | LK_RETRY, p);
return (error);
}
MALLOC(fvp->v_data, void *, sizeof(struct kernfs_node), M_TEMP,
M_WAITOK);
VTOKERN(fvp)->kf_kt = kt;
fvp->v_type = kt->kt_vtype;
vn_lock(fvp, LK_SHARED | LK_RETRY, p);
*vpp = fvp;
#ifdef DEBUG
printf("kernfs_lookup: newvp = %p\n", (void *)fvp);
#endif
return (0);
}
static int
kernfs_access(ap)
struct vop_access_args /* {
struct vnode *a_vp;
int a_mode;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
register struct vnode *vp = ap->a_vp;
mode_t amode = ap->a_mode;
mode_t fmode =
(vp->v_flag & VROOT) ? DIR_MODE : VTOKERN(vp)->kf_kt->kt_mode;
/* Some files are simply not modifiable. */
if ((amode & VWRITE) && (fmode & (S_IWUSR|S_IWGRP|S_IWOTH)) == 0)
return (EPERM);
return (vaccess(vp->v_tag, fmode, 0, 0, ap->a_mode, ap->a_cred, NULL));
}
static int
kernfs_getattr(ap)
struct vop_getattr_args /* {
struct vnode *a_vp;
struct vattr *a_vap;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
struct vnode *vp = ap->a_vp;
struct vattr *vap = ap->a_vap;
int error = 0;
char strbuf[KSTRING];
bzero((caddr_t) vap, sizeof(*vap));
vattr_null(vap);
vap->va_uid = 0;
vap->va_gid = 0;
vap->va_size = 0;
vap->va_blocksize = DEV_BSIZE;
nanotime(&vap->va_atime);
vap->va_mtime = vap->va_atime;
vap->va_ctime = vap->va_mtime;
vap->va_gen = 0;
vap->va_flags = 0;
vap->va_rdev = 0;
vap->va_bytes = 0;
if (vp->v_flag & VROOT) {
#ifdef DEBUG
printf("kernfs_getattr: stat rootdir\n");
#endif
vap->va_type = VDIR;
vap->va_mode = DIR_MODE;
vap->va_nlink = 2;
vap->va_fileid = 2;
vap->va_size = DEV_BSIZE;
} else {
struct kern_target *kt = VTOKERN(vp)->kf_kt;
int nbytes;
#ifdef DEBUG
printf("kernfs_getattr: stat target %s\n", kt->kt_name);
#endif
vap->va_type = kt->kt_vtype;
vap->va_mode = kt->kt_mode;
vap->va_nlink = 1;
vap->va_fileid = 1 + (kt - kern_targets) / sizeof(*kt);
error = kernfs_xread(kt, strbuf, sizeof(strbuf), &nbytes);
vap->va_size = nbytes;
}
#ifdef DEBUG
printf("kernfs_getattr: return error %d\n", error);
#endif
return (error);
}
static int
kernfs_setattr(ap)
struct vop_setattr_args /* {
struct vnode *a_vp;
struct vattr *a_vap;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
if (ap->a_vap->va_flags != VNOVAL)
return (EOPNOTSUPP);
/*
* Silently ignore attribute changes.
* This allows for open with truncate to have no
* effect until some data is written. I want to
* do it this way because all writes are atomic.
*/
return (0);
}
static int
kernfs_read(ap)
struct vop_read_args /* {
struct vnode *a_vp;
struct uio *a_uio;
int a_ioflag;
struct ucred *a_cred;
} */ *ap;
{
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct kern_target *kt;
char strbuf[KSTRING];
int off = uio->uio_offset;
int error, len;
if (vp->v_type == VDIR)
return (EOPNOTSUPP);
kt = VTOKERN(vp)->kf_kt;
#ifdef DEBUG
printf("kern_read %s\n", kt->kt_name);
#endif
len = 0;
if ((error = kernfs_xread(kt, strbuf, sizeof(strbuf), &len)) != 0)
return (error);
if (len <= off)
return (0);
return (uiomove(&strbuf[off], len - off, uio));
}
static int
kernfs_write(ap)
struct vop_write_args /* {
struct vnode *a_vp;
struct uio *a_uio;
int a_ioflag;
struct ucred *a_cred;
} */ *ap;
{
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct kern_target *kt;
int error, xlen;
char strbuf[KSTRING];
if (vp->v_type == VDIR)
return (EOPNOTSUPP);
kt = VTOKERN(vp)->kf_kt;
if (uio->uio_offset != 0)
return (EINVAL);
xlen = min(uio->uio_resid, KSTRING-1);
if ((error = uiomove(strbuf, xlen, uio)) != 0)
return (error);
if (uio->uio_resid != 0)
return (EIO);
strbuf[xlen] = '\0';
xlen = strlen(strbuf);
return (kernfs_xwrite(kt, strbuf, xlen));
}
static int
kernfs_readdir(ap)
struct vop_readdir_args /* {
struct vnode *a_vp;
struct uio *a_uio;
struct ucred *a_cred;
int *a_eofflag;
int *a_ncookies;
u_long **a_cookies;
} */ *ap;
{
int error, i, off;
struct uio *uio = ap->a_uio;
struct kern_target *kt;
struct dirent d;
if (ap->a_vp->v_type != VDIR)
return (ENOTDIR);
off = (int)uio->uio_offset;
if (off != uio->uio_offset || off < 0 || (u_int)off % UIO_MX != 0 ||
uio->uio_resid < UIO_MX)
return (EINVAL);
i = (u_int)off / UIO_MX;
error = 0;
for (kt = &kern_targets[i];
uio->uio_resid >= UIO_MX && i < nkern_targets; kt++, i++) {
struct dirent *dp = &d;
#ifdef DEBUG
printf("kernfs_readdir: i = %d\n", i);
#endif
bzero((caddr_t)dp, UIO_MX);
dp->d_namlen = kt->kt_namlen;
bcopy(kt->kt_name, dp->d_name, kt->kt_namlen+1);
#ifdef DEBUG
printf("kernfs_readdir: name = %s, len = %d\n",
dp->d_name, dp->d_namlen);
#endif
/*
* Fill in the remaining fields
*/
dp->d_reclen = UIO_MX;
dp->d_fileno = i + 3;
dp->d_type = kt->kt_type;
/*
* And ship to userland
*/
if ((error = uiomove((caddr_t)dp, UIO_MX, uio)) != 0)
break;
}
uio->uio_offset = i * UIO_MX;
return (error);
}
static int
kernfs_inactive(ap)
struct vop_inactive_args /* {
struct vnode *a_vp;
struct proc *a_p;
} */ *ap;
{
struct vnode *vp = ap->a_vp;
#ifdef DEBUG
printf("kernfs_inactive(%p)\n", (void *)vp);
#endif
/*
* Clear out the v_type field to avoid
* nasty things happening in vgone().
*/
VOP_UNLOCK(vp, 0, ap->a_p);
vp->v_type = VNON;
return (0);
}
static int
kernfs_reclaim(ap)
struct vop_reclaim_args /* {
struct vnode *a_vp;
} */ *ap;
{
struct vnode *vp = ap->a_vp;
#ifdef DEBUG
printf("kernfs_reclaim(%p)\n", (void *)vp);
#endif
if (vp->v_data) {
FREE(vp->v_data, M_TEMP);
vp->v_data = 0;
}
return (0);
}
/*
* Print out the contents of a kernfs vnode.
*/
/* ARGSUSED */
static int
kernfs_print(ap)
struct vop_print_args /* {
struct vnode *a_vp;
} */ *ap;
{
printf("tag VT_KERNFS, kernfs vnode\n");
return (0);
}
/*
* Kernfs "should never get here" operation
*/
static int
kernfs_badop()
{
return (EIO);
}
vop_t **kernfs_vnodeop_p;
static struct vnodeopv_entry_desc kernfs_vnodeop_entries[] = {
{ &vop_default_desc, (vop_t *) vop_defaultop },
{ &vop_access_desc, (vop_t *) kernfs_access },
{ &vop_bmap_desc, (vop_t *) kernfs_badop },
{ &vop_getattr_desc, (vop_t *) kernfs_getattr },
{ &vop_inactive_desc, (vop_t *) kernfs_inactive },
{ &vop_lookup_desc, (vop_t *) kernfs_lookup },
{ &vop_pathconf_desc, (vop_t *) vop_stdpathconf },
{ &vop_print_desc, (vop_t *) kernfs_print },
{ &vop_read_desc, (vop_t *) kernfs_read },
{ &vop_readdir_desc, (vop_t *) kernfs_readdir },
{ &vop_reclaim_desc, (vop_t *) kernfs_reclaim },
{ &vop_setattr_desc, (vop_t *) kernfs_setattr },
{ &vop_write_desc, (vop_t *) kernfs_write },
{ NULL, NULL }
};
static struct vnodeopv_desc kernfs_vnodeop_opv_desc =
{ &kernfs_vnodeop_p, kernfs_vnodeop_entries };
VNODEOP_SET(kernfs_vnodeop_opv_desc);