Changeset View
Changeset View
Standalone View
Standalone View
sys/amd64/vmm/vmm_dev.c
Show All 34 Lines | |||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/jail.h> | #include <sys/jail.h> | ||||
#include <sys/queue.h> | #include <sys/queue.h> | ||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/mutex.h> | #include <sys/mutex.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/mount.h> | |||||
#include <sys/conf.h> | #include <sys/conf.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/libkern.h> | #include <sys/libkern.h> | ||||
#include <sys/ioccom.h> | #include <sys/ioccom.h> | ||||
#include <sys/mman.h> | #include <sys/mman.h> | ||||
#include <sys/uio.h> | #include <sys/uio.h> | ||||
#include <sys/proc.h> | #include <sys/proc.h> | ||||
Show All 35 Lines | struct vmmdev_softc { | ||||
int flags; | int flags; | ||||
}; | }; | ||||
#define VSC_LINKED 0x01 | #define VSC_LINKED 0x01 | ||||
static SLIST_HEAD(, vmmdev_softc) head; | static SLIST_HEAD(, vmmdev_softc) head; | ||||
static unsigned pr_allow_flag; | static unsigned pr_allow_flag; | ||||
static struct mtx vmmdev_mtx; | static struct mtx vmmdev_mtx; | ||||
static unsigned vmmdev_prison_slot; | |||||
static MALLOC_DEFINE(M_VMMDEV, "vmmdev", "vmmdev"); | static MALLOC_DEFINE(M_VMMDEV, "vmmdev", "vmmdev"); | ||||
SYSCTL_DECL(_hw_vmm); | SYSCTL_DECL(_hw_vmm); | ||||
static int vmm_priv_check(struct ucred *ucred); | static int vmm_priv_check(struct ucred *ucred); | ||||
static int devmem_create_cdev(const char *vmname, int id, char *devmem); | static int devmem_create_cdev(const char *vmname, int id, char *devmem); | ||||
static void devmem_destroy(void *arg); | static void devmem_destroy(void *arg); | ||||
static int vmm_destroy(struct vmmdev_softc *sc); | |||||
static int | static int | ||||
vmm_priv_check(struct ucred *ucred) | vmm_priv_check(struct ucred *ucred) | ||||
{ | { | ||||
if (jailed(ucred) && | if (jailed(ucred) && | ||||
!(ucred->cr_prison->pr_allow & pr_allow_flag)) | !(ucred->cr_prison->pr_allow & pr_allow_flag)) | ||||
return (EPERM); | return (EPERM); | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
static struct vmmdev_softc * | static struct vmmdev_softc * | ||||
vmmdev_lookup(const char *name) | vmmdev_lookup(const char *name) | ||||
{ | { | ||||
struct vmmdev_softc *sc; | struct vmmdev_softc *sc; | ||||
struct vmmdev_softc *found = NULL; | struct vmmdev_softc *found = NULL; | ||||
#ifdef notyet /* XXX kernel is not compiled with invariants */ | |||||
mtx_assert(&vmmdev_mtx, MA_OWNED); | mtx_assert(&vmmdev_mtx, MA_OWNED); | ||||
#endif | |||||
SLIST_FOREACH(sc, &head, link) { | SLIST_FOREACH(sc, &head, link) { | ||||
if (strcmp(name, vm_name(sc->vm)) == 0) { | if (strcmp(name, vm_name(sc->vm)) == 0) { | ||||
if (!cr_cansee(curthread->td_ucred, sc->ucred)) | if (!cr_cansee(curthread->td_ucred, sc->ucred)) | ||||
found = sc; | found = sc; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 803 Lines • ▼ Show 20 Lines | if ((sc->flags & VSC_LINKED) != 0) { | ||||
SLIST_REMOVE(&head, sc, vmmdev_softc, link); | SLIST_REMOVE(&head, sc, vmmdev_softc, link); | ||||
mtx_unlock(&vmmdev_mtx); | mtx_unlock(&vmmdev_mtx); | ||||
} | } | ||||
free(sc, M_VMMDEV); | free(sc, M_VMMDEV); | ||||
} | } | ||||
static int | static int | ||||
sysctl_vmm_destroy(SYSCTL_HANDLER_ARGS) | vmm_destroy(struct vmmdev_softc *sc) | ||||
{ | { | ||||
struct devmem_softc *dsc; | struct devmem_softc *dsc; | ||||
struct vmmdev_softc *sc; | |||||
struct cdev *cdev; | struct cdev *cdev; | ||||
char *buf; | |||||
int error, buflen; | |||||
error = vmm_priv_check(req->td->td_ucred); | mtx_assert(&vmmdev_mtx, MA_OWNED); | ||||
if (error) | |||||
return (error); | |||||
buflen = VM_MAX_NAMELEN + 1; | |||||
buf = malloc(buflen, M_VMMDEV, M_WAITOK | M_ZERO); | |||||
strlcpy(buf, "beavis", buflen); | |||||
error = sysctl_handle_string(oidp, buf, buflen, req); | |||||
if (error != 0 || req->newptr == NULL) | |||||
goto out; | |||||
mtx_lock(&vmmdev_mtx); | |||||
sc = vmmdev_lookup(buf); | |||||
if (sc == NULL || sc->cdev == NULL) { | if (sc == NULL || sc->cdev == NULL) { | ||||
markj: I would suggest asserting that vmmdev_mtx is held here. | |||||
mtx_unlock(&vmmdev_mtx); | return (EINVAL); | ||||
error = EINVAL; | |||||
goto out; | |||||
} | } | ||||
/* | /* | ||||
* The 'cdev' will be destroyed asynchronously when 'si_threadcount' | * The 'cdev' will be destroyed asynchronously when 'si_threadcount' | ||||
* goes down to 0 so we should not do it again in the callback. | * goes down to 0 so we should not do it again in the callback. | ||||
* | * | ||||
* Setting 'sc->cdev' to NULL is also used to indicate that the VM | * Setting 'sc->cdev' to NULL is also used to indicate that the VM | ||||
* is scheduled for destruction. | * is scheduled for destruction. | ||||
*/ | */ | ||||
cdev = sc->cdev; | cdev = sc->cdev; | ||||
sc->cdev = NULL; | sc->cdev = NULL; | ||||
mtx_unlock(&vmmdev_mtx); | |||||
/* | /* | ||||
* Schedule all cdevs to be destroyed: | * Schedule all cdevs to be destroyed: | ||||
* | * | ||||
* - any new operations on the 'cdev' will return an error (ENXIO). | * - any new operations on the 'cdev' will return an error (ENXIO). | ||||
* | * | ||||
* - when the 'si_threadcount' dwindles down to zero the 'cdev' will | * - when the 'si_threadcount' dwindles down to zero the 'cdev' will | ||||
* be destroyed and the callback will be invoked in a taskqueue | * be destroyed and the callback will be invoked in a taskqueue | ||||
* context. | * context. | ||||
* | * | ||||
* - the 'devmem' cdevs are destroyed before the virtual machine 'cdev' | * - the 'devmem' cdevs are destroyed before the virtual machine 'cdev' | ||||
*/ | */ | ||||
SLIST_FOREACH(dsc, &sc->devmem, link) { | SLIST_FOREACH(dsc, &sc->devmem, link) { | ||||
KASSERT(dsc->cdev != NULL, ("devmem cdev already destroyed")); | KASSERT(dsc->cdev != NULL, ("devmem cdev already destroyed")); | ||||
destroy_dev_sched_cb(dsc->cdev, devmem_destroy, dsc); | destroy_dev_sched_cb(dsc->cdev, devmem_destroy, dsc); | ||||
} | } | ||||
destroy_dev_sched_cb(cdev, vmmdev_destroy, sc); | destroy_dev_sched_cb(cdev, vmmdev_destroy, sc); | ||||
error = 0; | |||||
return (0); | |||||
} | |||||
static int | |||||
sysctl_vmm_destroy(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct vmmdev_softc *sc; | |||||
char *buf; | |||||
int error; | |||||
Not Done Inline ActionsPedantically, buflen should be a size_t. markj: Pedantically, `buflen` should be a `size_t`. | |||||
size_t buflen; | |||||
error = vmm_priv_check(req->td->td_ucred); | |||||
if (error) | |||||
return (error); | |||||
buflen = VM_MAX_NAMELEN + 1; | |||||
buf = malloc(buflen, M_VMMDEV, M_WAITOK | M_ZERO); | |||||
strlcpy(buf, "beavis", buflen); | |||||
error = sysctl_handle_string(oidp, buf, buflen, req); | |||||
if (error != 0 || req->newptr == NULL) | |||||
goto out; | |||||
mtx_lock(&vmmdev_mtx); | |||||
sc = vmmdev_lookup(buf); | |||||
error = vmm_destroy(sc); | |||||
mtx_unlock(&vmmdev_mtx); | |||||
out: | out: | ||||
free(buf, M_VMMDEV); | free(buf, M_VMMDEV); | ||||
return (error); | return (error); | ||||
} | } | ||||
SYSCTL_PROC(_hw_vmm, OID_AUTO, destroy, | SYSCTL_PROC(_hw_vmm, OID_AUTO, destroy, | ||||
CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE, | CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE, | ||||
NULL, 0, sysctl_vmm_destroy, "A", | NULL, 0, sysctl_vmm_destroy, "A", | ||||
NULL); | NULL); | ||||
Show All 9 Lines | |||||
static int | static int | ||||
sysctl_vmm_create(SYSCTL_HANDLER_ARGS) | sysctl_vmm_create(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct vm *vm; | struct vm *vm; | ||||
struct cdev *cdev; | struct cdev *cdev; | ||||
struct vmmdev_softc *sc, *sc2; | struct vmmdev_softc *sc, *sc2; | ||||
char *buf; | char *buf; | ||||
int error, buflen; | int error; | ||||
size_t buflen; | |||||
error = vmm_priv_check(req->td->td_ucred); | error = vmm_priv_check(req->td->td_ucred); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
buflen = VM_MAX_NAMELEN + 1; | buflen = VM_MAX_NAMELEN + 1; | ||||
buf = malloc(buflen, M_VMMDEV, M_WAITOK | M_ZERO); | buf = malloc(buflen, M_VMMDEV, M_WAITOK | M_ZERO); | ||||
strlcpy(buf, "beavis", buflen); | strlcpy(buf, "beavis", buflen); | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | out: | ||||
free(buf, M_VMMDEV); | free(buf, M_VMMDEV); | ||||
return (error); | return (error); | ||||
} | } | ||||
SYSCTL_PROC(_hw_vmm, OID_AUTO, create, | SYSCTL_PROC(_hw_vmm, OID_AUTO, create, | ||||
CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE, | CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE, | ||||
NULL, 0, sysctl_vmm_create, "A", | NULL, 0, sysctl_vmm_create, "A", | ||||
NULL); | NULL); | ||||
static void | |||||
vmmdev_prison_cleanup(struct prison *pr) | |||||
{ | |||||
struct vmmdev_softc *sc; | |||||
struct vmmdev_softc *tsc; | |||||
mtx_lock(&vmmdev_mtx); | |||||
Not Done Inline ActionsThis is safe because vmmdev_destroy(), which frees sc, is called asynchronously. If that were not the case, and vmm_destroy() freed sc directly, then this would be a use-after-free, since after vmm_destroy() returns we would continue traversal by following sc->link. I would suggest coding this a bit more defensively by using SLIST_FOREACH_SAFE to avoid referencing sc after vmm_destroy() returns, in case the implementation of vmm_destroy() changes at some point. markj: This is safe because vmmdev_destroy(), which frees `sc`, is called asynchronously. If that were… | |||||
SLIST_FOREACH_SAFE(sc, &head, link, tsc) { | |||||
if (sc->ucred->cr_prison == pr) | |||||
vmm_destroy(sc); | |||||
} | |||||
mtx_unlock(&vmmdev_mtx); | |||||
} | |||||
static int | |||||
vmmdev_prison_remove(void *obj, void *data __unused) | |||||
{ | |||||
struct prison *pr = obj; | |||||
vmmdev_prison_cleanup(pr); | |||||
return (0); | |||||
} | |||||
static int | |||||
vmmdev_prison_set(void *obj, void *data) | |||||
{ | |||||
struct prison *pr = obj; | |||||
struct vfsoptlist *opts = data; | |||||
if (vfs_flagopt(opts, "allow.novmm", NULL, 0)) | |||||
vmmdev_prison_cleanup(pr); | |||||
return (0); | |||||
} | |||||
Not Done Inline ActionsYou could get rid of vmm_disabled and just write if (vfs_flagopt(opts, "allow.novmm", NULL, 0)) vmmdev_prison_cleanup(pr); markj: You could get rid of `vmm_disabled` and just write
```
if (vfs_flagopt(opts, "allow.novmm"… | |||||
void | void | ||||
vmmdev_init(void) | vmmdev_init(void) | ||||
{ | { | ||||
osd_method_t methods[PR_MAXMETHOD] = { | |||||
[PR_METHOD_SET] = vmmdev_prison_set, | |||||
[PR_METHOD_REMOVE] = vmmdev_prison_remove, | |||||
}; | |||||
mtx_init(&vmmdev_mtx, "vmm device mutex", NULL, MTX_DEF); | mtx_init(&vmmdev_mtx, "vmm device mutex", NULL, MTX_DEF); | ||||
pr_allow_flag = prison_add_allow(NULL, "vmm", NULL, | pr_allow_flag = prison_add_allow(NULL, "vmm", NULL, | ||||
"Allow use of vmm in a jail."); | "Allow use of vmm in a jail."); | ||||
vmmdev_prison_slot = osd_jail_register(NULL, methods); | |||||
} | } | ||||
int | int | ||||
vmmdev_cleanup(void) | vmmdev_cleanup(void) | ||||
{ | { | ||||
int error; | int error; | ||||
if (SLIST_EMPTY(&head)) | if (SLIST_EMPTY(&head)) { | ||||
osd_jail_deregister(vmmdev_prison_slot); | |||||
error = 0; | error = 0; | ||||
else | } else { | ||||
error = EBUSY; | error = EBUSY; | ||||
} | |||||
return (error); | return (error); | ||||
} | } | ||||
static int | static int | ||||
devmem_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t len, | devmem_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t len, | ||||
struct vm_object **objp, int nprot) | struct vm_object **objp, int nprot) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 90 Lines • Show Last 20 Lines |
I would suggest asserting that vmmdev_mtx is held here.