Skip to content

Commit

Permalink
[hyperv] Require support for VMBus version 3.0 or newer
Browse files Browse the repository at this point in the history
We require the ability to disconnect from and reconnect to VMBus; if
we don't have this then there is no (viable) way for a loaded
operating system to continue to use any VMBus devices.  (There is also
a small but non-zero risk that the host will continue to write to our
interrupt and monitor pages, since the VMBUS_UNLOAD message in earlier
versions is essentially a no-op.)

This requires us to ensure that the host supports protocol version 3.0
(VMBUS_VERSION_WIN8_1).  However, we can't actually _use_ protocol
version 3.0, since doing so causes an iSCSI-booted Windows Server 2012
R2 VM to crash due to a NULL pointer dereference in vmbus.sys.

To work around this problem, we first ensure that we can connect using
protocol v3.0, then disconnect and reconnect using the oldest known
protocol.

This deliberately prevents the use of the iPXE native Hyper-V drivers
on older versions of Hyper-V, where we could use our drivers but in so
doing would break the loaded operating system.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Dec 21, 2014
1 parent af07324 commit 0166a68
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 17 deletions.
14 changes: 12 additions & 2 deletions src/include/ipxe/vmbus.h
Expand Up @@ -42,8 +42,17 @@ union vmbus_version {
};
} __attribute__ (( packed ));

/** Oldest known VMBus protocol version (Windows Server 2008) */
#define VMBUS_VERSION_WS2008 ( ( 0 << 16 ) | ( 13 << 0 ) )
/** Known VMBus protocol versions */
enum vmbus_raw_version {
/** Windows Server 2008 */
VMBUS_VERSION_WS2008 = ( ( 0 << 16 ) | ( 13 << 0 ) ),
/** Windows 7 */
VMBUS_VERSION_WIN7 = ( ( 1 << 16 ) | ( 1 << 0 ) ),
/** Windows 8 */
VMBUS_VERSION_WIN8 = ( ( 2 << 16 ) | ( 4 << 0 ) ),
/** Windows 8.1 */
VMBUS_VERSION_WIN8_1 = ( ( 3 << 16 ) | ( 0 << 0 ) ),
};

/** Guest physical address range descriptor */
struct vmbus_gpa_range {
Expand Down Expand Up @@ -82,6 +91,7 @@ enum vmbus_message_type {
VMBUS_INITIATE_CONTACT = 14,
VMBUS_VERSION_RESPONSE = 15,
VMBUS_UNLOAD = 16,
VMBUS_UNLOAD_RESPONSE = 17,
};

/** VMBus "offer channel" message */
Expand Down
86 changes: 71 additions & 15 deletions src/interface/hyperv/vmbus.c
Expand Up @@ -38,13 +38,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/hyperv.h>
#include <ipxe/vmbus.h>

/** Chosen VMBus protocol version
*
* This is a policy decision. We use the oldest common version in
* order to avoid including any version-specific code.
*/
#define VMBUS_VERSION VMBUS_VERSION_WS2008

/** VMBus initial GPADL ID
*
* This is an opaque value with no meaning. The Linux kernel uses
Expand Down Expand Up @@ -122,9 +115,11 @@ static int vmbus_wait_for_message ( struct hv_hypervisor *hv ) {
* Initiate contact
*
* @v hv Hyper-V hypervisor
* @v raw VMBus protocol (raw) version
* @ret rc Return status code
*/
static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
static int vmbus_initiate_contact ( struct hv_hypervisor *hv,
unsigned int raw ) {
struct vmbus *vmbus = hv->vmbus;
const struct vmbus_version_response *version = &vmbus->message->version;
struct vmbus_initiate_contact initiate;
Expand All @@ -133,7 +128,7 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
/* Construct message */
memset ( &initiate, 0, sizeof ( initiate ) );
initiate.header.type = cpu_to_le32 ( VMBUS_INITIATE_CONTACT );
initiate.version.raw = cpu_to_le32 ( VMBUS_VERSION );
initiate.version.raw = cpu_to_le32 ( raw );
initiate.intr = virt_to_phys ( vmbus->intr );
initiate.monitor_in = virt_to_phys ( vmbus->monitor_in );
initiate.monitor_out = virt_to_phys ( vmbus->monitor_out );
Expand All @@ -158,7 +153,7 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
vmbus );
return -ENOTSUP;
}
if ( version->version.raw != cpu_to_le32 ( VMBUS_VERSION ) ) {
if ( version->version.raw != cpu_to_le32 ( raw ) ) {
DBGC ( vmbus, "VMBUS %p unexpected version %d.%d\n",
vmbus, le16_to_cpu ( version->version.major ),
le16_to_cpu ( version->version.minor ) );
Expand All @@ -178,8 +173,69 @@ static int vmbus_initiate_contact ( struct hv_hypervisor *hv ) {
* @ret rc Return status code
*/
static int vmbus_unload ( struct hv_hypervisor *hv ) {
struct vmbus *vmbus = hv->vmbus;
const struct vmbus_message_header *header = &vmbus->message->header;
int rc;

/* Post message */
if ( ( rc = vmbus_post_empty_message ( hv, VMBUS_UNLOAD ) ) != 0 )
return rc;

/* Wait for response */
if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 )
return rc;

/* Check response */
if ( header->type != cpu_to_le32 ( VMBUS_UNLOAD_RESPONSE ) ) {
DBGC ( vmbus, "VMBUS %p unexpected unload response type %d\n",
vmbus, le32_to_cpu ( header->type ) );
return -EPROTO;
}

return 0;
}

/**
* Negotiate protocol version
*
* @v hv Hyper-V hypervisor
* @ret rc Return status code
*/
static int vmbus_negotiate_version ( struct hv_hypervisor *hv ) {
int rc;

return vmbus_post_empty_message ( hv, VMBUS_UNLOAD );
/* We require the ability to disconnect from and reconnect to
* VMBus; if we don't have this then there is no (viable) way
* for a loaded operating system to continue to use any VMBus
* devices. (There is also a small but non-zero risk that the
* host will continue to write to our interrupt and monitor
* pages, since the VMBUS_UNLOAD message in earlier versions
* is essentially a no-op.)
*
* This requires us to ensure that the host supports protocol
* version 3.0 (VMBUS_VERSION_WIN8_1). However, we can't
* actually _use_ protocol version 3.0, since doing so causes
* an iSCSI-booted Windows Server 2012 R2 VM to crash due to a
* NULL pointer dereference in vmbus.sys.
*
* To work around this problem, we first ensure that we can
* connect using protocol v3.0, then disconnect and reconnect
* using the oldest known protocol.
*/

/* Initiate contact to check for required protocol support */
if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WIN8_1 ) ) != 0 )
return rc;

/* Terminate contact */
if ( ( rc = vmbus_unload ( hv ) ) != 0 )
return rc;

/* Reinitiate contact using the oldest known protocol version */
if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WS2008 ) ) != 0 )
return rc;

return 0;
}

/**
Expand Down Expand Up @@ -1232,9 +1288,9 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
/* Enable message interrupt */
hv_enable_sint ( hv, VMBUS_MESSAGE_SINT );

/* Initiate contact */
if ( ( rc = vmbus_initiate_contact ( hv ) ) != 0 )
goto err_initiate_contact;
/* Negotiate protocol version */
if ( ( rc = vmbus_negotiate_version ( hv ) ) != 0 )
goto err_negotiate_version;

/* Enumerate channels */
if ( ( rc = vmbus_probe_channels ( hv, parent ) ) != 0 )
Expand All @@ -1245,7 +1301,7 @@ int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) {
vmbus_remove_channels ( hv, parent );
err_probe_channels:
vmbus_unload ( hv );
err_initiate_contact:
err_negotiate_version:
hv_disable_sint ( hv, VMBUS_MESSAGE_SINT );
hv_free_pages ( hv, vmbus->intr, vmbus->monitor_in, vmbus->monitor_out,
NULL );
Expand Down

0 comments on commit 0166a68

Please sign in to comment.