diff options
author | 2015-07-21 09:13:11 +0000 | |
---|---|---|
committer | 2015-07-21 09:13:11 +0000 | |
commit | e45726f3ecee08eed269cfcb07d9ab9361ae69fc (patch) | |
tree | e5b738fcfa1a8f5db7155a254302566034ef663d /sys/dev/pv | |
parent | basic macro cleanup, mostly converting to .Dv where appropriate (diff) | |
download | wireguard-openbsd-e45726f3ecee08eed269cfcb07d9ab9361ae69fc.tar.xz wireguard-openbsd-e45726f3ecee08eed269cfcb07d9ab9361ae69fc.zip |
Move vmt.c to sys/dev/pv/vmt.c. The history is lost but can be found
in the Attic of sys/dev/vmt.c.
OK to do it deraadt@
Diffstat (limited to 'sys/dev/pv')
-rw-r--r-- | sys/dev/pv/files.pv | 4 | ||||
-rw-r--r-- | sys/dev/pv/vmt.c | 1292 |
2 files changed, 1294 insertions, 2 deletions
diff --git a/sys/dev/pv/files.pv b/sys/dev/pv/files.pv index d0834032390..2acad4b1f62 100644 --- a/sys/dev/pv/files.pv +++ b/sys/dev/pv/files.pv @@ -1,4 +1,4 @@ -# $OpenBSD: files.pv,v 1.1 2015/07/21 03:38:22 reyk Exp $ +# $OpenBSD: files.pv,v 1.2 2015/07/21 09:13:11 reyk Exp $ # # Config file and device description for paravirtual devices. # Included by ports that need it. @@ -11,4 +11,4 @@ file dev/pv/pvbus.c pvbus needs-flag # VMware Tools device vmt attach vmt at pvbus -file dev/vmt.c vmt needs-flag +file dev/pv/vmt.c vmt needs-flag diff --git a/sys/dev/pv/vmt.c b/sys/dev/pv/vmt.c new file mode 100644 index 00000000000..967682db53b --- /dev/null +++ b/sys/dev/pv/vmt.c @@ -0,0 +1,1292 @@ +/* $OpenBSD: vmt.c,v 1.1 2015/07/21 09:13:11 reyk Exp $ */ + +/* + * Copyright (c) 2007 David Crawshaw <david@zentus.com> + * Copyright (c) 2008 David Gwynne <dlg@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. + */ + +#if !defined(__i386__) && !defined(__amd64__) +#error vmt(4) is only supported on i386 and amd64 +#endif + +/* + * Protocol reverse engineered by Ken Kato: + * http://chitchat.at.infoseek.co.jp/vmware/backdoor.html + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/timeout.h> +#include <sys/signalvar.h> +#include <sys/syslog.h> +#include <sys/proc.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_var.h> +#include <netinet/in.h> + +#include <dev/rndvar.h> + +/* "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" + +/* 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; +#define VMT_RPC_BUFLEN 256 + + 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 *); + +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_update_guest_info(struct vmt_softc *); +void vmt_update_guest_uptime(struct vmt_softc *); + +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 *); + +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 }, + { 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) +{ + const char **busname = (const char **)aux; + + if (!vmt_probe()) + return (0); + + return (strcmp(*busname, vmt_cd.cd_name) == 0); +} + +void +vmt_attach(struct device *parent, struct device *self, void *aux) +{ + struct vmt_softc *sc = (struct vmt_softc *)self; + + 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); + + timeout_set(&sc->sc_tick, vmt_tick, sc); + if (mountroothook_establish(vmt_tick, sc) == NULL) + DPRINTF("%s: unable to establish tick\n", DEVNAME(sc)); + + timeout_set(&sc->sc_tclo_tick, vmt_tclo_tick, sc); + timeout_add_sec(&sc->sc_tclo_tick, 1); + sc->sc_tclo_ping = 1; + + return; + +free: + free(sc->sc_rpc_buf, M_DEVBUF, 0); +} + +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); + add_true_randomness(frame.eax.word); + add_true_randomness(frame.esi.word); + add_true_randomness(frame.edx.word); + add_true_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_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(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); + + suspend_randomness(); + + log(LOG_KERN | LOG_NOTICE, + "Shutting down in response to request from VMware host\n"); + prsignal(initprocess, SIGUSR2); +} + +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); + + suspend_randomness(); + + log(LOG_KERN | LOG_NOTICE, + "Rebooting in response to request from VMware host\n"); + prsignal(initprocess, SIGINT); +} + +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 */ + sc->sc_hostname[0] = '\0'; + sc->sc_set_guest_os = 0; + 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_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; + } + } +} + +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; + + /* 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)); + timeout_add_sec(&sc->sc_tclo_tick, 15); + 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; + 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 '<tab>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. + */ |