/* $OpenBSD: vmt.c,v 1.18 2020/05/29 04:42:25 deraadt Exp $ */ /* * Copyright (c) 2007 David Crawshaw * Copyright (c) 2008 David Gwynne * * 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. */ #if !defined(__i386__) && !defined(__amd64__) #error vmt(4) is only supported on i386 and amd64 #endif /* * Protocol reverse engineered by Ken Kato: * https://sites.google.com/site/chitchatvmback/backdoor */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "The" magic number, always occupies the EAX register. */ #define VM_MAGIC 0x564D5868 /* Port numbers, passed on EDX.LOW . */ #define VM_PORT_CMD 0x5658 #define VM_PORT_RPC 0x5659 /* Commands, passed on ECX.LOW. */ #define VM_CMD_GET_SPEED 0x01 #define VM_CMD_APM 0x02 #define VM_CMD_GET_MOUSEPOS 0x04 #define VM_CMD_SET_MOUSEPOS 0x05 #define VM_CMD_GET_CLIPBOARD_LEN 0x06 #define VM_CMD_GET_CLIPBOARD 0x07 #define VM_CMD_SET_CLIPBOARD_LEN 0x08 #define VM_CMD_SET_CLIPBOARD 0x09 #define VM_CMD_GET_VERSION 0x0a #define VM_VERSION_UNMANAGED 0x7fffffff #define VM_CMD_GET_DEVINFO 0x0b #define VM_CMD_DEV_ADDREMOVE 0x0c #define VM_CMD_GET_GUI_OPTIONS 0x0d #define VM_CMD_SET_GUI_OPTIONS 0x0e #define VM_CMD_GET_SCREEN_SIZE 0x0f #define VM_CMD_GET_HWVER 0x11 #define VM_CMD_POPUP_OSNOTFOUND 0x12 #define VM_CMD_GET_BIOS_UUID 0x13 #define VM_CMD_GET_MEM_SIZE 0x14 /*#define VM_CMD_GET_TIME 0x17 */ /* deprecated */ #define VM_CMD_RPC 0x1e #define VM_CMD_GET_TIME_FULL 0x2e /* RPC sub-commands, passed on ECX.HIGH. */ #define VM_RPC_OPEN 0x00 #define VM_RPC_SET_LENGTH 0x01 #define VM_RPC_SET_DATA 0x02 #define VM_RPC_GET_LENGTH 0x03 #define VM_RPC_GET_DATA 0x04 #define VM_RPC_GET_END 0x05 #define VM_RPC_CLOSE 0x06 /* RPC magic numbers, passed on EBX. */ #define VM_RPC_OPEN_RPCI 0x49435052UL /* with VM_RPC_OPEN. */ #define VM_RPC_OPEN_TCLO 0x4F4C4354UL /* with VP_RPC_OPEN. */ #define VM_RPC_ENH_DATA 0x00010000UL /* with enhanced RPC data calls. */ #define VM_RPC_FLAG_COOKIE 0x80000000UL /* RPC reply flags */ #define VM_RPC_REPLY_SUCCESS 0x0001 #define VM_RPC_REPLY_DORECV 0x0002 /* incoming message available */ #define VM_RPC_REPLY_CLOSED 0x0004 /* RPC channel is closed */ #define VM_RPC_REPLY_UNSENT 0x0008 /* incoming message was removed? */ #define VM_RPC_REPLY_CHECKPOINT 0x0010 /* checkpoint occurred -> retry */ #define VM_RPC_REPLY_POWEROFF 0x0020 /* underlying device is powering off */ #define VM_RPC_REPLY_TIMEOUT 0x0040 #define VM_RPC_REPLY_HB 0x0080 /* high-bandwidth tx/rx available */ /* VM state change IDs */ #define VM_STATE_CHANGE_HALT 1 #define VM_STATE_CHANGE_REBOOT 2 #define VM_STATE_CHANGE_POWERON 3 #define VM_STATE_CHANGE_RESUME 4 #define VM_STATE_CHANGE_SUSPEND 5 /* VM guest info keys */ #define VM_GUEST_INFO_DNS_NAME 1 #define VM_GUEST_INFO_IP_ADDRESS 2 #define VM_GUEST_INFO_DISK_FREE_SPACE 3 #define VM_GUEST_INFO_BUILD_NUMBER 4 #define VM_GUEST_INFO_OS_NAME_FULL 5 #define VM_GUEST_INFO_OS_NAME 6 #define VM_GUEST_INFO_UPTIME 7 #define VM_GUEST_INFO_MEMORY 8 #define VM_GUEST_INFO_IP_ADDRESS_V2 9 /* RPC responses */ #define VM_RPC_REPLY_OK "OK " #define VM_RPC_RESET_REPLY "OK ATR toolbox" #define VM_RPC_REPLY_ERROR "ERROR Unknown command" #define VM_RPC_REPLY_ERROR_IP_ADDR "ERROR Unable to find guest IP address" /* VM backup error codes */ #define VM_BACKUP_SUCCESS 0 #define VM_BACKUP_SYNC_ERROR 3 #define VM_BACKUP_REMOTE_ABORT 4 #define VM_BACKUP_TIMEOUT 30 /* seconds */ /* A register. */ union vm_reg { struct { uint16_t low; uint16_t high; } part; uint32_t word; #ifdef __amd64__ struct { uint32_t low; uint32_t high; } words; uint64_t quad; #endif } __packed; /* A register frame. */ struct vm_backdoor { union vm_reg eax; union vm_reg ebx; union vm_reg ecx; union vm_reg edx; union vm_reg esi; union vm_reg edi; union vm_reg ebp; } __packed; /* RPC context. */ struct vm_rpc { uint16_t channel; uint32_t cookie1; uint32_t cookie2; }; struct vmt_softc { struct device sc_dev; struct vm_rpc sc_tclo_rpc; char *sc_rpc_buf; int sc_rpc_error; int sc_tclo_ping; int sc_set_guest_os; int sc_quiesce; struct task sc_quiesce_task; #define VMT_RPC_BUFLEN 4096 struct timeout sc_tick; struct timeout sc_tclo_tick; struct ksensordev sc_sensordev; struct ksensor sc_sensor; char sc_hostname[MAXHOSTNAMELEN]; }; #ifdef VMT_DEBUG #define DPRINTF(_arg...) printf(_arg) #else #define DPRINTF(_arg...) do {} while(0) #endif #define DEVNAME(_s) ((_s)->sc_dev.dv_xname) void vm_cmd(struct vm_backdoor *); void vm_ins(struct vm_backdoor *); void vm_outs(struct vm_backdoor *); /* Functions for communicating with the VM Host. */ int vm_rpc_open(struct vm_rpc *, uint32_t); int vm_rpc_close(struct vm_rpc *); int vm_rpc_send(const struct vm_rpc *, const uint8_t *, uint32_t); int vm_rpc_send_str(const struct vm_rpc *, const uint8_t *); int vm_rpc_get_length(const struct vm_rpc *, uint32_t *, uint16_t *); int vm_rpc_get_data(const struct vm_rpc *, char *, uint32_t, uint16_t); int vm_rpc_send_rpci_tx_buf(struct vmt_softc *, const uint8_t *, uint32_t); int vm_rpc_send_rpci_tx(struct vmt_softc *, const char *, ...) __attribute__((__format__(__kprintf__,2,3))); int vm_rpci_response_successful(struct vmt_softc *); int vmt_kvop(void *, int, char *, char *, size_t); void vmt_probe_cmd(struct vm_backdoor *, uint16_t); void vmt_tclo_state_change_success(struct vmt_softc *, int, char); void vmt_do_reboot(struct vmt_softc *); void vmt_do_shutdown(struct vmt_softc *); void vmt_shutdown(void *); void vmt_clear_guest_info(struct vmt_softc *); void vmt_update_guest_info(struct vmt_softc *); void vmt_update_guest_uptime(struct vmt_softc *); void vmt_tick_hook(struct device *self); void vmt_tick(void *); void vmt_resume(void); int vmt_match(struct device *, void *, void *); void vmt_attach(struct device *, struct device *, void *); int vmt_activate(struct device *, int); void vmt_tclo_tick(void *); int vmt_tclo_process(struct vmt_softc *, const char *); void vmt_tclo_reset(struct vmt_softc *); void vmt_tclo_ping(struct vmt_softc *); void vmt_tclo_halt(struct vmt_softc *); void vmt_tclo_reboot(struct vmt_softc *); void vmt_tclo_poweron(struct vmt_softc *); void vmt_tclo_suspend(struct vmt_softc *); void vmt_tclo_resume(struct vmt_softc *); void vmt_tclo_capreg(struct vmt_softc *); void vmt_tclo_broadcastip(struct vmt_softc *); void vmt_set_backup_status(struct vmt_softc *, const char *, int, const char *); void vmt_quiesce_task(void *); void vmt_quiesce_done_task(void *); void vmt_tclo_abortbackup(struct vmt_softc *); void vmt_tclo_startbackup(struct vmt_softc *); void vmt_tclo_backupdone(struct vmt_softc *); int vmt_probe(void); struct vmt_tclo_rpc { const char *name; void (*cb)(struct vmt_softc *); } vmt_tclo_rpc[] = { /* Keep sorted by name (case-sensitive) */ { "Capabilities_Register", vmt_tclo_capreg }, { "OS_Halt", vmt_tclo_halt }, { "OS_PowerOn", vmt_tclo_poweron }, { "OS_Reboot", vmt_tclo_reboot }, { "OS_Resume", vmt_tclo_resume }, { "OS_Suspend", vmt_tclo_suspend }, { "Set_Option broadcastIP 1", vmt_tclo_broadcastip }, { "ping", vmt_tclo_ping }, { "reset", vmt_tclo_reset }, { "vmbackup.abort", vmt_tclo_abortbackup }, { "vmbackup.snapshotDone", vmt_tclo_backupdone }, { "vmbackup.start 1", vmt_tclo_startbackup }, { NULL }, #if 0 /* Various unsupported commands */ { "Set_Option autohide 0" }, { "Set_Option copypaste 1" }, { "Set_Option enableDnD 1" }, { "Set_Option enableMessageBusTunnel 0" }, { "Set_Option linkRootHgfsShare 0" }, { "Set_Option mapRootHgfsShare 0" }, { "Set_Option synctime 1" }, { "Set_Option synctime.period 0" }, { "Set_Option time.synchronize.tools.enable 1" }, { "Set_Option time.synchronize.tools.percentCorrection 0" }, { "Set_Option time.synchronize.tools.slewCorrection 1" }, { "Set_Option time.synchronize.tools.startup 1" }, { "Set_Option toolScripts.afterPowerOn 1" }, { "Set_Option toolScripts.afterResume 1" }, { "Set_Option toolScripts.beforePowerOff 1" }, { "Set_Option toolScripts.beforeSuspend 1" }, { "Time_Synchronize 0" }, { "Vix_1_Relayed_Command \"38cdcae40e075d66\"" }, #endif }; struct cfattach vmt_ca = { sizeof(struct vmt_softc), vmt_match, vmt_attach, NULL, vmt_activate }; struct cfdriver vmt_cd = { NULL, "vmt", DV_DULL }; extern char hostname[MAXHOSTNAMELEN]; void vmt_probe_cmd(struct vm_backdoor *frame, uint16_t cmd) { bzero(frame, sizeof(*frame)); (frame->eax).word = VM_MAGIC; (frame->ebx).word = ~VM_MAGIC; (frame->ecx).part.low = cmd; (frame->ecx).part.high = 0xffff; (frame->edx).part.low = VM_PORT_CMD; (frame->edx).part.high = 0; vm_cmd(frame); } int vmt_probe(void) { struct vm_backdoor frame; vmt_probe_cmd(&frame, VM_CMD_GET_VERSION); if (frame.eax.word == 0xffffffff || frame.ebx.word != VM_MAGIC) return (0); vmt_probe_cmd(&frame, VM_CMD_GET_SPEED); if (frame.eax.word == VM_MAGIC) return (0); return (1); } int vmt_match(struct device *parent, void *match, void *aux) { struct pv_attach_args *pva = aux; struct pvbus_hv *hv = &pva->pva_hv[PVBUS_VMWARE]; if (hv->hv_base == 0) return (0); if (!vmt_probe()) return (0); return (1); } void vmt_attach(struct device *parent, struct device *self, void *aux) { struct vmt_softc *sc = (struct vmt_softc *)self; struct pv_attach_args *pva = aux; struct pvbus_hv *hv = &pva->pva_hv[PVBUS_VMWARE]; printf("\n"); sc->sc_rpc_buf = malloc(VMT_RPC_BUFLEN, M_DEVBUF, M_NOWAIT); if (sc->sc_rpc_buf == NULL) { printf("%s: unable to allocate buffer for RPC\n", DEVNAME(sc)); return; } if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) { printf("%s: failed to open backdoor RPC channel " "(TCLO protocol)\n", DEVNAME(sc)); goto free; } /* don't know if this is important at all yet */ if (vm_rpc_send_rpci_tx(sc, "tools.capability.hgfs_server toolbox 1") != 0) { printf(": failed to set HGFS server capability\n"); goto free; } strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, sizeof(sc->sc_sensordev.xname)); sc->sc_sensor.type = SENSOR_TIMEDELTA; sc->sc_sensor.status = SENSOR_S_UNKNOWN; sensor_attach(&sc->sc_sensordev, &sc->sc_sensor); sensordev_install(&sc->sc_sensordev); config_mountroot(self, vmt_tick_hook); timeout_set(&sc->sc_tclo_tick, vmt_tclo_tick, sc); timeout_add_sec(&sc->sc_tclo_tick, 1); sc->sc_tclo_ping = 1; /* pvbus(4) key/value interface */ hv->hv_kvop = vmt_kvop; hv->hv_arg = sc; return; free: free(sc->sc_rpc_buf, M_DEVBUF, VMT_RPC_BUFLEN); } int vmt_kvop(void *arg, int op, char *key, char *value, size_t valuelen) { struct vmt_softc *sc = arg; char *buf = NULL, *ptr; size_t bufsz; int error = 0; bufsz = VMT_RPC_BUFLEN; buf = malloc(bufsz, M_TEMP|M_ZERO, M_WAITOK); switch (op) { case PVBUS_KVWRITE: if ((size_t)snprintf(buf, bufsz, "info-set %s %s", key, value) >= bufsz) { DPRINTF("%s: write command too long", DEVNAME(sc)); error = EINVAL; goto done; } break; case PVBUS_KVREAD: if ((size_t)snprintf(buf, bufsz, "info-get %s", key) >= bufsz) { DPRINTF("%s: read command too long", DEVNAME(sc)); error = EINVAL; goto done; } break; default: error = EOPNOTSUPP; goto done; } if (vm_rpc_send_rpci_tx(sc, "%s", buf) != 0) { DPRINTF("%s: error sending command: %s\n", DEVNAME(sc), buf); sc->sc_rpc_error = 1; error = EIO; goto done; } if (vm_rpci_response_successful(sc) == 0) { DPRINTF("%s: host rejected command: %s\n", DEVNAME(sc), buf); error = EINVAL; goto done; } /* skip response that was tested in vm_rpci_response_successful() */ ptr = sc->sc_rpc_buf + 2; /* might truncat, copy anyway but return error */ if (strlcpy(value, ptr, valuelen) >= valuelen) error = ENOMEM; done: free(buf, M_TEMP, bufsz); return (error); } void vmt_resume(void) { struct vm_backdoor frame; extern void rdrand(void *); bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ecx.part.low = VM_CMD_GET_TIME_FULL; frame.edx.part.low = VM_PORT_CMD; vm_cmd(&frame); rdrand(NULL); enqueue_randomness(frame.eax.word); enqueue_randomness(frame.esi.word); enqueue_randomness(frame.edx.word); enqueue_randomness(frame.ebx.word); resume_randomness(NULL, 0); } int vmt_activate(struct device *self, int act) { int rv = 0; switch (act) { case DVACT_POWERDOWN: vmt_shutdown(self); break; case DVACT_RESUME: vmt_resume(); break; } return (rv); } void vmt_update_guest_uptime(struct vmt_softc *sc) { /* host wants uptime in hundredths of a second */ if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %lld00", VM_GUEST_INFO_UPTIME, (long long)time_uptime) != 0) { DPRINTF("%s: unable to set guest uptime", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_clear_guest_info(struct vmt_softc *sc) { sc->sc_hostname[0] = '\0'; sc->sc_set_guest_os = 0; } void vmt_update_guest_info(struct vmt_softc *sc) { if (strncmp(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)) != 0) { strlcpy(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)); if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s", VM_GUEST_INFO_DNS_NAME, sc->sc_hostname) != 0) { DPRINTF("%s: unable to set hostname", DEVNAME(sc)); sc->sc_rpc_error = 1; } } /* * We're supposed to pass the full network address information back * here, but that involves xdr (sunrpc) data encoding, which seems a * bit unreasonable. */ if (sc->sc_set_guest_os == 0) { if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s %s %s", VM_GUEST_INFO_OS_NAME_FULL, ostype, osrelease, osversion) != 0) { DPRINTF("%s: unable to set full guest OS", DEVNAME(sc)); sc->sc_rpc_error = 1; } /* * Host doesn't like it if we send an OS name it doesn't * recognise, so use the closest match, which happens * to be FreeBSD. */ if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s", VM_GUEST_INFO_OS_NAME, "FreeBSD") != 0) { DPRINTF("%s: unable to set guest OS", DEVNAME(sc)); sc->sc_rpc_error = 1; } sc->sc_set_guest_os = 1; } } void vmt_tick_hook(struct device *self) { struct vmt_softc *sc = (struct vmt_softc *)self; timeout_set(&sc->sc_tick, vmt_tick, sc); vmt_tick(sc); } void vmt_tick(void *xarg) { struct vmt_softc *sc = xarg; struct vm_backdoor frame; struct timeval *guest = &sc->sc_sensor.tv; struct timeval host, diff; microtime(guest); bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ecx.part.low = VM_CMD_GET_TIME_FULL; frame.edx.part.low = VM_PORT_CMD; vm_cmd(&frame); if (frame.eax.word != 0xffffffff) { host.tv_sec = ((uint64_t)frame.esi.word << 32) | frame.edx.word; host.tv_usec = frame.ebx.word; timersub(guest, &host, &diff); sc->sc_sensor.value = (u_int64_t)diff.tv_sec * 1000000000LL + (u_int64_t)diff.tv_usec * 1000LL; sc->sc_sensor.status = SENSOR_S_OK; } else { sc->sc_sensor.status = SENSOR_S_UNKNOWN; } vmt_update_guest_info(sc); vmt_update_guest_uptime(sc); timeout_add_sec(&sc->sc_tick, 15); } void vmt_tclo_state_change_success(struct vmt_softc *sc, int success, char state) { if (vm_rpc_send_rpci_tx(sc, "tools.os.statechange.status %d %d", success, state) != 0) { DPRINTF("%s: unable to send state change result\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_do_shutdown(struct vmt_softc *sc) { vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_HALT); vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK); pvbus_shutdown(&sc->sc_dev); } void vmt_do_reboot(struct vmt_softc *sc) { vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_REBOOT); vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK); pvbus_reboot(&sc->sc_dev); } void vmt_shutdown(void *arg) { struct vmt_softc *sc = arg; if (vm_rpc_send_rpci_tx(sc, "tools.capability.hgfs_server toolbox 0") != 0) { DPRINTF("%s: failed to disable hgfs server capability\n", DEVNAME(sc)); } if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) { DPRINTF("%s: failed to send shutdown ping\n", DEVNAME(sc)); } vm_rpc_close(&sc->sc_tclo_rpc); } void vmt_tclo_reset(struct vmt_softc *sc) { if (sc->sc_rpc_error != 0) { DPRINTF("%s: resetting rpc\n", DEVNAME(sc)); vm_rpc_close(&sc->sc_tclo_rpc); /* reopen and send the reset reply next time around */ sc->sc_rpc_error = 1; return; } if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) { DPRINTF("%s: failed to send reset reply\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_ping(struct vmt_softc *sc) { vmt_update_guest_info(sc); if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { DPRINTF("%s: error sending ping response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_halt(struct vmt_softc *sc) { vmt_do_shutdown(sc); } void vmt_tclo_reboot(struct vmt_softc *sc) { vmt_do_reboot(sc); } void vmt_tclo_poweron(struct vmt_softc *sc) { vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_POWERON); if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { DPRINTF("%s: error sending poweron response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_suspend(struct vmt_softc *sc) { log(LOG_KERN | LOG_NOTICE, "VMware guest entering suspended state\n"); suspend_randomness(); vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_SUSPEND); if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { DPRINTF("%s: error sending suspend response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_resume(struct vmt_softc *sc) { log(LOG_KERN | LOG_NOTICE, "VMware guest resuming from suspended state\n"); /* force guest info update */ vmt_clear_guest_info(sc); vmt_update_guest_info(sc); vmt_resume(); vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_RESUME); if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { DPRINTF("%s: error sending resume response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_capreg(struct vmt_softc *sc) { /* don't know if this is important at all */ if (vm_rpc_send_rpci_tx(sc, "vmx.capability.unified_loop toolbox") != 0) { DPRINTF("%s: unable to set unified loop\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } if (vm_rpci_response_successful(sc) == 0) { DPRINTF("%s: host rejected unified loop setting\n", DEVNAME(sc)); } /* the trailing space is apparently important here */ if (vm_rpc_send_rpci_tx(sc, "tools.capability.statechange ") != 0) { DPRINTF("%s: unable to send statechange capability\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } if (vm_rpci_response_successful(sc) == 0) { DPRINTF("%s: host rejected statechange capability\n", DEVNAME(sc)); } if (vm_rpc_send_rpci_tx(sc, "tools.set.version %u", VM_VERSION_UNMANAGED) != 0) { DPRINTF("%s: unable to set tools version\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } vmt_clear_guest_info(sc); vmt_update_guest_uptime(sc); if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { DPRINTF("%s: error sending capabilities_register" " response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_broadcastip(struct vmt_softc *sc) { struct ifnet *iface; struct sockaddr_in *guest_ip; /* find first available ipv4 address */ guest_ip = NULL; TAILQ_FOREACH(iface, &ifnet, if_list) { struct ifaddr *iface_addr; /* skip loopback */ if (strncmp(iface->if_xname, "lo", 2) == 0 && iface->if_xname[2] >= '0' && iface->if_xname[2] <= '9') { continue; } TAILQ_FOREACH(iface_addr, &iface->if_addrlist, ifa_list) { if (iface_addr->ifa_addr->sa_family != AF_INET) continue; guest_ip = satosin(iface_addr->ifa_addr); break; } } if (guest_ip != NULL) { char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &guest_ip->sin_addr, ip, sizeof(ip)); if (vm_rpc_send_rpci_tx(sc, "info-set guestinfo.ip %s", ip) != 0) { DPRINTF("%s: unable to send guest IP address\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { DPRINTF("%s: error sending broadcastIP" " response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } else { if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR_IP_ADDR) != 0) { DPRINTF("%s: error sending broadcastIP" " error response\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } } void vmt_set_backup_status(struct vmt_softc *sc, const char *state, int code, const char *desc) { if (vm_rpc_send_rpci_tx(sc, "vmbackup.eventSet %s %d %s", state, code, desc) != 0) { DPRINTF("%s: setting backup status failed\n", DEVNAME(sc)); } } void vmt_quiesce_task(void *data) { struct vmt_softc *sc = data; int err; DPRINTF("%s: quiescing filesystems for backup\n", DEVNAME(sc)); err = vfs_stall(curproc, 1); if (err != 0) { printf("%s: unable to quiesce filesystems\n", DEVNAME(sc)); vfs_stall(curproc, 0); vmt_set_backup_status(sc, "req.aborted", VM_BACKUP_SYNC_ERROR, "vfs_stall failed"); vmt_set_backup_status(sc, "req.done", VM_BACKUP_SUCCESS, ""); sc->sc_quiesce = 0; return; } DPRINTF("%s: filesystems quiesced\n", DEVNAME(sc)); vmt_set_backup_status(sc, "prov.snapshotCommit", VM_BACKUP_SUCCESS, ""); } void vmt_quiesce_done_task(void *data) { struct vmt_softc *sc = data; vfs_stall(curproc, 0); if (sc->sc_quiesce == -1) vmt_set_backup_status(sc, "req.aborted", VM_BACKUP_REMOTE_ABORT, ""); vmt_set_backup_status(sc, "req.done", VM_BACKUP_SUCCESS, ""); sc->sc_quiesce = 0; } void vmt_tclo_abortbackup(struct vmt_softc *sc) { const char *reply = VM_RPC_REPLY_OK; if (sc->sc_quiesce > 0) { DPRINTF("%s: aborting backup\n", DEVNAME(sc)); sc->sc_quiesce = -1; task_set(&sc->sc_quiesce_task, vmt_quiesce_done_task, sc); task_add(systq, &sc->sc_quiesce_task); } else { DPRINTF("%s: can't abort, no backup in progress\n", DEVNAME(sc)); reply = VM_RPC_REPLY_ERROR; } if (vm_rpc_send_str(&sc->sc_tclo_rpc, reply) != 0) { DPRINTF("%s: error sending vmbackup.abort reply\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_startbackup(struct vmt_softc *sc) { const char *reply = VM_RPC_REPLY_OK; if (sc->sc_quiesce == 0) { DPRINTF("%s: starting quiesce\n", DEVNAME(sc)); vmt_set_backup_status(sc, "reset", VM_BACKUP_SUCCESS, ""); task_set(&sc->sc_quiesce_task, vmt_quiesce_task, sc); task_add(systq, &sc->sc_quiesce_task); sc->sc_quiesce = 1; } else { DPRINTF("%s: can't start backup, already in progress\n", DEVNAME(sc)); reply = VM_RPC_REPLY_ERROR; } if (vm_rpc_send_str(&sc->sc_tclo_rpc, reply) != 0) { DPRINTF("%s: error sending vmbackup.start reply\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } void vmt_tclo_backupdone(struct vmt_softc *sc) { const char *reply = VM_RPC_REPLY_OK; if (sc->sc_quiesce > 0) { DPRINTF("%s: backup complete\n", DEVNAME(sc)); task_set(&sc->sc_quiesce_task, vmt_quiesce_done_task, sc); task_add(systq, &sc->sc_quiesce_task); } else { DPRINTF("%s: got backup complete, but not doing a backup\n", DEVNAME(sc)); reply = VM_RPC_REPLY_ERROR; } if (vm_rpc_send_str(&sc->sc_tclo_rpc, reply) != 0) { DPRINTF("%s: error sending vmbackup.snapshotDone reply\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } int vmt_tclo_process(struct vmt_softc *sc, const char *name) { int i; /* Search for rpc command and call handler */ for (i = 0; vmt_tclo_rpc[i].name != NULL; i++) { if (strcmp(vmt_tclo_rpc[i].name, sc->sc_rpc_buf) == 0) { vmt_tclo_rpc[i].cb(sc); return (0); } } DPRINTF("%s: unknown command: \"%s\"\n", DEVNAME(sc), name); return (-1); } void vmt_tclo_tick(void *xarg) { struct vmt_softc *sc = xarg; u_int32_t rlen; u_int16_t ack; int delay; /* By default, poll every second for new messages */ delay = 1; if (sc->sc_quiesce > 0) { /* abort quiesce if it's taking too long */ if (sc->sc_quiesce++ == VM_BACKUP_TIMEOUT) { printf("%s: aborting quiesce\n", DEVNAME(sc)); sc->sc_quiesce = -1; task_set(&sc->sc_quiesce_task, vmt_quiesce_done_task, sc); task_add(systq, &sc->sc_quiesce_task); } else vmt_set_backup_status(sc, "req.keepAlive", VM_BACKUP_SUCCESS, ""); } /* reopen tclo channel if it's currently closed */ if (sc->sc_tclo_rpc.channel == 0 && sc->sc_tclo_rpc.cookie1 == 0 && sc->sc_tclo_rpc.cookie2 == 0) { if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) { DPRINTF("%s: unable to reopen TCLO channel\n", DEVNAME(sc)); delay = 15; goto out; } if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) { DPRINTF("%s: failed to send reset reply\n", DEVNAME(sc)); sc->sc_rpc_error = 1; goto out; } else { sc->sc_rpc_error = 0; } } if (sc->sc_tclo_ping) { if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) { DPRINTF("%s: failed to send TCLO outgoing ping\n", DEVNAME(sc)); sc->sc_rpc_error = 1; goto out; } } if (vm_rpc_get_length(&sc->sc_tclo_rpc, &rlen, &ack) != 0) { DPRINTF("%s: failed to get length of incoming TCLO data\n", DEVNAME(sc)); sc->sc_rpc_error = 1; goto out; } if (rlen == 0) { sc->sc_tclo_ping = 1; goto out; } if (rlen >= VMT_RPC_BUFLEN) { rlen = VMT_RPC_BUFLEN - 1; } if (vm_rpc_get_data(&sc->sc_tclo_rpc, sc->sc_rpc_buf, rlen, ack) != 0) { DPRINTF("%s: failed to get incoming TCLO data\n", DEVNAME(sc)); sc->sc_rpc_error = 1; goto out; } sc->sc_tclo_ping = 0; /* The VM host can queue multiple messages; continue without delay */ delay = 0; if (vmt_tclo_process(sc, sc->sc_rpc_buf) != 0) { if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) { DPRINTF("%s: error sending unknown command reply\n", DEVNAME(sc)); sc->sc_rpc_error = 1; } } if (sc->sc_rpc_error == 1) { /* On error, give time to recover and wait a second */ delay = 1; } out: timeout_add_sec(&sc->sc_tclo_tick, delay); } #define BACKDOOR_OP_I386(op, frame) \ __asm__ volatile ( \ "pushal;" \ "pushl %%eax;" \ "movl 0x18(%%eax), %%ebp;" \ "movl 0x14(%%eax), %%edi;" \ "movl 0x10(%%eax), %%esi;" \ "movl 0x0c(%%eax), %%edx;" \ "movl 0x08(%%eax), %%ecx;" \ "movl 0x04(%%eax), %%ebx;" \ "movl 0x00(%%eax), %%eax;" \ op \ "xchgl %%eax, 0x00(%%esp);" \ "movl %%ebp, 0x18(%%eax);" \ "movl %%edi, 0x14(%%eax);" \ "movl %%esi, 0x10(%%eax);" \ "movl %%edx, 0x0c(%%eax);" \ "movl %%ecx, 0x08(%%eax);" \ "movl %%ebx, 0x04(%%eax);" \ "popl 0x00(%%eax);" \ "popal;" \ ::"a"(frame) \ ) #define BACKDOOR_OP_AMD64(op, frame) \ __asm__ volatile ( \ "pushq %%rbp; \n\t" \ "pushq %%rax; \n\t" \ "movq 0x30(%%rax), %%rbp; \n\t" \ "movq 0x28(%%rax), %%rdi; \n\t" \ "movq 0x20(%%rax), %%rsi; \n\t" \ "movq 0x18(%%rax), %%rdx; \n\t" \ "movq 0x10(%%rax), %%rcx; \n\t" \ "movq 0x08(%%rax), %%rbx; \n\t" \ "movq 0x00(%%rax), %%rax; \n\t" \ op "\n\t" \ "xchgq %%rax, 0x00(%%rsp); \n\t" \ "movq %%rbp, 0x30(%%rax); \n\t" \ "movq %%rdi, 0x28(%%rax); \n\t" \ "movq %%rsi, 0x20(%%rax); \n\t" \ "movq %%rdx, 0x18(%%rax); \n\t" \ "movq %%rcx, 0x10(%%rax); \n\t" \ "movq %%rbx, 0x08(%%rax); \n\t" \ "popq 0x00(%%rax); \n\t" \ "popq %%rbp; \n\t" \ : /* No outputs. */ : "a" (frame) \ /* No pushal on amd64 so warn gcc about the clobbered registers. */ \ : "rbx", "rcx", "rdx", "rdi", "rsi", "cc", "memory" \ ) #ifdef __i386__ #define BACKDOOR_OP(op, frame) BACKDOOR_OP_I386(op, frame) #else #define BACKDOOR_OP(op, frame) BACKDOOR_OP_AMD64(op, frame) #endif void vm_cmd(struct vm_backdoor *frame) { BACKDOOR_OP("inl %%dx, %%eax;", frame); } void vm_ins(struct vm_backdoor *frame) { BACKDOOR_OP("cld;\n\trep insb;", frame); } void vm_outs(struct vm_backdoor *frame) { BACKDOOR_OP("cld;\n\trep outsb;", frame); } int vm_rpc_open(struct vm_rpc *rpc, uint32_t proto) { struct vm_backdoor frame; bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = proto | VM_RPC_FLAG_COOKIE; frame.ecx.part.low = VM_CMD_RPC; frame.ecx.part.high = VM_RPC_OPEN; frame.edx.part.low = VM_PORT_CMD; frame.edx.part.high = 0; vm_cmd(&frame); if (frame.ecx.part.high != 1 || frame.edx.part.low != 0) { /* open-vm-tools retries without VM_RPC_FLAG_COOKIE here.. */ DPRINTF("vmware: open failed, eax=%08x, ecx=%08x, edx=%08x\n", frame.eax.word, frame.ecx.word, frame.edx.word); return EIO; } rpc->channel = frame.edx.part.high; rpc->cookie1 = frame.esi.word; rpc->cookie2 = frame.edi.word; return 0; } int vm_rpc_close(struct vm_rpc *rpc) { struct vm_backdoor frame; bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = 0; frame.ecx.part.low = VM_CMD_RPC; frame.ecx.part.high = VM_RPC_CLOSE; frame.edx.part.low = VM_PORT_CMD; frame.edx.part.high = rpc->channel; frame.edi.word = rpc->cookie2; frame.esi.word = rpc->cookie1; vm_cmd(&frame); if (frame.ecx.part.high == 0 || frame.ecx.part.low != 0) { DPRINTF("vmware: close failed, eax=%08x, ecx=%08x\n", frame.eax.word, frame.ecx.word); return EIO; } rpc->channel = 0; rpc->cookie1 = 0; rpc->cookie2 = 0; return 0; } int vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length) { struct vm_backdoor frame; /* Send the length of the command. */ bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = length; frame.ecx.part.low = VM_CMD_RPC; frame.ecx.part.high = VM_RPC_SET_LENGTH; frame.edx.part.low = VM_PORT_CMD; frame.edx.part.high = rpc->channel; frame.esi.word = rpc->cookie1; frame.edi.word = rpc->cookie2; vm_cmd(&frame); if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) { DPRINTF("vmware: sending length failed, eax=%08x, ecx=%08x\n", frame.eax.word, frame.ecx.word); return EIO; } if (length == 0) return 0; /* Only need to poke once if command is null. */ /* Send the command using enhanced RPC. */ bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = VM_RPC_ENH_DATA; frame.ecx.word = length; frame.edx.part.low = VM_PORT_RPC; frame.edx.part.high = rpc->channel; frame.ebp.word = rpc->cookie1; frame.edi.word = rpc->cookie2; #ifdef __amd64__ frame.esi.quad = (uint64_t)buf; #else frame.esi.word = (uint32_t)buf; #endif vm_outs(&frame); if (frame.ebx.word != VM_RPC_ENH_DATA) { /* open-vm-tools retries on VM_RPC_REPLY_CHECKPOINT */ DPRINTF("vmware: send failed, ebx=%08x\n", frame.ebx.word); return EIO; } return 0; } int vm_rpc_send_str(const struct vm_rpc *rpc, const uint8_t *str) { return vm_rpc_send(rpc, str, strlen(str)); } int vm_rpc_get_data(const struct vm_rpc *rpc, char *data, uint32_t length, uint16_t dataid) { struct vm_backdoor frame; /* Get data using enhanced RPC. */ bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = VM_RPC_ENH_DATA; frame.ecx.word = length; frame.edx.part.low = VM_PORT_RPC; frame.edx.part.high = rpc->channel; frame.esi.word = rpc->cookie1; #ifdef __amd64__ frame.edi.quad = (uint64_t)data; #else frame.edi.word = (uint32_t)data; #endif frame.ebp.word = rpc->cookie2; vm_ins(&frame); /* NUL-terminate the data */ data[length] = '\0'; if (frame.ebx.word != VM_RPC_ENH_DATA) { DPRINTF("vmware: get data failed, ebx=%08x\n", frame.ebx.word); return EIO; } /* Acknowledge data received. */ bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = dataid; frame.ecx.part.low = VM_CMD_RPC; frame.ecx.part.high = VM_RPC_GET_END; frame.edx.part.low = VM_PORT_CMD; frame.edx.part.high = rpc->channel; frame.esi.word = rpc->cookie1; frame.edi.word = rpc->cookie2; vm_cmd(&frame); if (frame.ecx.part.high == 0) { DPRINTF("vmware: ack data failed, eax=%08x, ecx=%08x\n", frame.eax.word, frame.ecx.word); return EIO; } return 0; } int vm_rpc_get_length(const struct vm_rpc *rpc, uint32_t *length, uint16_t *dataid) { struct vm_backdoor frame; bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ebx.word = 0; frame.ecx.part.low = VM_CMD_RPC; frame.ecx.part.high = VM_RPC_GET_LENGTH; frame.edx.part.low = VM_PORT_CMD; frame.edx.part.high = rpc->channel; frame.esi.word = rpc->cookie1; frame.edi.word = rpc->cookie2; vm_cmd(&frame); if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) { DPRINTF("vmware: get length failed, eax=%08x, ecx=%08x\n", frame.eax.word, frame.ecx.word); return EIO; } if ((frame.ecx.part.high & VM_RPC_REPLY_DORECV) == 0) { *length = 0; *dataid = 0; } else { *length = frame.ebx.word; *dataid = frame.edx.part.high; } return 0; } int vm_rpci_response_successful(struct vmt_softc *sc) { return (sc->sc_rpc_buf[0] == '1' && sc->sc_rpc_buf[1] == ' '); } int vm_rpc_send_rpci_tx_buf(struct vmt_softc *sc, const uint8_t *buf, uint32_t length) { struct vm_rpc rpci; u_int32_t rlen; u_int16_t ack; int result = 0; if (vm_rpc_open(&rpci, VM_RPC_OPEN_RPCI) != 0) { DPRINTF("%s: rpci channel open failed\n", DEVNAME(sc)); return EIO; } if (vm_rpc_send(&rpci, sc->sc_rpc_buf, length) != 0) { DPRINTF("%s: unable to send rpci command\n", DEVNAME(sc)); result = EIO; goto out; } if (vm_rpc_get_length(&rpci, &rlen, &ack) != 0) { DPRINTF("%s: failed to get length of rpci response data\n", DEVNAME(sc)); result = EIO; goto out; } if (rlen > 0) { if (rlen >= VMT_RPC_BUFLEN) { rlen = VMT_RPC_BUFLEN - 1; } if (vm_rpc_get_data(&rpci, sc->sc_rpc_buf, rlen, ack) != 0) { DPRINTF("%s: failed to get rpci response data\n", DEVNAME(sc)); result = EIO; goto out; } } out: if (vm_rpc_close(&rpci) != 0) { DPRINTF("%s: unable to close rpci channel\n", DEVNAME(sc)); } return result; } int vm_rpc_send_rpci_tx(struct vmt_softc *sc, const char *fmt, ...) { va_list args; int len; va_start(args, fmt); len = vsnprintf(sc->sc_rpc_buf, VMT_RPC_BUFLEN, fmt, args); va_end(args); if (len >= VMT_RPC_BUFLEN) { DPRINTF("%s: rpci command didn't fit in buffer\n", DEVNAME(sc)); return EIO; } return vm_rpc_send_rpci_tx_buf(sc, sc->sc_rpc_buf, len); } #if 0 struct vm_backdoor frame; bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; frame.ecx.part.low = VM_CMD_GET_VERSION; frame.edx.part.low = VM_PORT_CMD; printf("\n"); printf("eax 0x%08x\n", frame.eax.word); printf("ebx 0x%08x\n", frame.ebx.word); printf("ecx 0x%08x\n", frame.ecx.word); printf("edx 0x%08x\n", frame.edx.word); printf("ebp 0x%08x\n", frame.ebp.word); printf("edi 0x%08x\n", frame.edi.word); printf("esi 0x%08x\n", frame.esi.word); vm_cmd(&frame); printf("-\n"); printf("eax 0x%08x\n", frame.eax.word); printf("ebx 0x%08x\n", frame.ebx.word); printf("ecx 0x%08x\n", frame.ecx.word); printf("edx 0x%08x\n", frame.edx.word); printf("ebp 0x%08x\n", frame.ebp.word); printf("edi 0x%08x\n", frame.edi.word); printf("esi 0x%08x\n", frame.esi.word); #endif /* * Notes on tracing backdoor activity in vmware-guestd: * * - Find the addresses of the inl / rep insb / rep outsb * instructions used to perform backdoor operations. * One way to do this is to disassemble vmware-guestd: * * $ objdump -S /emul/freebsd/sbin/vmware-guestd > vmware-guestd.S * * and search for 'in ' in the resulting file. The rep insb and * rep outsb code is directly below that. * * - Run vmware-guestd under gdb, setting up breakpoints as follows: * (the addresses shown here are the ones from VMware-server-1.0.10-203137, * the last version that actually works in FreeBSD emulation on OpenBSD) * * break *0x805497b (address of 'in' instruction) * commands 1 * silent * echo INOUT\n * print/x $ecx * print/x $ebx * print/x $edx * continue * end * break *0x805497c (address of instruction after 'in') * commands 2 * silent * echo ===\n * print/x $ecx * print/x $ebx * print/x $edx * echo \n * continue * end * break *0x80549b7 (address of instruction before 'rep insb') * commands 3 * silent * set variable $inaddr = $edi * set variable $incount = $ecx * continue * end * break *0x80549ba (address of instruction after 'rep insb') * commands 4 * silent * echo IN\n * print $incount * x/s $inaddr * echo \n * continue * end * break *0x80549fb (address of instruction before 'rep outsb') * commands 5 * silent * echo OUT\n * print $ecx * x/s $esi * echo \n * continue * end * * This will produce a log of the backdoor operations, including the * data sent and received and the relevant register values. You can then * match the register values to the various constants in this file. */