diff --git a/lib/libvmmapi/vmmapi.h b/lib/libvmmapi/vmmapi.h --- a/lib/libvmmapi/vmmapi.h +++ b/lib/libvmmapi/vmmapi.h @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -113,6 +114,10 @@ int vm_munmap_memseg(struct vmctx *ctx, vm_paddr_t gpa, size_t len); +int vmmctl_open(void); +int vm_fcreate(int fd, const char *name); +int vm_fdestroy(int fd, const char *name); + int vm_create(const char *name); int vm_get_device_fd(struct vmctx *ctx); struct vmctx *vm_open(const char *name); diff --git a/lib/libvmmapi/vmmapi.c b/lib/libvmmapi/vmmapi.c --- a/lib/libvmmapi/vmmapi.c +++ b/lib/libvmmapi/vmmapi.c @@ -105,6 +105,44 @@ return (fd); } +int +vmmctl_open(void) +{ + /* Try to load vmm(4) */ + if (modfind("vmm") < 0) + kldload("vmm"); + + return (open("/dev/vmmctl", O_RDWR)); +} + +int +vm_fcreate(int fd, const char *name) +{ + struct vmmctl_op op; + + if (strlen(name) > VM_MAX_NAMELEN) { + errno = ENAMETOOLONG; + return (-1); + } + + strcpy(op.name, name); + return (ioctl(fd, VMMCTL_CREATE, &op)); +} + +int +vm_fdestroy(int fd, const char *name) +{ + struct vmmctl_op op; + + if (strlen(name) > VM_MAX_NAMELEN) { + errno = ENAMETOOLONG; + return (-1); + } + + strcpy(op.name, name); + return (ioctl(fd, VMMCTL_DESTROY, &op)); +} + int vm_create(const char *name) { diff --git a/sys/amd64/include/vmm_dev.h b/sys/amd64/include/vmm_dev.h --- a/sys/amd64/include/vmm_dev.h +++ b/sys/amd64/include/vmm_dev.h @@ -347,6 +347,15 @@ IOCNUM_RESTORE_TIME = 115 }; +struct vmmctl_op { + char name[VM_MAX_NAMELEN + 1]; +}; + +#define VMMCTL_CREATE \ + _IOWR('v', 101, struct vmmctl_op) +#define VMMCTL_DESTROY \ + _IOWR('v', 102, struct vmmctl_op) + #define VM_RUN \ _IOWR('v', IOCNUM_RUN, struct vm_run) #define VM_SUSPEND \ diff --git a/sys/amd64/vmm/vmm_dev.c b/sys/amd64/vmm/vmm_dev.c --- a/sys/amd64/vmm/vmm_dev.c +++ b/sys/amd64/vmm/vmm_dev.c @@ -79,12 +79,14 @@ }; struct vmmdev_softc { - struct vm *vm; /* vm instance cookie */ + struct vm *vm; /* vm instance cookie */ struct cdev *cdev; struct ucred *ucred; SLIST_ENTRY(vmmdev_softc) link; SLIST_HEAD(, devmem_softc) devmem; int flags; + TAILQ_ENTRY(vmmdev_softc) fvmm_next; /* next entry in fvmm list */ + bool persistent; /* persistent = created via sysctl */ }; #define VSC_LINKED 0x01 @@ -1063,6 +1065,11 @@ mtx_lock(&vmmdev_mtx); sc = vmmdev_lookup(buf); + if (!sc->persistent) { + mtx_unlock(&vmmdev_mtx); + error = EPERM; + goto out; + } error = vmm_destroy(sc); mtx_unlock(&vmmdev_mtx); @@ -1085,11 +1092,63 @@ }; static int -sysctl_vmm_create(SYSCTL_HANDLER_ARGS) +vmm_create(const char *name, struct vmmdev_softc **sc) { - struct vm *vm; struct cdev *cdev; - struct vmmdev_softc *sc, *sc2; + struct vm *vm; + struct vmmdev_softc *tsc; + int error; + + error = vm_create(name, &vm); + if (error != 0) + return (error); + + tsc = malloc(sizeof(struct vmmdev_softc), M_VMMDEV, M_WAITOK | M_ZERO); + tsc->ucred = crhold(curthread->td_ucred); + tsc->vm = vm; + tsc->persistent = true; + SLIST_INIT(&tsc->devmem); + + error = make_dev_p(MAKEDEV_CHECKNAME, &cdev, &vmmdevsw, tsc->ucred, + UID_ROOT, GID_WHEEL, 0600, "vmm/%s", name); + if (error != 0) { + vmmdev_destroy(tsc); + return (error); + } + + tsc->cdev = cdev; + tsc->cdev->si_drv1 = tsc; + *sc = tsc; + + return (0); +} + +static int +vmm_add(struct vmmdev_softc *sc) +{ + struct vmmdev_softc *tsc; + + mtx_assert(&vmmdev_mtx, MA_OWNED); + + /* + * Lookup the name just in case somebody sneaked in. + */ + tsc = vmmdev_lookup(vm_name(sc->vm)); + if (tsc == NULL) { + SLIST_INSERT_HEAD(&head, sc, link); + sc->flags |= VSC_LINKED; + } + + if (tsc != NULL) + return (EEXIST); + + return (0); +} + +static int +sysctl_vmm_create(SYSCTL_HANDLER_ARGS) +{ + struct vmmdev_softc *sc; char *buf; int error; size_t buflen; @@ -1105,52 +1164,21 @@ if (error != 0 || req->newptr == NULL) goto out; - mtx_lock(&vmmdev_mtx); sc = vmmdev_lookup(buf); - mtx_unlock(&vmmdev_mtx); if (sc != NULL) { error = EEXIST; goto out; } - error = vm_create(buf, &vm); + error = vmm_create(buf, &sc); if (error != 0) goto out; - sc = malloc(sizeof(struct vmmdev_softc), M_VMMDEV, M_WAITOK | M_ZERO); - sc->ucred = crhold(curthread->td_ucred); - sc->vm = vm; - SLIST_INIT(&sc->devmem); - - /* - * Lookup the name again just in case somebody sneaked in when we - * dropped the lock. - */ - mtx_lock(&vmmdev_mtx); - sc2 = vmmdev_lookup(buf); - if (sc2 == NULL) { - SLIST_INSERT_HEAD(&head, sc, link); - sc->flags |= VSC_LINKED; - } - mtx_unlock(&vmmdev_mtx); - - if (sc2 != NULL) { - vmmdev_destroy(sc); - error = EEXIST; - goto out; - } - - error = make_dev_p(MAKEDEV_CHECKNAME, &cdev, &vmmdevsw, sc->ucred, - UID_ROOT, GID_WHEEL, 0600, "vmm/%s", buf); - if (error != 0) { - vmmdev_destroy(sc); - goto out; - } - mtx_lock(&vmmdev_mtx); - sc->cdev = cdev; - sc->cdev->si_drv1 = sc; + error = vmm_add(sc); mtx_unlock(&vmmdev_mtx); + if (error != 0) + vmm_destroy(sc); out: free(buf, M_VMMDEV); @@ -1161,73 +1189,6 @@ NULL, 0, sysctl_vmm_create, "A", NULL); -static void -vmmdev_prison_cleanup(struct prison *pr) -{ - struct vmmdev_softc *sc; - struct vmmdev_softc *tsc; - - mtx_lock(&vmmdev_mtx); - - 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); -} - -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); - pr_allow_flag = prison_add_allow(NULL, "vmm", NULL, - "Allow use of vmm in a jail."); - - vmmdev_prison_slot = osd_jail_register(NULL, methods); -} - -int -vmmdev_cleanup(void) -{ - int error; - - if (SLIST_EMPTY(&head)) { - osd_jail_deregister(vmmdev_prison_slot); - error = 0; - } else { - error = EBUSY; - } - - return (error); -} - static int devmem_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t len, struct vm_object **objp, int nprot) @@ -1322,3 +1283,181 @@ dsc->cdev = NULL; dsc->sc = NULL; } + +struct fvmm { + TAILQ_HEAD(softc_list, vmmdev_softc) scs; +}; + +static int +fvmm_destroy(struct fvmm *fvmm, const char *name) +{ + struct vmmdev_softc *sc, *tsc; + int error = ENOENT; + + mtx_lock(&vmmdev_mtx); + TAILQ_FOREACH_SAFE(sc, &fvmm->scs, fvmm_next, tsc) { + if (strcmp(vm_name(sc->vm), name) == 0) { + vmm_destroy(sc); + TAILQ_REMOVE(&fvmm->scs, sc, fvmm_next); + error = 0; + } + } + mtx_unlock(&vmmdev_mtx); + + return (error); +} + +static void +fvmm_dtor(void *data) +{ + struct fvmm *fvmm = data; + struct vmmdev_softc *sc; + + mtx_lock(&vmmdev_mtx); + while ((sc = TAILQ_FIRST(&fvmm->scs))) { + TAILQ_REMOVE(&fvmm->scs, sc, fvmm_next); + vmm_destroy(sc); + } + mtx_unlock(&vmmdev_mtx); + free(fvmm, M_VMMDEV); +} + +static int +vmmctl_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + struct fvmm *fvmm; + int error; + + fvmm = malloc(sizeof(struct fvmm), M_VMMDEV, M_WAITOK | M_ZERO); + TAILQ_INIT(&fvmm->scs); + error = devfs_set_cdevpriv(fvmm, fvmm_dtor); + if (error) + fvmm_dtor(fvmm); + return (error); +} + +static int +vmmctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, + struct thread *td) +{ + struct fvmm *fvmm; + struct vmmdev_softc *sc; + struct vmmctl_op *op; + int error = 0; + + devfs_get_cdevpriv((void **)&fvmm); + + switch (cmd) { + case VMMCTL_CREATE: + op = (struct vmmctl_op *)data; + if (strnlen(op->name, VM_MAX_NAMELEN + 1) == + VM_MAX_NAMELEN + 1) { + error = EINVAL; + break; + } + error = vmm_create(op->name, &sc); + if (error) + break; + sc->persistent = false; + mtx_lock(&vmmdev_mtx); + error = vmm_add(sc); + if (error) { + vmm_destroy(sc); + mtx_unlock(&vmmdev_mtx); + break; + } + TAILQ_INSERT_HEAD(&fvmm->scs, sc, fvmm_next); + mtx_unlock(&vmmdev_mtx); + break; + case VMMCTL_DESTROY: + op = (struct vmmctl_op *)data; + if (strnlen(op->name, VM_MAX_NAMELEN + 1) == + VM_MAX_NAMELEN + 1) { + error = EINVAL; + break; + } + error = fvmm_destroy(fvmm, op->name); + break; + } + + return (error); +} + +static void +vmmdev_prison_cleanup(struct prison *pr) +{ + struct vmmdev_softc *sc; + struct vmmdev_softc *tsc; + + mtx_lock(&vmmdev_mtx); + + 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); +} + +static struct cdevsw vmmctl_cdevsw = { + .d_version = D_VERSION, + .d_open = vmmctl_open, + .d_ioctl = vmmctl_ioctl, + .d_name = "vmmctl", +}; +static struct cdev *vmmctl_dev; + +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); + pr_allow_flag = prison_add_allow(NULL, "vmm", NULL, + "Allow use of vmm in a jail."); + + vmmdev_prison_slot = osd_jail_register(NULL, methods); + + vmmctl_dev = make_dev(&vmmctl_cdevsw, 0, + UID_ROOT, GID_WHEEL, 0600, "vmmctl"); +} + +int +vmmdev_cleanup(void) +{ + int error; + + if (SLIST_EMPTY(&head)) { + osd_jail_deregister(vmmdev_prison_slot); + destroy_dev(vmmctl_dev); + error = 0; + } else { + error = EBUSY; + } + + return (error); +}