Following the discusion on misc@ and a diff from tedu@ [1], here's a bit more work cleaning up the issue in vmd(8) to prevent vmd dying if a user tries to create a vm with memory above the rlimit.
I changed tedu's diff a bit to make it less verbose and print a value that's human readable using fmt_scaled(3). It also only prints the message about data limits if the error was ENOMEM. I've also incorporated some feedback off list and follow the error condition further through vmd. Now vmctl receives a descriptive error as well. OK or feedback? An example trying to start a vm named "test": Before ====== vmctl: $ doas vmctl start -Lc -d vm/openbsd.qcow2 -m4G test vmctl: pipe closed vmd: $ doas vmd -d startup test: could not allocate guest memory - exiting: Cannot allocate memory vmm: read vcp id priv exiting, pid 36084 control exiting, pid 58866 (vmd exits, a dove cries) After ===== vmctl: $ doas vmctl start -Lc -d vm/openbsd.qcow2 -m4G test vmctl: start vm command failed: Cannot allocate memory vmd: $ doas obj/vmd -d startup test: could not allocate guest memory (data limit is 4.0G) test: failed to start vm (vmd still alive) -dv [1] https://marc.info/?l=openbsd-misc&m=164581487723923&w=2 diff e7fa9d3941282eed56a0d5808179cb0e321faae6 /usr/src blob - 4c6c99f1133cec7cb1e38dfd22e595e4d2023842 file + usr.sbin/vmd/vm.c --- usr.sbin/vmd/vm.c +++ usr.sbin/vmd/vm.c @@ -26,6 +26,7 @@ #include <sys/socket.h> #include <sys/time.h> #include <sys/mman.h> +#include <sys/resource.h> #include <dev/ic/i8253reg.h> #include <dev/isa/isareg.h> @@ -292,17 +293,24 @@ start_vm(struct vmd_vm *vm, int fd) ret = alloc_guest_mem(vcp); if (ret) { + struct rlimit lim; + char buf[FMT_SCALED_STRSIZE]; + if (ret == ENOMEM && getrlimit(RLIMIT_DATA, &lim) == 0) { + if (fmt_scaled(lim.rlim_cur, buf) == 0) + fatalx("could not allocate guest memory (data " + "limit is %s)", buf); + } errno = ret; - fatal("could not allocate guest memory - exiting"); + fatal("could not allocate guest memory"); } ret = vmm_create_vm(vcp); current_vm = vm; /* send back the kernel-generated vm id (0 on error) */ - if (write(fd, &vcp->vcp_id, sizeof(vcp->vcp_id)) != + if (atomicio(vwrite, fd, &vcp->vcp_id, sizeof(vcp->vcp_id)) != sizeof(vcp->vcp_id)) - fatal("write vcp id"); + fatal("failed to send created vm id to vmm process"); if (ret) { errno = ret; @@ -319,10 +327,9 @@ start_vm(struct vmd_vm *vm, int fd) fatal("pledge"); if (vm->vm_state & VM_STATE_RECEIVED) { - ret = read(vm->vm_receive_fd, &vrp, sizeof(vrp)); - if (ret != sizeof(vrp)) { + ret = atomicio(read, vm->vm_receive_fd, &vrp, sizeof(vrp)); + if (ret != sizeof(vrp)) fatal("received incomplete vrp - exiting"); - } vrs = vrp.vrwp_regs; } else { /* blob - a60291c17f1f5bb94572f531bdc7e6b2f6b707d5 file + usr.sbin/vmd/vmd.c --- usr.sbin/vmd/vmd.c +++ usr.sbin/vmd/vmd.c @@ -399,9 +399,9 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struc } if (vmr.vmr_result) { + log_warnx("%s: failed to start vm", vcp->vcp_name); + vm_remove(vm, __func__); errno = vmr.vmr_result; - log_warn("%s: failed to start vm", vcp->vcp_name); - vm_remove(vm, __func__); break; } blob - eb75b4c587884ec43704420ef4172386a5b39bd9 file + usr.sbin/vmd/vmm.c --- usr.sbin/vmd/vmm.c +++ usr.sbin/vmd/vmm.c @@ -51,6 +51,7 @@ #include "vmd.h" #include "vmm.h" +#include "atomicio.h" void vmm_sighdlr(int, short, void *); int vmm_start_vm(struct imsg *, uint32_t *, pid_t *); @@ -145,7 +146,7 @@ vmm_dispatch_parent(int fd, struct privsep_proc *p, st case IMSG_VMDOP_START_VM_END: res = vmm_start_vm(imsg, &id, &pid); /* Check if the ID can be mapped correctly */ - if ((id = vm_id2vmid(id, NULL)) == 0) + if (res == 0 && (id = vm_id2vmid(id, NULL)) == 0) res = ENOENT; cmd = IMSG_VMDOP_START_VM_RESPONSE; break; @@ -615,7 +616,8 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *p struct vmd_vm *vm; int ret = EINVAL; int fds[2]; - size_t i, j; + pid_t vm_pid; + size_t i, j, sz; if ((vm = vm_getbyvmid(imsg->hdr.peerid)) == NULL) { log_warnx("%s: can't find vm", __func__); @@ -635,18 +637,18 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *p fatal("socketpair"); /* Start child vmd for this VM (fork, chroot, drop privs) */ - ret = fork(); + vm_pid = fork(); /* Start child failed? - cleanup and leave */ - if (ret == -1) { + if (vm_pid == -1) { log_warnx("%s: start child failed", __func__); ret = EIO; goto err; } - if (ret > 0) { + if (vm_pid > 0) { /* Parent */ - vm->vm_pid = ret; + vm->vm_pid = vm_pid; close(fds[1]); for (i = 0 ; i < vcp->vcp_ndisks; i++) { @@ -674,10 +676,16 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *p } /* Read back the kernel-generated vm id from the child */ - if (read(fds[0], &vcp->vcp_id, sizeof(vcp->vcp_id)) != - sizeof(vcp->vcp_id)) - fatal("read vcp id"); + sz = atomicio(read, fds[0], &vcp->vcp_id, sizeof(vcp->vcp_id)); + if (sz != sizeof(vcp->vcp_id)) { + log_debug("%s: failed to receive vm id from vm %s", + __func__, vcp->vcp_name); + /* If the vm cannot allocate memory, we end up here. */ + ret = ENOMEM; + goto err; + } + /* If the vm id is 0, the vmm(4) failed to create the vm. */ if (vcp->vcp_id == 0) goto err;