d46957d5ba
for nfsclient and nfs4client in order to prevent local root users from panicing the system. PR: kern/77463 Submitted by: Wojciech A. Koszek Reviewed by: cel, rees MFC after: 2 weeks Security: Local root users can panic the system at will
457 lines
10 KiB
C
457 lines
10 KiB
C
/* $FreeBSD$ */
|
|
/* $Id: nfs4_dev.c,v 1.10 2003/11/05 14:58:59 rees Exp $ */
|
|
|
|
/*-
|
|
* copyright (c) 2003
|
|
* the regents of the university of michigan
|
|
* all rights reserved
|
|
*
|
|
* permission is granted to use, copy, create derivative works and redistribute
|
|
* this software and such derivative works for any purpose, so long as the name
|
|
* of the university of michigan is not used in any advertising or publicity
|
|
* pertaining to the use or distribution of this software without specific,
|
|
* written prior authorization. if the above copyright notice or any other
|
|
* identification of the university of michigan is included in any copy of any
|
|
* portion of this software, then the disclaimer below must also be included.
|
|
*
|
|
* this software is provided as is, without representation from the university
|
|
* of michigan as to its fitness for any purpose, and without warranty by the
|
|
* university of michigan of any kind, either express or implied, including
|
|
* without limitation the implied warranties of merchantability and fitness for
|
|
* a particular purpose. the regents of the university of michigan shall not be
|
|
* liable for any damages, including special, indirect, incidental, or
|
|
* consequential damages, with respect to any claim arising out of or in
|
|
* connection with the use of the software, even if it has been or is hereafter
|
|
* advised of the possibility of such damages.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/signalvar.h>
|
|
|
|
#include <nfs4client/nfs4_dev.h>
|
|
|
|
#ifdef NFS4DEVVERBOSE
|
|
#define NFS4DEV_DEBUG(X...) printf(X)
|
|
#else
|
|
#define NFS4DEV_DEBUG(X...)
|
|
#endif
|
|
|
|
#define NFS4DEV_NAME "nfs4"
|
|
#define CDEV_MINOR 1
|
|
|
|
MALLOC_DEFINE(M_NFS4DEV, "nfs4_dev", "NFS4 device");
|
|
|
|
struct nfs4dev_upcall {
|
|
/* request msg */
|
|
struct nfs4dev_msg up_reqmsg;
|
|
size_t up_reqmsglen;
|
|
|
|
/* reply (payload only) */
|
|
caddr_t up_rep;
|
|
size_t * up_replen;
|
|
|
|
int up_copied; /* non-zero when reply has been copied to
|
|
'*up_rep' */
|
|
|
|
int up_error; /* non-zero if an error occured */
|
|
|
|
TAILQ_ENTRY(nfs4dev_upcall) up_entry;
|
|
};
|
|
|
|
|
|
#define nfs4dev_upcall_get(MP) MALLOC((MP), struct nfs4dev_upcall *, sizeof(struct nfs4dev_upcall), M_NFS4DEV, M_WAITOK | M_ZERO)
|
|
|
|
#define nfs4dev_upcall_put(MP) FREE((MP), M_NFS4DEV)
|
|
|
|
static int nfs4dev_nopen = 0;
|
|
static struct thread * nfs4dev_reader = NULL;
|
|
static struct cdev *nfs4device = 0;
|
|
static struct mtx nfs4dev_daemon_mtx;
|
|
|
|
static int nfs4dev_xid = 0;
|
|
/* queue of pending upcalls */
|
|
TAILQ_HEAD(, nfs4dev_upcall) nfs4dev_newq;
|
|
static struct mtx nfs4dev_newq_mtx;
|
|
|
|
/* queue of upcalls waiting for replys */
|
|
TAILQ_HEAD(, nfs4dev_upcall) nfs4dev_waitq;
|
|
static struct mtx nfs4dev_waitq_mtx;
|
|
|
|
/* dev hooks */
|
|
static d_open_t nfs4dev_open;
|
|
static d_close_t nfs4dev_close;
|
|
static d_ioctl_t nfs4dev_ioctl;
|
|
static d_poll_t nfs4dev_poll;
|
|
|
|
static struct cdevsw nfs4dev_cdevsw = {
|
|
#if (__FreeBSD_version > 502102)
|
|
.d_version = D_VERSION,
|
|
.d_flags = D_NEEDGIANT,
|
|
#endif
|
|
.d_open = nfs4dev_open,
|
|
.d_close = nfs4dev_close,
|
|
.d_ioctl = nfs4dev_ioctl,
|
|
.d_poll = nfs4dev_poll,
|
|
.d_name = NFS4DEV_NAME,
|
|
};
|
|
|
|
static int nfs4dev_reply(caddr_t);
|
|
static int nfs4dev_request(caddr_t);
|
|
|
|
/* Userland requests a new operation to service */
|
|
static int
|
|
nfs4dev_request(caddr_t addr)
|
|
{
|
|
struct nfs4dev_upcall * u;
|
|
struct nfs4dev_msg * m = (struct nfs4dev_msg *) addr;
|
|
|
|
mtx_lock(&nfs4dev_newq_mtx);
|
|
|
|
if (TAILQ_EMPTY(&nfs4dev_newq)) {
|
|
mtx_unlock(&nfs4dev_newq_mtx);
|
|
return EAGAIN;
|
|
}
|
|
|
|
u = TAILQ_FIRST(&nfs4dev_newq);
|
|
TAILQ_REMOVE(&nfs4dev_newq, u, up_entry);
|
|
mtx_unlock(&nfs4dev_newq_mtx);
|
|
|
|
bcopy(&u->up_reqmsg, m, sizeof(struct nfs4dev_msg));
|
|
|
|
mtx_lock(&nfs4dev_waitq_mtx);
|
|
TAILQ_INSERT_TAIL(&nfs4dev_waitq, u, up_entry);
|
|
mtx_unlock(&nfs4dev_waitq_mtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
nfs4dev_reply(caddr_t addr)
|
|
{
|
|
struct nfs4dev_upcall * u;
|
|
struct nfs4dev_msg * m = (struct nfs4dev_msg *) addr;
|
|
int error;
|
|
|
|
if (m->msg_vers != NFS4DEV_VERSION) {
|
|
printf("nfs4dev version mismatch\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
if (m->msg_type > NFS4DEV_MAX_TYPE) {
|
|
NFS4DEV_DEBUG("nfs4dev: unsupported message type\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
if (m->msg_len < sizeof(*m) - NFS4DEV_MSG_MAX_DATALEN ||
|
|
m->msg_len > NFS4DEV_MSG_MAX_DATALEN) {
|
|
NFS4DEV_DEBUG("bad message length\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
/* match the reply with a request */
|
|
mtx_lock(&nfs4dev_waitq_mtx);
|
|
TAILQ_FOREACH(u, &nfs4dev_waitq, up_entry) {
|
|
if (m->msg_xid == u->up_reqmsg.msg_xid) {
|
|
if (m->msg_type == u->up_reqmsg.msg_type)
|
|
goto found;
|
|
NFS4DEV_DEBUG("nfs4dev: op type mismatch!\n");
|
|
break;
|
|
}
|
|
}
|
|
mtx_unlock(&nfs4dev_waitq_mtx);
|
|
|
|
NFS4DEV_DEBUG("nfs4dev msg op: %d xid: %x not found.\n",
|
|
m->msg_type, m->msg_xid);
|
|
|
|
error = EIO;
|
|
goto bad;
|
|
|
|
found:
|
|
TAILQ_REMOVE(&nfs4dev_waitq, u, up_entry);
|
|
mtx_unlock(&nfs4dev_waitq_mtx);
|
|
|
|
if (m->msg_error) {
|
|
error = m->msg_error;
|
|
goto bad;
|
|
}
|
|
|
|
if (m->msg_len > *u->up_replen) {
|
|
error = EFAULT;
|
|
goto bad;
|
|
}
|
|
|
|
bcopy(m->msg_data, u->up_rep, m->msg_len);
|
|
*u->up_replen = m->msg_len;
|
|
|
|
u->up_copied = m->msg_len;
|
|
wakeup(u);
|
|
|
|
return 0;
|
|
bad:
|
|
if (u) {
|
|
u->up_error = error;
|
|
wakeup(u);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void
|
|
nfs4dev_init(void)
|
|
{
|
|
nfs4dev_xid = arc4random();
|
|
TAILQ_INIT(&nfs4dev_newq);
|
|
TAILQ_INIT(&nfs4dev_waitq);
|
|
mtx_init(&nfs4dev_newq_mtx, "nfs4dev newq", NULL, MTX_DEF);
|
|
mtx_init(&nfs4dev_waitq_mtx, "nfs4dev waitq", NULL, MTX_DEF);
|
|
|
|
mtx_init(&nfs4dev_daemon_mtx, "nfs4dev state", NULL, MTX_DEF);
|
|
|
|
nfs4device = make_dev(&nfs4dev_cdevsw, CDEV_MINOR, (uid_t)0, (gid_t)0,
|
|
S_IRUSR | S_IWUSR, "nfs4");
|
|
}
|
|
|
|
void
|
|
nfs4dev_uninit(void)
|
|
{
|
|
struct proc * dead = NULL;
|
|
|
|
mtx_lock(&nfs4dev_daemon_mtx);
|
|
if (nfs4dev_nopen) {
|
|
if (nfs4dev_reader == NULL) {
|
|
NFS4DEV_DEBUG("nfs4dev uninit(): unregistered reader\n");
|
|
} else {
|
|
dead = nfs4dev_reader->td_proc;
|
|
}
|
|
}
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
|
|
if (dead != NULL) {
|
|
NFS4DEV_DEBUG("nfs4dev_uninit(): you forgot to kill attached daemon (pid: %u)\n",
|
|
dead->p_pid);
|
|
PROC_LOCK(dead);
|
|
psignal(dead, SIGTERM);
|
|
PROC_UNLOCK(dead);
|
|
}
|
|
|
|
/* XXX moot? */
|
|
nfs4dev_purge();
|
|
|
|
mtx_destroy(&nfs4dev_newq_mtx);
|
|
mtx_destroy(&nfs4dev_waitq_mtx);
|
|
mtx_destroy(&nfs4dev_daemon_mtx);
|
|
|
|
destroy_dev(nfs4device);
|
|
}
|
|
|
|
/* device interface functions */
|
|
static int
|
|
nfs4dev_open(struct cdev *dev, int flags, int fmt, d_thread_t *td)
|
|
{
|
|
if (dev != nfs4device)
|
|
return ENODEV;
|
|
|
|
mtx_lock(&nfs4dev_daemon_mtx);
|
|
if (nfs4dev_nopen) {
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
return EBUSY;
|
|
}
|
|
|
|
nfs4dev_nopen++;
|
|
nfs4dev_reader = curthread;
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
nfs4dev_close(struct cdev *dev, int flags, int fmt, d_thread_t *td)
|
|
{
|
|
struct nfs4dev_upcall * u;
|
|
|
|
if (dev != nfs4device)
|
|
return ENODEV;
|
|
|
|
mtx_lock(&nfs4dev_daemon_mtx);
|
|
if (!nfs4dev_nopen) {
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
return ENOENT;
|
|
}
|
|
|
|
nfs4dev_nopen--;
|
|
nfs4dev_reader = NULL;
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
|
|
mtx_lock(&nfs4dev_waitq_mtx);
|
|
|
|
while (!TAILQ_EMPTY(&nfs4dev_waitq)) {
|
|
u = TAILQ_FIRST(&nfs4dev_waitq);
|
|
TAILQ_REMOVE(&nfs4dev_waitq, u, up_entry);
|
|
u->up_error = EINTR;
|
|
wakeup(u);
|
|
}
|
|
|
|
mtx_unlock(&nfs4dev_waitq_mtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
nfs4dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
|
|
{
|
|
int error;
|
|
|
|
if (dev != nfs4device)
|
|
return ENODEV;
|
|
|
|
if (data == NULL)
|
|
return EFAULT;
|
|
|
|
if (nfs4dev_reader != curthread)
|
|
nfs4dev_reader = curthread;
|
|
|
|
switch (cmd) {
|
|
case NFS4DEVIOCGET:
|
|
error = nfs4dev_request(data);
|
|
break;
|
|
case NFS4DEVIOCPUT:
|
|
error = nfs4dev_reply(data);
|
|
break;
|
|
default:
|
|
NFS4DEV_DEBUG("nfs4dev_ioctl: unkown ioctl cmd %d\n", (int)cmd);
|
|
error = EOPNOTSUPP;
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
nfs4dev_poll(struct cdev *dev, int events, struct thread *td)
|
|
{
|
|
int revents;
|
|
|
|
if (dev != nfs4device)
|
|
return EINVAL;
|
|
|
|
mtx_lock(&nfs4dev_daemon_mtx);
|
|
if (nfs4dev_nopen == 0) {
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
return 0;
|
|
}
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
|
|
revents = 0;
|
|
|
|
/* check readable data */
|
|
mtx_lock(&nfs4dev_newq_mtx);
|
|
if (!TAILQ_EMPTY(&nfs4dev_newq))
|
|
revents |= POLLIN;
|
|
mtx_unlock(&nfs4dev_newq_mtx);
|
|
|
|
mtx_lock(&nfs4dev_waitq_mtx);
|
|
if (!TAILQ_EMPTY(&nfs4dev_waitq))
|
|
revents |= POLLOUT;
|
|
mtx_unlock(&nfs4dev_waitq_mtx);
|
|
|
|
return revents;
|
|
}
|
|
|
|
int
|
|
nfs4dev_call(uint32_t type, caddr_t req_data, size_t req_len, caddr_t rep_data, size_t * rep_lenp)
|
|
{
|
|
struct nfs4dev_upcall * u;
|
|
int error = 0;
|
|
unsigned int xtmp;
|
|
|
|
mtx_lock(&nfs4dev_daemon_mtx);
|
|
if (nfs4dev_nopen == 0) {
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
return EINVAL;
|
|
}
|
|
mtx_unlock(&nfs4dev_daemon_mtx);
|
|
|
|
if (type > NFS4DEV_MAX_TYPE)
|
|
return EOPNOTSUPP;
|
|
|
|
NFS4DEV_DEBUG("upcall %d/%d:%d\n", type, req_len, *rep_lenp);
|
|
|
|
nfs4dev_upcall_get(u);
|
|
|
|
u->up_error = 0;
|
|
u->up_rep = rep_data;
|
|
u->up_replen = rep_lenp;
|
|
u->up_copied = 0;
|
|
|
|
u->up_reqmsg.msg_vers = NFS4DEV_VERSION;
|
|
/* XXX efficient copying */
|
|
bcopy(req_data, u->up_reqmsg.msg_data, req_len);
|
|
u->up_reqmsg.msg_len = req_len;
|
|
|
|
mtx_lock(&nfs4dev_newq_mtx);
|
|
|
|
/* get new XID */
|
|
while ((xtmp = arc4random() % 256) == 0);
|
|
nfs4dev_xid += xtmp;
|
|
u->up_reqmsg.msg_xid = nfs4dev_xid;
|
|
|
|
TAILQ_INSERT_TAIL(&nfs4dev_newq, u, up_entry);
|
|
mtx_unlock(&nfs4dev_newq_mtx);
|
|
|
|
|
|
NFS4DEV_DEBUG("nfs4dev op: %d xid: %x sleeping\n", u->up_reqmsg.msg_type, u->up_reqmsg.msg_xid);
|
|
|
|
do {
|
|
tsleep(u, PLOCK, "nfs4dev", 0);
|
|
} while (u->up_copied == 0 && u->up_error == 0);
|
|
|
|
/* upcall now removed from the queue */
|
|
|
|
NFS4DEV_DEBUG("nfs4dev prog: %d xid: %x continues...\n",
|
|
u->up_reqmsg.msg_type, u->up_reqmsg.msg_xid);
|
|
|
|
if (u->up_error) {
|
|
error = u->up_error;
|
|
NFS4DEV_DEBUG("nfs4dev prog: %d xid: %x error: %d\n",
|
|
u->up_reqmsg.msg_type, u->up_reqmsg.msg_xid, u->up_error);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
nfs4dev_upcall_put(u);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
nfs4dev_purge(void)
|
|
{
|
|
struct nfs4dev_upcall * u;
|
|
|
|
mtx_lock(&nfs4dev_newq_mtx);
|
|
while (!TAILQ_EMPTY(&nfs4dev_newq)) {
|
|
u = TAILQ_FIRST(&nfs4dev_newq);
|
|
TAILQ_REMOVE(&nfs4dev_newq, u, up_entry);
|
|
u->up_error = EINTR;
|
|
wakeup(u);
|
|
}
|
|
mtx_unlock(&nfs4dev_newq_mtx);
|
|
|
|
mtx_lock(&nfs4dev_waitq_mtx);
|
|
while (!TAILQ_EMPTY(&nfs4dev_waitq)) {
|
|
u = TAILQ_FIRST(&nfs4dev_waitq);
|
|
TAILQ_REMOVE(&nfs4dev_waitq, u, up_entry);
|
|
u->up_error = EINTR;
|
|
wakeup(u);
|
|
}
|
|
mtx_unlock(&nfs4dev_waitq_mtx);
|
|
}
|