From: reyk Date: Sun, 15 Jul 2018 14:36:54 +0000 (+0000) Subject: Track resources and enforce cpu/memory/interface limits for non-root users. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=382b7e55cc6fe1849fae73368aaa01ad2d4535e7;p=openbsd Track resources and enforce cpu/memory/interface limits for non-root users. The limits are currently hard-coded and undocumented (4 CPUs/VMs, 2G memory, 8 interfaces) but will be configurable in an upcoming diff. These limits are tracked in total usage; for example, a user will be able to run up to 4 VMs with 512M of memory or a single VM with 2G. OK ccardenas@ mlarkin@ --- diff --git a/usr.sbin/vmd/config.c b/usr.sbin/vmd/config.c index 5f6db1afba8..ac5f1808a67 100644 --- a/usr.sbin/vmd/config.c +++ b/usr.sbin/vmd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.47 2018/07/13 10:26:57 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.48 2018/07/15 14:36:54 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter @@ -68,6 +68,12 @@ config_init(struct vmd *env) return (-1); TAILQ_INIT(env->vmd_switches); } + if (what & CONFIG_USERS) { + if ((env->vmd_users = calloc(1, + sizeof(*env->vmd_users))) == NULL) + return (-1); + TAILQ_INIT(env->vmd_users); + } return (0); } @@ -187,7 +193,16 @@ config_setvm(struct privsep *ps, struct vmd_vm *vm, uint32_t peerid, uid_t uid) if (vm->vm_running) { log_warnx("%s: vm is already running", __func__); errno = EALREADY; - goto fail; + return (-1); + } + + /* increase the user reference counter and check user limits */ + if (vm->vm_user != NULL && user_get(vm->vm_user->usr_id.uid) != NULL) { + user_inc(vcp, vm->vm_user, 1); + if (user_checklimit(vm->vm_user, vcp) == -1) { + errno = EPERM; + goto fail; + } } diskfds = reallocarray(NULL, vcp->vcp_ndisks, sizeof(*diskfds)); diff --git a/usr.sbin/vmd/proc.h b/usr.sbin/vmd/proc.h index 323b57acbc8..f0e4704aefb 100644 --- a/usr.sbin/vmd/proc.h +++ b/usr.sbin/vmd/proc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: proc.h,v 1.13 2018/06/26 10:00:08 reyk Exp $ */ +/* $OpenBSD: proc.h,v 1.14 2018/07/15 14:36:54 reyk Exp $ */ /* * Copyright (c) 2010-2015 Reyk Floeter @@ -98,6 +98,7 @@ enum privsep_procid { #define CONFIG_RELOAD 0x00 #define CONFIG_VMS 0x01 #define CONFIG_SWITCHES 0x02 +#define CONFIG_USERS 0x04 #define CONFIG_ALL 0xff struct privsep_pipes { diff --git a/usr.sbin/vmd/vioscsi.c b/usr.sbin/vmd/vioscsi.c index f8b44a72a85..091136df597 100644 --- a/usr.sbin/vmd/vioscsi.c +++ b/usr.sbin/vmd/vioscsi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vioscsi.c,v 1.7 2018/07/10 20:43:15 reyk Exp $ */ +/* $OpenBSD: vioscsi.c,v 1.8 2018/07/15 14:36:54 reyk Exp $ */ /* * Copyright (c) 2017 Carlos Cardenas @@ -261,7 +261,7 @@ vioscsi_handle_inquiry(struct vioscsi_dev *dev, inq = (struct scsi_inquiry *)(req->cdb); inq_len = (uint16_t)_2btol(inq->length); - log_debug("%s: INQ - EVPD %d PAGE_CODE 0x%08x LEN %d", __func__, + DPRINTF("%s: INQ - EVPD %d PAGE_CODE 0x%08x LEN %d", __func__, inq->flags & SI_EVPD, inq->pagecode, inq_len); vioscsi_prepare_resp(&resp, @@ -341,7 +341,7 @@ vioscsi_handle_mode_sense(struct vioscsi_dev *dev, mode_page_ctl = mode_sense->page & SMS_PAGE_CTRL; mode_page_code = mode_sense->page & SMS_PAGE_CODE; - log_debug("%s: M_SENSE - DBD %d Page Ctrl 0x%x Code 0x%x Len %u", + DPRINTF("%s: M_SENSE - DBD %d Page Ctrl 0x%x Code 0x%x Len %u", __func__, mode_sense->byte2 & SMS_DBD, mode_page_ctl, mode_page_code, mode_sense->length); @@ -484,7 +484,7 @@ vioscsi_handle_mode_sense_big(struct vioscsi_dev *dev, mode_page_code = mode_sense_10->page & SMS_PAGE_CODE; mode_sense_len = (uint16_t)_2btol(mode_sense_10->length); - log_debug("%s: M_SENSE_10 - DBD %d Page Ctrl 0x%x Code 0x%x Len %u", + DPRINTF("%s: M_SENSE_10 - DBD %d Page Ctrl 0x%x Code 0x%x Len %u", __func__, mode_sense_10->byte2 & SMS_DBD, mode_page_ctl, mode_page_code, mode_sense_len); @@ -620,7 +620,7 @@ vioscsi_handle_read_capacity(struct vioscsi_dev *dev, memset(&resp, 0, sizeof(resp)); r_cap = (struct scsi_read_capacity *)(req->cdb); r_cap_addr = _4btol(r_cap->addr); - log_debug("%s: %s - Addr 0x%08x", __func__, + DPRINTF("%s: %s - Addr 0x%08x", __func__, vioscsi_op_names(r_cap->opcode), r_cap_addr); vioscsi_prepare_resp(&resp, @@ -633,7 +633,7 @@ vioscsi_handle_read_capacity(struct vioscsi_dev *dev, goto read_capacity_out; } - log_debug("%s: ISO has %lld bytes and %lld blocks", + DPRINTF("%s: ISO has %lld bytes and %lld blocks", __func__, dev->sz, dev->n_blocks); /* @@ -709,7 +709,7 @@ vioscsi_handle_read_capacity_16(struct vioscsi_dev *dev, memset(&resp, 0, sizeof(resp)); r_cap_16 = (struct scsi_read_capacity_16 *)(req->cdb); r_cap_addr_16 = _8btol(r_cap_16->addr); - log_debug("%s: %s - Addr 0x%016llx", __func__, + DPRINTF("%s: %s - Addr 0x%016llx", __func__, vioscsi_op_names(r_cap_16->opcode), r_cap_addr_16); vioscsi_prepare_resp(&resp, VIRTIO_SCSI_S_OK, SCSI_OK, 0, 0, 0); @@ -722,7 +722,7 @@ vioscsi_handle_read_capacity_16(struct vioscsi_dev *dev, goto read_capacity_16_out; } - log_debug("%s: ISO has %lld bytes and %lld blocks", __func__, + DPRINTF("%s: ISO has %lld bytes and %lld blocks", __func__, dev->sz, dev->n_blocks); _lto8b(dev->n_blocks - 1, r_cap_data_16->addr); @@ -785,11 +785,11 @@ vioscsi_handle_report_luns(struct vioscsi_dev *dev, rpl = (struct scsi_report_luns *)(req->cdb); rpl_length = _4btol(rpl->length); - log_debug("%s: REPORT_LUNS Report 0x%x Length %d", __func__, + DPRINTF("%s: REPORT_LUNS Report 0x%x Length %d", __func__, rpl->selectreport, rpl_length); if (rpl_length < RPL_MIN_SIZE) { - log_debug("%s: RPL_Length %d < %d (RPL_MIN_SIZE)", __func__, + DPRINTF("%s: RPL_Length %d < %d (RPL_MIN_SIZE)", __func__, rpl_length, RPL_MIN_SIZE); vioscsi_prepare_resp(&resp, @@ -886,12 +886,12 @@ vioscsi_handle_read_6(struct vioscsi_dev *dev, read_lba = ((read_6->addr[0] & SRW_TOPADDR) << 16 ) | (read_6->addr[1] << 8) | read_6->addr[2]; - log_debug("%s: READ Addr 0x%08x Len %d (%d)", + DPRINTF("%s: READ Addr 0x%08x Len %d (%d)", __func__, read_lba, read_6->length, read_6->length * dev->max_xfer); /* check if lba is in range */ if (read_lba > dev->n_blocks - 1) { - log_debug("%s: requested block out of range req: %ud max: %lld", + DPRINTF("%s: requested block out of range req: %ud max: %lld", __func__, read_lba, dev->n_blocks); vioscsi_prepare_resp(&resp, @@ -1017,12 +1017,12 @@ vioscsi_handle_read_10(struct vioscsi_dev *dev, read_10_len = _2btol(read_10->length); chunk_offset = 0; - log_debug("%s: READ_10 Addr 0x%08x Len %d (%d)", + DPRINTF("%s: READ_10 Addr 0x%08x Len %d (%d)", __func__, read_lba, read_10_len, read_10_len * dev->max_xfer); /* check if lba is in range */ if (read_lba > dev->n_blocks - 1) { - log_debug("%s: requested block out of range req: %ud max: %lld", + DPRINTF("%s: requested block out of range req: %ud max: %lld", __func__, read_lba, dev->n_blocks); vioscsi_prepare_resp(&resp, @@ -1153,9 +1153,9 @@ vioscsi_handle_prevent_allow(struct vioscsi_dev *dev, vioscsi_prepare_resp(&resp, VIRTIO_SCSI_S_OK, SCSI_OK, 0, 0, 0); if (dev->locked) { - log_debug("%s: unlocking medium", __func__); + DPRINTF("%s: unlocking medium", __func__); } else { - log_debug("%s: locking medium", __func__); + DPRINTF("%s: locking medium", __func__); } dev->locked = dev->locked ? 0 : 1; @@ -1187,7 +1187,7 @@ vioscsi_handle_mechanism_status(struct vioscsi_dev *dev, memset(&resp, 0, sizeof(resp)); mech_status = (struct scsi_mechanism_status *)(req->cdb); mech_status_len = (uint16_t)_2btol(mech_status->length); - log_debug("%s: MECH_STATUS Len %u", __func__, mech_status_len); + DPRINTF("%s: MECH_STATUS Len %u", __func__, mech_status_len); mech_status_header = calloc(1, sizeof(*mech_status_header)); @@ -1247,7 +1247,7 @@ vioscsi_handle_read_toc(struct vioscsi_dev *dev, memset(&resp, 0, sizeof(resp)); toc = (struct scsi_read_toc *)(req->cdb); toc_len = (uint16_t)_2btol(toc->data_len); - log_debug("%s: %s - MSF %d Track 0x%02x Addr 0x%04x", + DPRINTF("%s: %s - MSF %d Track 0x%02x Addr 0x%04x", __func__, vioscsi_op_names(toc->opcode), ((toc->byte2 >> 1) & 1), toc->from_track, toc_len); @@ -1257,7 +1257,7 @@ vioscsi_handle_read_toc(struct vioscsi_dev *dev, if (toc->from_track > 1 && toc->from_track != READ_TOC_LEAD_OUT_TRACK) { /* illegal request */ - log_debug("%s: illegal request Track 0x%02x", + DPRINTF("%s: illegal request Track 0x%02x", __func__, toc->from_track); vioscsi_prepare_resp(&resp, @@ -1385,7 +1385,7 @@ vioscsi_handle_read_disc_info(struct vioscsi_dev *dev, memset(&resp, 0, sizeof(resp)); read_disc = (struct scsi_read_disc_information *)(req->cdb); - log_debug("%s: Disc Info %x", __func__, read_disc->byte2); + DPRINTF("%s: Disc Info %x", __func__, read_disc->byte2); /* send back unsupported */ vioscsi_prepare_resp(&resp, @@ -1424,7 +1424,7 @@ vioscsi_handle_gesn(struct vioscsi_dev *dev, memset(&resp, 0, sizeof(resp)); gesn = (struct scsi_gesn *)(req->cdb); - log_debug("%s: GESN Method %s", __func__, + DPRINTF("%s: GESN Method %s", __func__, gesn->byte2 ? "Polling" : "Asynchronous"); if (gesn->byte2 == 0) { @@ -1530,7 +1530,7 @@ vioscsi_handle_get_config(struct vioscsi_dev *dev, get_configuration = (struct scsi_get_configuration *)(req->cdb); get_conf_feature = (uint16_t)_2btol(get_configuration->feature); get_conf_len = (uint16_t)_2btol(get_configuration->length); - log_debug("%s: Conf RT %x Feature %d Len %d", __func__, + DPRINTF("%s: Conf RT %x Feature %d Len %d", __func__, get_configuration->byte2, get_conf_feature, get_conf_len); get_conf_reply = (uint8_t*)calloc(G_CONFIG_REPLY_SIZE, sizeof(uint8_t)); @@ -1664,7 +1664,7 @@ vioscsi_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, *intr = 0xFF; - log_debug("%s: request %s reg %u,%s sz %u", __func__, + DPRINTF("%s: request %s reg %u,%s sz %u", __func__, dir ? "READ" : "WRITE", reg, vioscsi_reg_name(reg), sz); if (dir == 0) { @@ -1677,7 +1677,7 @@ vioscsi_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, break; case VIRTIO_CONFIG_GUEST_FEATURES: dev->cfg.guest_feature = *data; - log_debug("%s: guest feature set to %u", + DPRINTF("%s: guest feature set to %u", __func__, dev->cfg.guest_feature); break; case VIRTIO_CONFIG_QUEUE_ADDRESS: @@ -1695,10 +1695,10 @@ vioscsi_io(int dir, uint16_t reg, uint32_t *data, uint8_t *intr, break; case VIRTIO_CONFIG_DEVICE_STATUS: dev->cfg.device_status = *data; - log_debug("%s: device status set to %u", + DPRINTF("%s: device status set to %u", __func__, dev->cfg.device_status); if (dev->cfg.device_status == 0) { - log_debug("%s: device reset", __func__); + DPRINTF("%s: device reset", __func__); dev->cfg.guest_feature = 0; dev->cfg.queue_address = 0; vioscsi_update_qa(dev); @@ -2156,7 +2156,7 @@ vioscsi_notifyq(struct vioscsi_dev *dev) * respond with a BAD_TARGET response. */ if (req.lun[1] >= VIOSCSI_MAX_TARGET || req.lun[3] > 0) { - log_debug("%s: Ignore CMD 0x%02x,%s on lun %u:%u:%u:%u", + DPRINTF("%s: Ignore CMD 0x%02x,%s on lun %u:%u:%u:%u", __func__, req.cdb[0], vioscsi_op_names(req.cdb[0]), req.lun[0], req.lun[1], req.lun[2], req.lun[3]); /* Move index for response */ @@ -2187,7 +2187,7 @@ vioscsi_notifyq(struct vioscsi_dev *dev) goto next_msg; } - log_debug("%s: Queue %d id 0x%llx lun %u:%u:%u:%u" + DPRINTF("%s: Queue %d id 0x%llx lun %u:%u:%u:%u" " cdb OP 0x%02x,%s", __func__, dev->cfg.queue_notify, req.id, req.lun[0], req.lun[1], req.lun[2], req.lun[3], diff --git a/usr.sbin/vmd/vmd.c b/usr.sbin/vmd/vmd.c index da184629dc5..2b07a8463b7 100644 --- a/usr.sbin/vmd/vmd.c +++ b/usr.sbin/vmd/vmd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vmd.c,v 1.97 2018/07/13 10:26:57 reyk Exp $ */ +/* $OpenBSD: vmd.c,v 1.98 2018/07/15 14:36:54 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1090,6 +1091,9 @@ vm_stop(struct vmd_vm *vm, int keeptty, const char *caller) vm->vm_running = 0; vm->vm_shutdown = 0; + user_inc(&vm->vm_params.vmc_params, vm->vm_user, 0); + user_put(vm->vm_user); + if (vm->vm_iev.ibuf.fd != -1) { event_del(&vm->vm_iev.ev); close(vm->vm_iev.ibuf.fd); @@ -1140,6 +1144,7 @@ vm_remove(struct vmd_vm *vm, const char *caller) TAILQ_REMOVE(env->vmd_vms, vm, vm_entry); + user_put(vm->vm_user); vm_stop(vm, 0, caller); free(vm); } @@ -1151,6 +1156,7 @@ vm_register(struct privsep *ps, struct vmop_create_params *vmc, struct vmd_vm *vm = NULL, *vm_parent = NULL; struct vm_create_params *vcp = &vmc->vmc_params; struct vmop_owner *vmo = NULL; + struct vmd_user *usr = NULL; uint32_t rng; unsigned int i; struct vmd_switch *sw; @@ -1223,6 +1229,13 @@ vm_register(struct privsep *ps, struct vmop_create_params *vmc, } } + /* track active users */ + if (uid != 0 && env->vmd_users != NULL && + (usr = user_get(uid)) == NULL) { + log_warnx("could not add user"); + goto fail; + } + if ((vm = calloc(1, sizeof(*vm))) == NULL) goto fail; @@ -1233,6 +1246,7 @@ vm_register(struct privsep *ps, struct vmop_create_params *vmc, vm->vm_tty = -1; vm->vm_receive_fd = -1; vm->vm_paused = 0; + vm->vm_user = usr; for (i = 0; i < vcp->vcp_ndisks; i++) vm->vm_disks[i] = -1; @@ -1765,6 +1779,104 @@ switch_getbyname(const char *name) return (NULL); } +struct vmd_user * +user_get(uid_t uid) +{ + struct vmd_user *usr; + + if (uid == 0) + return (NULL); + + /* first try to find an existing user */ + TAILQ_FOREACH(usr, env->vmd_users, usr_entry) { + if (usr->usr_id.uid == uid) + goto done; + } + + if ((usr = calloc(1, sizeof(*usr))) == NULL) { + log_warn("could not allocate user"); + return (NULL); + } + + usr->usr_id.uid = uid; + usr->usr_id.gid = -1; + TAILQ_INSERT_TAIL(env->vmd_users, usr, usr_entry); + + done: + DPRINTF("%s: uid %d #%d +", + __func__, usr->usr_id.uid, usr->usr_refcnt + 1); + usr->usr_refcnt++; + + return (usr); +} + +void +user_put(struct vmd_user *usr) +{ + if (usr == NULL) + return; + + DPRINTF("%s: uid %d #%d -", + __func__, usr->usr_id.uid, usr->usr_refcnt - 1); + + if (--usr->usr_refcnt > 0) + return; + + TAILQ_REMOVE(env->vmd_users, usr, usr_entry); + free(usr); +} + +void +user_inc(struct vm_create_params *vcp, struct vmd_user *usr, int inc) +{ + char mem[FMT_SCALED_STRSIZE]; + + if (usr == NULL) + return; + + /* increment or decrement counters */ + inc = inc ? 1 : -1; + + usr->usr_maxcpu += vcp->vcp_ncpus * inc; + usr->usr_maxmem += vcp->vcp_memranges[0].vmr_size * inc; + usr->usr_maxifs += vcp->vcp_nnics * inc; + + if (log_getverbose() > 1) { + (void)fmt_scaled(usr->usr_maxmem * 1024 * 1024, mem); + log_debug("%s: %c uid %d ref %d cpu %llu mem %s ifs %llu", + __func__, inc == 1 ? '+' : '-', + usr->usr_id.uid, usr->usr_refcnt, + usr->usr_maxcpu, mem, usr->usr_maxifs); + } +} + +int +user_checklimit(struct vmd_user *usr, struct vm_create_params *vcp) +{ + const char *limit = ""; + + /* XXX make the limits configurable */ + if (usr->usr_maxcpu > VM_DEFAULT_USER_MAXCPU) { + limit = "cpu "; + goto fail; + } + if (usr->usr_maxcpu > VM_DEFAULT_USER_MAXMEM) { + limit = "memory "; + goto fail; + } + if (usr->usr_maxifs > VM_DEFAULT_USER_MAXIFS) { + limit = "interface "; + goto fail; + } + + return (0); + + fail: + log_warnx("%s: user %d %slimit reached", vcp->vcp_name, + usr->usr_id.uid, limit); + return (-1); +} + char * get_string(uint8_t *ptr, size_t len) { diff --git a/usr.sbin/vmd/vmd.h b/usr.sbin/vmd/vmd.h index bf670f5eb95..2b83bb42c71 100644 --- a/usr.sbin/vmd/vmd.h +++ b/usr.sbin/vmd/vmd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: vmd.h,v 1.77 2018/07/13 10:26:57 reyk Exp $ */ +/* $OpenBSD: vmd.h,v 1.78 2018/07/15 14:36:54 reyk Exp $ */ /* * Copyright (c) 2015 Mike Larkin @@ -54,6 +54,11 @@ #define VMD_SWITCH_TYPE "bridge" #define VM_DEFAULT_MEMORY 512 +/* default user instance limits */ +#define VM_DEFAULT_USER_MAXCPU 4 +#define VM_DEFAULT_USER_MAXMEM 2048 +#define VM_DEFAULT_USER_MAXIFS 8 + /* vmd -> vmctl error codes */ #define VMD_BIOS_MISSING 1001 #define VMD_DISK_MISSING 1002 @@ -243,11 +248,23 @@ struct vmd_vm { int vm_received; int vm_paused; int vm_receive_fd; + struct vmd_user *vm_user; TAILQ_ENTRY(vmd_vm) vm_entry; }; TAILQ_HEAD(vmlist, vmd_vm); +struct vmd_user { + struct vmop_owner usr_id; + uint64_t usr_maxcpu; + uint64_t usr_maxmem; + uint64_t usr_maxifs; + int usr_refcnt; + + TAILQ_ENTRY(vmd_user) usr_entry; +}; +TAILQ_HEAD(userlist, vmd_user); + struct address { struct sockaddr_storage ss; int prefixlen; @@ -274,6 +291,7 @@ struct vmd { struct vmlist *vmd_vms; uint32_t vmd_nswitches; struct switchlist *vmd_switches; + struct userlist *vmd_users; int vmd_fd; int vmd_ptmfd; @@ -329,6 +347,10 @@ int vm_opentty(struct vmd_vm *); void vm_closetty(struct vmd_vm *); void switch_remove(struct vmd_switch *); struct vmd_switch *switch_getbyname(const char *); +struct vmd_user *user_get(uid_t); +void user_put(struct vmd_user *); +void user_inc(struct vm_create_params *, struct vmd_user *, int); +int user_checklimit(struct vmd_user *, struct vm_create_params *); char *get_string(uint8_t *, size_t); uint32_t prefixlen2mask(uint8_t);