diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2016-10-04 17:17:31 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2016-10-04 17:17:31 +0000 |
commit | 9290b4b7508c036af2be8c47e19a7fb7d8d9ca18 (patch) | |
tree | f646cce0d09b3563559ad8d034284522a5d1ea1e /usr.sbin/vmd | |
parent | 9172e696a13a394f11feeeae637c38e50925e8ac (diff) |
Add a new "priv" process that is responsible for ioctls and restricted
operations that aren't allowed under pledge. This is a companion to
the "vmd" process that runs as root but with pledge.
With the "priv" process, each new tap(4) interface now gets a
description to indicate the vm, eg. "vm1-if0-myvm". For network
configuration will be done by vmd/priv later.
OK mlarkin@
Diffstat (limited to 'usr.sbin/vmd')
-rw-r--r-- | usr.sbin/vmd/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/vmd/config.c | 41 | ||||
-rw-r--r-- | usr.sbin/vmd/priv.c | 173 | ||||
-rw-r--r-- | usr.sbin/vmd/proc.c | 12 | ||||
-rw-r--r-- | usr.sbin/vmd/proc.h | 6 | ||||
-rw-r--r-- | usr.sbin/vmd/vmd.c | 39 | ||||
-rw-r--r-- | usr.sbin/vmd/vmd.h | 25 | ||||
-rw-r--r-- | usr.sbin/vmd/vmm.c | 16 |
8 files changed, 282 insertions, 34 deletions
diff --git a/usr.sbin/vmd/Makefile b/usr.sbin/vmd/Makefile index 2df31af8f3f..3fe88e58824 100644 --- a/usr.sbin/vmd/Makefile +++ b/usr.sbin/vmd/Makefile @@ -1,10 +1,10 @@ -# $OpenBSD: Makefile,v 1.8 2016/09/01 15:38:03 mlarkin Exp $ +# $OpenBSD: Makefile,v 1.9 2016/10/04 17:17:30 reyk Exp $ .if ${MACHINE} == "amd64" PROG= vmd SRCS= vmm.c loadfile_elf.c pci.c virtio.c i8259.c mc146818.c -SRCS+= vmd.c control.c log.c proc.c config.c ns8250.c i8253.c +SRCS+= vmd.c control.c log.c priv.c proc.c config.c ns8250.c i8253.c SRCS+= parse.y CFLAGS+= -Wall -I${.CURDIR} diff --git a/usr.sbin/vmd/config.c b/usr.sbin/vmd/config.c index c6ced578967..e3de3abbd35 100644 --- a/usr.sbin/vmd/config.c +++ b/usr.sbin/vmd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.12 2016/09/29 22:42:04 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.13 2016/10/04 17:17:30 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org> @@ -20,6 +20,9 @@ #include <sys/queue.h> #include <sys/time.h> #include <sys/uio.h> +#include <sys/socket.h> + +#include <net/if.h> #include <stdio.h> #include <stdlib.h> @@ -44,6 +47,7 @@ config_init(struct vmd *env) /* Global configuration */ ps->ps_what[PROC_PARENT] = CONFIG_ALL; ps->ps_what[PROC_VMM] = CONFIG_VMS; + ps->ps_what[PROC_PRIV] = 0; /* Other configuration */ what = ps->ps_what[privsep_process]; @@ -110,6 +114,8 @@ config_getvm(struct privsep *ps, struct vm_create_params *vcp, int fd, ttys_fd; int kernfd = -1, *diskfds = NULL, *tapfds = NULL; int saved_errno = 0; + char ptyname[VM_TTYNAME_MAX]; + char ifname[IF_NAMESIZE]; errno = 0; @@ -149,13 +155,15 @@ config_getvm(struct privsep *ps, struct vm_create_params *vcp, TAILQ_INSERT_TAIL(env->vmd_vms, vm, vm_entry); - if (privsep_process != PROC_PARENT) { + switch (privsep_process) { + case PROC_VMM: if (kernel_fd == -1) { log_debug("invalid kernel fd"); goto fail; } vm->vm_kernel = kernel_fd; - } else { + break; + case PROC_PARENT: diskfds = reallocarray(NULL, vcp->vcp_ndisks, sizeof(*diskfds)); if (diskfds == NULL) { saved_errno = errno; @@ -197,16 +205,22 @@ config_getvm(struct privsep *ps, struct vm_create_params *vcp, /* Open disk network interfaces */ for (i = 0 ; i < vcp->vcp_nnics; i++) { - if ((tapfds[i] = opentap()) == -1) { + if ((tapfds[i] = opentap(ifname)) == -1) { saved_errno = errno; log_warn("%s: can't open tap", __func__); goto fail; } + + if ((vm->vm_ifnames[i] = strdup(ifname)) == NULL) { + saved_errno = errno; + log_warn("%s: can't save ifname", __func__); + goto fail; + } } /* Open TTY */ - if (openpty(&fd, &ttys_fd, - vm->vm_ttyname, NULL, NULL) == -1) { + if (openpty(&fd, &ttys_fd, ptyname, NULL, NULL) == -1 || + (vm->vm_ttyname = strdup(ptyname)) == NULL) { saved_errno = errno; log_warn("%s: can't open tty", __func__); goto fail; @@ -229,8 +243,10 @@ config_getvm(struct privsep *ps, struct vm_create_params *vcp, } proc_compose_imsg(ps, PROC_VMM, -1, - IMSG_VMDOP_START_VM_END, vm->vm_vmid, fd, - vcp, sizeof(*vcp)); + IMSG_VMDOP_START_VM_END, vm->vm_vmid, fd, NULL, 0); + break; + default: + fatalx("vm received by invalid process"); } free(diskfds); @@ -303,10 +319,15 @@ config_getif(struct privsep *ps, struct imsg *imsg) if (n >= vm->vm_params.vcp_nnics || vm->vm_ifs[n] != -1 || imsg->fd == -1) { log_debug("invalid interface id"); - errno = EINVAL; - return (-1); + goto fail; } vm->vm_ifs[n] = imsg->fd; return (0); + fail: + if (imsg->fd != -1) + close(imsg->fd); + errno = EINVAL; + return (-1); + } diff --git a/usr.sbin/vmd/priv.c b/usr.sbin/vmd/priv.c new file mode 100644 index 00000000000..987b38c71e7 --- /dev/null +++ b/usr.sbin/vmd/priv.c @@ -0,0 +1,173 @@ +/* $OpenBSD: priv.c,v 1.1 2016/10/04 17:17:30 reyk Exp $ */ + +/* + * Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> /* nitems */ +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/ioctl.h> +#include <sys/tree.h> + +#include <net/if.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> + +#include "proc.h" +#include "vmd.h" + +int priv_dispatch_parent(int, struct privsep_proc *, struct imsg *); +void priv_run(struct privsep *, struct privsep_proc *, void *); + +int priv_getiftype(char *, char *, unsigned int *); +int priv_findname(const char *, const char **); + +static struct privsep_proc procs[] = { + { "parent", PROC_PARENT, priv_dispatch_parent } +}; + +void +priv(struct privsep *ps, struct privsep_proc *p) +{ + proc_run(ps, p, procs, nitems(procs), priv_run, NULL); +} + +void +priv_run(struct privsep *ps, struct privsep_proc *p, void *arg) +{ + struct vmd *env = ps->ps_env; + + /* + * no pledge(2) in the "priv" process: + * write ioctls are not permitted by pledge. + */ + + /* Open our own socket for generic interface ioctls */ + if ((env->vmd_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + fatal("socket"); +} + +int +priv_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + const char *desct[] = { "tap", "switch", "bridge", NULL }; + struct privsep *ps = p->p_ps; + struct vmop_ifreq vfr; + struct vmd *env = ps->ps_env; + struct ifreq ifr; + char type[IF_NAMESIZE]; + unsigned int unit; + + switch (imsg->hdr.type) { + case IMSG_VMDOP_PRIV_IFDESCR: + IMSG_SIZE_CHECK(imsg, &vfr); + memcpy(&vfr, imsg->data, sizeof(vfr)); + + /* We should not get malicious requests from the parent */ + if (priv_getiftype(vfr.vfr_name, type, &unit) == -1 || + priv_findname(type, desct) == -1) + fatalx("%s: rejected priv operation on interface: %s", + __func__, vfr.vfr_name); + + strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (caddr_t)vfr.vfr_value; + if (ioctl(env->vmd_fd, SIOCSIFDESCR, &ifr) < 0) + log_warn("SIOCSIFDESCR"); + break; + default: + return (-1); + } + + return (0); +} + +int +priv_getiftype(char *ifname, char *type, unsigned int *unitptr) +{ + const char *errstr; + size_t span; + unsigned int unit; + + /* Extract the name part */ + span = strcspn(ifname, "0123456789"); + if (span == 0 || span >= strlen(ifname) || span >= (IF_NAMESIZE - 1)) + return (-1); + memcpy(type, ifname, span); + type[span] = 0; + + /* Now parse the unit (we don't strictly validate the format here) */ + unit = strtonum(ifname + span, 0, UINT_MAX, &errstr); + if (errstr != NULL) + return (-1); + if (unitptr != NULL) + *unitptr = unit; + + return (0); +} + +int +priv_findname(const char *name, const char **names) +{ + unsigned int i; + + for (i = 0; names[i] != NULL; i++) { + if (strcmp(name, names[i]) == 0) + return (0); + } + + return (-1); +} + +/* + * Called from the process peer + */ + +int +vm_priv_ifconfig(struct privsep *ps, struct vmd_vm *vm) +{ + struct vm_create_params *vcp = &vm->vm_params; + unsigned int i; + struct vmop_ifreq vfr; + + for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) { + if (vm->vm_ifnames[i] == NULL) + break; + + if (strlcpy(vfr.vfr_name, vm->vm_ifnames[i], + sizeof(vfr.vfr_name)) >= sizeof(vfr.vfr_name)) + return (-1); + + /* Description can be truncated */ + (void)snprintf(vfr.vfr_value, sizeof(vfr.vfr_value), + "vm%u-if%u-%s", vm->vm_vmid, i, vcp->vcp_name); + + proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFDESCR, + &vfr, sizeof(vfr)); + + /* XXX Add interface to bridge/switch */ + } + + return (0); +} diff --git a/usr.sbin/vmd/proc.c b/usr.sbin/vmd/proc.c index 5e473717b2b..52ab19c2ef8 100644 --- a/usr.sbin/vmd/proc.c +++ b/usr.sbin/vmd/proc.c @@ -1,7 +1,7 @@ -/* $OpenBSD: proc.c,v 1.6 2016/09/29 22:42:04 reyk Exp $ */ +/* $OpenBSD: proc.c,v 1.7 2016/10/04 17:17:30 reyk Exp $ */ /* - * Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2010 - 2016 Reyk Floeter <reyk@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any @@ -498,7 +498,7 @@ proc_run(struct privsep *ps, struct privsep_proc *p, struct privsep_proc *procs, unsigned int nproc, void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg) { - struct passwd *pw = ps->ps_pw; + struct passwd *pw; const char *root; struct control_sock *rcs; @@ -518,6 +518,12 @@ proc_run(struct privsep *ps, struct privsep_proc *p, fatalx(__func__); } + /* Use non-standard user */ + if (p->p_pw != NULL) + pw = p->p_pw; + else + pw = ps->ps_pw; + /* Change root directory */ if (p->p_chroot != NULL) root = p->p_chroot; diff --git a/usr.sbin/vmd/proc.h b/usr.sbin/vmd/proc.h index b8ebdcc5085..053d9a527f5 100644 --- a/usr.sbin/vmd/proc.h +++ b/usr.sbin/vmd/proc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: proc.h,v 1.7 2016/09/29 22:42:04 reyk Exp $ */ +/* $OpenBSD: proc.h,v 1.8 2016/10/04 17:17:30 reyk Exp $ */ /* * Copyright (c) 2010-2015 Reyk Floeter <reyk@openbsd.org> @@ -89,6 +89,7 @@ enum privsep_procid { PROC_PARENT = 0, PROC_CONTROL, PROC_VMM, + PROC_PRIV, PROC_MAX, } privsep_process; @@ -135,9 +136,10 @@ struct privsep_proc { struct imsg *); void (*p_init)(struct privsep *, struct privsep_proc *); + void (*p_shutdown)(void); const char *p_chroot; + struct passwd *p_pw; struct privsep *p_ps; - void (*p_shutdown)(void); }; struct privsep_fd { diff --git a/usr.sbin/vmd/vmd.c b/usr.sbin/vmd/vmd.c index c2c12db5e91..c4aadb8419d 100644 --- a/usr.sbin/vmd/vmd.c +++ b/usr.sbin/vmd/vmd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vmd.c,v 1.30 2016/09/29 22:42:04 reyk Exp $ */ +/* $OpenBSD: vmd.c,v 1.31 2016/10/04 17:17:30 reyk Exp $ */ /* * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org> @@ -49,10 +49,17 @@ int vmd_dispatch_vmm(int, struct privsep_proc *, struct imsg *); struct vmd *env; static struct privsep_proc procs[] = { + /* Keep "priv" on top as procs[0] */ + { "priv", PROC_PRIV, NULL, priv }, { "control", PROC_CONTROL, vmd_dispatch_control, control }, { "vmm", PROC_VMM, vmd_dispatch_vmm, vmm }, + }; +/* For the privileged process */ +static struct privsep_proc *proc_priv = &procs[0]; +static struct passwd proc_privpw; + int vmd_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg) { @@ -176,10 +183,18 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg) errno = vmr.vmr_result; log_warn("%s: failed to start vm", vcp->vcp_name); vm_remove(vm); - } else { - log_info("%s: started vm %d successfully, tty %s", - vcp->vcp_name, vcp->vcp_id, vm->vm_ttyname); + break; + } + + /* Now configure all the interfaces */ + if (vm_priv_ifconfig(ps, vm) == -1) { + log_warn("%s: failed to configure vm", vcp->vcp_name); + vm_remove(vm); + break; } + + log_info("%s: started vm %d successfully, tty %s", + vcp->vcp_name, vcp->vcp_id, vm->vm_ttyname); break; case IMSG_VMDOP_TERMINATE_VM_RESPONSE: case IMSG_VMDOP_TERMINATE_VM_EVENT: @@ -196,9 +211,10 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg) case IMSG_VMDOP_GET_INFO_VM_DATA: IMSG_SIZE_CHECK(imsg, &vir); memcpy(&vir, imsg->data, sizeof(vir)); - if ((vm = vm_getbyid(vir.vir_info.vir_id)) != NULL) + if ((vm = vm_getbyid(vir.vir_info.vir_id)) != NULL) { (void)strlcpy(vir.vir_ttyname, vm->vm_ttyname, sizeof(vir.vir_ttyname)); + } if (proc_compose_imsg(ps, PROC_CONTROL, -1, imsg->hdr.type, imsg->hdr.peerid, -1, &vir, sizeof(vir)) == -1) { vm_remove(vm); @@ -324,6 +340,7 @@ main(int argc, char **argv) ps = &env->vmd_ps; ps->ps_env = env; + env->vmd_fd = -1; if (config_init(env) == -1) fatal("failed to initialize configuration"); @@ -331,9 +348,9 @@ main(int argc, char **argv) if ((ps->ps_pw = getpwnam(VMD_USER)) == NULL) fatal("unknown user %s", VMD_USER); - /* Configure the control socket */ - ps->ps_csock.cs_name = SOCKET_NAME; - TAILQ_INIT(&ps->ps_rcsocks); + /* First proc runs as root without pledge but in default chroot */ + proc_priv->p_pw = &proc_privpw; /* initialized to all 0 */ + proc_priv->p_chroot = ps->ps_pw->pw_dir; /* from VMD_USER */ /* Open /dev/vmm */ if (env->vmd_noaction == 0) { @@ -342,6 +359,10 @@ main(int argc, char **argv) fatal("%s", VMM_NODE); } + /* Configure the control socket */ + ps->ps_csock.cs_name = SOCKET_NAME; + TAILQ_INIT(&ps->ps_rcsocks); + /* Configuration will be parsed after forking the children */ env->vmd_conffile = conffile; @@ -517,12 +538,14 @@ vm_remove(struct vmd_vm *vm) for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) { if (vm->vm_ifs[i] != -1) close(vm->vm_ifs[i]); + free(vm->vm_ifnames[i]); } if (vm->vm_kernel != -1) close(vm->vm_kernel); if (vm->vm_tty != -1) close(vm->vm_tty); + free(vm->vm_ttyname); free(vm); } diff --git a/usr.sbin/vmd/vmd.h b/usr.sbin/vmd/vmd.h index 8aacb0dc414..472b2d970d3 100644 --- a/usr.sbin/vmd/vmd.h +++ b/usr.sbin/vmd/vmd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: vmd.h,v 1.25 2016/09/29 22:42:04 reyk Exp $ */ +/* $OpenBSD: vmd.h,v 1.26 2016/10/04 17:17:30 reyk Exp $ */ /* * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> @@ -18,9 +18,12 @@ #include <sys/types.h> #include <sys/queue.h> +#include <sys/socket.h> #include <machine/vmmvar.h> +#include <net/if.h> + #include <limits.h> #include <pthread.h> @@ -34,7 +37,7 @@ #define SOCKET_NAME "/var/run/vmd.sock" #define VMM_NODE "/dev/vmm" #define VM_NAME_MAX 64 -#define VM_TTYNAME_MAX 12 +#define VM_TTYNAME_MAX 16 #define MAX_TAP 256 #define NR_BACKLOG 5 @@ -52,12 +55,13 @@ enum imsg_type { IMSG_VMDOP_START_VM_RESPONSE, IMSG_VMDOP_TERMINATE_VM_REQUEST, IMSG_VMDOP_TERMINATE_VM_RESPONSE, + IMSG_VMDOP_TERMINATE_VM_EVENT, IMSG_VMDOP_GET_INFO_VM_REQUEST, IMSG_VMDOP_GET_INFO_VM_DATA, IMSG_VMDOP_GET_INFO_VM_END_DATA, IMSG_VMDOP_LOAD, IMSG_VMDOP_RELOAD, - IMSG_VMDOP_TERMINATE_VM_EVENT + IMSG_VMDOP_PRIV_IFDESCR }; struct vmop_result { @@ -77,6 +81,12 @@ struct vmop_id { char vid_name[VMM_MAX_NAME_LEN]; }; +struct vmop_ifreq { + uint32_t vfr_id; + char vfr_name[IF_NAMESIZE]; + char vfr_value[VM_NAME_MAX]; +}; + struct vmd_vm { struct vm_create_params vm_params; pid_t vm_pid; @@ -84,7 +94,8 @@ struct vmd_vm { int vm_kernel; int vm_disks[VMM_MAX_DISKS_PER_VM]; int vm_ifs[VMM_MAX_NICS_PER_VM]; - char vm_ttyname[VM_TTYNAME_MAX]; + char *vm_ifnames[VMM_MAX_NICS_PER_VM]; + char *vm_ttyname; int vm_tty; uint32_t vm_peerid; TAILQ_ENTRY(vmd_vm) vm_entry; @@ -115,11 +126,15 @@ struct vmd_vm *vm_getbypid(pid_t); void vm_remove(struct vmd_vm *); char *get_string(uint8_t *, size_t); +/* priv.c */ +void priv(struct privsep *, struct privsep_proc *); +int vm_priv_ifconfig(struct privsep *, struct vmd_vm *); + /* vmm.c */ void vmm(struct privsep *, struct privsep_proc *); int write_mem(paddr_t, void *buf, size_t); int read_mem(paddr_t, void *buf, size_t); -int opentap(void); +int opentap(char *); int fd_hasdata(int); void mutex_lock(pthread_mutex_t *); void mutex_unlock(pthread_mutex_t *); diff --git a/usr.sbin/vmd/vmm.c b/usr.sbin/vmd/vmm.c index f82294c816a..972cbdea3b0 100644 --- a/usr.sbin/vmd/vmm.c +++ b/usr.sbin/vmd/vmm.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vmm.c,v 1.45 2016/09/29 22:42:04 reyk Exp $ */ +/* $OpenBSD: vmm.c,v 1.46 2016/10/04 17:17:30 reyk Exp $ */ /* * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> @@ -34,6 +34,8 @@ #include <machine/specialreg.h> #include <machine/vmmvar.h> +#include <net/if.h> + #include <errno.h> #include <event.h> #include <fcntl.h> @@ -63,7 +65,7 @@ io_fn_t ioports_map[MAX_PORTS]; void vmm_sighdlr(int, short, void *); int start_client_vmd(void); -int opentap(void); +int opentap(char *); int start_vm(struct imsg *, uint32_t *); int terminate_vm(struct vm_terminate_params *); int get_info_vm(struct privsep *, struct imsg *, int); @@ -374,11 +376,14 @@ terminate_vm(struct vm_terminate_params *vtp) * * Opens the next available tap device, up to MAX_TAP. * + * Parameters + * ifname: an optional buffer of at least IF_NAMESIZE bytes. + * * Returns a file descriptor to the tap node opened, or -1 if no tap * devices were available. */ int -opentap(void) +opentap(char *ifname) { int i, fd; char path[PATH_MAX]; @@ -386,8 +391,11 @@ opentap(void) for (i = 0; i < MAX_TAP; i++) { snprintf(path, PATH_MAX, "/dev/tap%d", i); fd = open(path, O_RDWR | O_NONBLOCK); - if (fd != -1) + if (fd != -1) { + if (ifname != NULL) + snprintf(ifname, IF_NAMESIZE, "tap%d", i); return (fd); + } } return (-1); |