Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openhcl/underhill_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ async fn launch_workers(
vmbus_max_version: opt.vmbus_max_version,
vmbus_enable_mnf: opt.vmbus_enable_mnf,
vmbus_force_confidential_external_memory: opt.vmbus_force_confidential_external_memory,
vmbus_force_gpa_pinning: opt.vmbus_force_gpa_pinning,
vmbus_channel_unstick_delay: (opt.vmbus_channel_unstick_delay_ms != 0)
.then(|| Duration::from_millis(opt.vmbus_channel_unstick_delay_ms)),
cmdline_append: opt.cmdline_append.clone(),
Expand Down
7 changes: 7 additions & 0 deletions openhcl/underhill_core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ pub struct Options {
/// N.B.: Not all vmbus devices support this feature, so enabling it may cause failures.
pub vmbus_force_confidential_external_memory: bool,

/// (OPENHCL_VMBUS_FORCE_GPA_PINNING=1)
/// Force all vmbus channels to use pinned GPA ranges if the guest supports that feature. Used
/// for testing purposes only.
pub vmbus_force_gpa_pinning: bool,

/// (OPENHCL_VMBUS_CHANNEL_UNSTICK_DELAY_MS=\<number\>) (default: 100)
/// Delay before unsticking a vmbus channel after it has been opened, in milliseconds. Set to
/// zero to disable unsticking.
Expand Down Expand Up @@ -446,6 +451,7 @@ impl Options {
read_legacy_openhcl_env("OPENHCL_VMBUS_ENABLE_MNF").map(|v| parse_bool(Some(v)));
let vmbus_force_confidential_external_memory =
parse_env_bool("OPENHCL_VMBUS_FORCE_CONFIDENTIAL_EXTERNAL_MEMORY");
let vmbus_force_gpa_pinning = parse_env_bool("OPENHCL_VMBUS_FORCE_GPA_PINNING");
let vmbus_channel_unstick_delay_ms =
parse_legacy_env_number("OPENHCL_VMBUS_CHANNEL_UNSTICK_DELAY_MS")?;
let cmdline_append = read_legacy_openhcl_env("OPENHCL_CMDLINE_APPEND")
Expand Down Expand Up @@ -584,6 +590,7 @@ impl Options {
vmbus_max_version,
vmbus_enable_mnf,
vmbus_force_confidential_external_memory,
vmbus_force_gpa_pinning,
vmbus_channel_unstick_delay_ms: vmbus_channel_unstick_delay_ms.unwrap_or(100),
cmdline_append,
vnc_port: vnc_port.unwrap_or(3),
Expand Down
30 changes: 27 additions & 3 deletions openhcl/underhill_core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ pub struct UnderhillEnvCfg {
pub vmbus_enable_mnf: Option<bool>,
/// Force the use of confidential external memory for all non-relay vmbus channels.
pub vmbus_force_confidential_external_memory: bool,
/// Force the use of GPA pinning for all vmbus channels.
pub vmbus_force_gpa_pinning: bool,
/// Delay before unsticking a vmbus channel after it has been opened.
pub vmbus_channel_unstick_delay: Option<Duration>,
/// Command line to append to VTL0 command line. Only used for linux direct.
Expand Down Expand Up @@ -3219,18 +3221,38 @@ async fn new_underhill_vm(
.unwrap_or(!controllers.mana.is_empty());
tracing::info!(CVM_ALLOWED, enable_mnf, "Underhill MNF enabled?");

// Enable the GPA pinning feature only if the hypercalls are available.
#[cfg(not(guest_arch = "x86_64"))]
let support_gpa_pinning = false;
#[cfg(guest_arch = "x86_64")]
let support_gpa_pinning = {
let result =
safe_intrinsics::cpuid(hvdef::HV_CPUID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION, 0);
hvdef::HvEnlightenmentInformation::from(
result.eax as u128
| (result.ebx as u128) << 32
| (result.ecx as u128) << 64
| (result.edx as u128) << 96,
)
.use_gpa_pinning_hypercall()
};

let max_version = env_cfg
.vmbus_max_version
.map(vmbus_core::MaxVersionInfo::new)
.or_else(|| {
// For compatibility with rollback, any additional features are currently disabled,
// except for isolated guests which do not support servicing.
// For compatibility with rollback, the max version should only include feature
// flags that are available in all in-service versions of OpenHCL.
// N.B. Isolated VMs do not support servicing, so they can use all flags.
// N.B. VM SKUs that support GPA pinning are guaranteed to use a compatible
// version of OpenHCL so it can safely be enabled here.
(!hardware_isolated).then_some(vmbus_core::MaxVersionInfo {
version: vmbus_core::protocol::Version::Copper as u32,
feature_flags: vmbus_core::protocol::FeatureFlags::new()
.with_guest_specified_signal_parameters(true)
.with_channel_interrupt_redirection(true)
.with_modify_connection(true),
.with_modify_connection(true)
.with_gpa_pinning(true),
})
});

Expand All @@ -3256,6 +3278,8 @@ async fn new_underhill_vm(
.force_confidential_external_memory(
env_cfg.vmbus_force_confidential_external_memory,
)
.support_gpa_pinning(support_gpa_pinning)
.force_gpa_pinning(support_gpa_pinning && env_cfg.vmbus_force_gpa_pinning)
.channel_unstick_delay(env_cfg.vmbus_channel_unstick_delay)
// For saved-state compat with release/2411.
.send_messages_while_stopped(true)
Expand Down
3 changes: 3 additions & 0 deletions vm/devices/net/netvsp/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ impl TestNicDevice {
interrupt: host_to_guest_interrupt,
use_confidential_ring: false,
use_confidential_external_memory: false,
is_external_memory_pinned: false,
};

let open_response = self
Expand Down Expand Up @@ -753,6 +754,7 @@ impl TestNicDevice {
interrupt: host_to_guest_interrupt,
use_confidential_ring: false,
use_confidential_external_memory: false,
is_external_memory_pinned: false,
};

let open_response = self
Expand Down Expand Up @@ -879,6 +881,7 @@ impl TestNicDevice {
interrupt: host_to_guest_interrupt.clone(),
use_confidential_external_memory: false,
use_confidential_ring: false,
is_external_memory_pinned: false,
}),
gpadls,
})
Expand Down
7 changes: 7 additions & 0 deletions vm/devices/vmbus/vmbus_channel/src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ pub struct OpenRequest {
/// Indicates if the currently connected vmbus client, as well as the channel the request is
/// for, supports the use of confidential external memory.
pub use_confidential_external_memory: bool,
/// Indicates if the currently connected vmbus client is expected to pin any external memory
/// used by the channel. This is only true if the vmbus client supports GPA pinning and the
/// channel indicated it requires pinned external memory. It can only be true for paravisor
/// channels in a VM that supports the GPA pinning hypercalls.
pub is_external_memory_pinned: bool,
}

impl OpenRequest {
Expand All @@ -226,6 +231,8 @@ impl OpenRequest {
&& offer_flags.confidential_ring_buffer(),
use_confidential_external_memory: feature_flags.confidential_channels()
&& offer_flags.confidential_external_memory(),
is_external_memory_pinned: feature_flags.gpa_pinning()
&& offer_flags.require_pinned_external_memory(),
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions vm/devices/vmbus/vmbus_core/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ pub struct FeatureFlags {
/// case the guest cannot cancel MNF interrupts from the host.
pub server_specified_monitor_pages: bool, // 0x40

#[bits(25)]
/// The guest supports channels that require the use of pinned memory. This indicates that the
/// `require_pinned_external_memory` flag in the channel offer message is supported.
pub gpa_pinning: bool, // 0x80

#[bits(24)]
_reserved: u32,
}

Expand Down Expand Up @@ -553,9 +557,9 @@ pub struct OfferFlags {
/// Indicates the channel must use encrypted additional GPADLs and GPA direct ranges on a
/// hardware-isolated VM.
pub confidential_external_memory: bool, // 0x4
#[bits(1)]
_reserved1: u16,
pub named_pipe_mode: bool, // 0x10
/// Indicates that additional GPADLs and GPA direct packets must use pinned GPA ranges.
pub require_pinned_external_memory: bool, // 0x8
pub named_pipe_mode: bool, // 0x10
#[bits(8)]
_reserved2: u16,
pub tlnpi_provider: bool, // 0x2000
Expand Down
34 changes: 23 additions & 11 deletions vm/devices/vmbus/vmbus_server/src/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ pub struct Server {
// shared memory and we cannot set protections on shared memory.
require_server_allocated_mnf: bool,
use_absolute_channel_order: bool,
support_gpa_pinning: bool,
}

pub struct ServerWithNotifier<'a, T> {
Expand Down Expand Up @@ -1323,6 +1324,7 @@ static SUPPORTED_VERSIONS: &[Version] = &[

// Feature flags that are always supported.
// N.B. Confidential channels are conditionally supported if running in the paravisor.
// N.B. GPA pinning is conditionally supported if the server is configured to support it.
const SUPPORTED_FEATURE_FLAGS: FeatureFlags = FeatureFlags::new()
.with_guest_specified_signal_parameters(true)
.with_channel_interrupt_redirection(true)
Expand Down Expand Up @@ -1373,6 +1375,7 @@ impl Server {
child_connection_id: u32,
channel_id_offset: u16,
use_absolute_channel_order: bool,
support_gpa_pinning: bool,
) -> Self {
Server {
state: ConnectionState::Disconnected,
Expand All @@ -1388,6 +1391,7 @@ impl Server {
pending_messages: PendingMessages(VecDeque::new()),
require_server_allocated_mnf: false,
use_absolute_channel_order,
support_gpa_pinning,
}
}

Expand Down Expand Up @@ -2282,14 +2286,6 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
return;
};

tracelimit::info_ratelimited!(
vtl,
?version,
client_id = ?request.client_id,
trusted = request.trusted,
"Guest negotiated version"
);

// Make sure we can receive incoming interrupts on the monitor page. The parent to child
// page is not used as this server doesn't send monitored interrupts.
let monitor_page = match request.monitor_page {
Expand Down Expand Up @@ -2365,7 +2361,8 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
// supported.
const LOCAL_FEATURE_FLAGS: FeatureFlags = FeatureFlags::new()
.with_client_id(true)
.with_confidential_channels(true);
.with_confidential_channels(true)
.with_gpa_pinning(true);

let (relay_feature_flags, server_specified_monitor_page) = match response {
// There is no relay, or it successfully processed our request.
Expand Down Expand Up @@ -2434,6 +2431,14 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
.set_server_specified_monitor_pages(false);
}

tracelimit::info_ratelimited!(
vtl = self.inner.assigned_channels.vtl as u8,
version = ?info.version,
client_id = ?info.client_id,
trusted = info.trusted,
"guest negotiated version"
);

let version = info.version;
self.inner.state = ConnectionState::Connected(info);

Expand Down Expand Up @@ -2462,8 +2467,9 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {

let supported_flags = if version >= Version::Copper {
// Confidential channels should only be enabled if the connection is trusted.
let max_supported_flags =
SUPPORTED_FEATURE_FLAGS.with_confidential_channels(request.trusted);
let max_supported_flags = SUPPORTED_FEATURE_FLAGS
.with_confidential_channels(request.trusted)
.with_gpa_pinning(self.inner.support_gpa_pinning);

// The max features may be limited in order to test older protocol versions.
if let Some(max_version) = self.inner.max_version {
Expand Down Expand Up @@ -3965,6 +3971,8 @@ impl<N: Notifier> MessageSender<'_, N> {
fn send_offer(&mut self, channel: &mut Channel, connection_info: &ConnectionInfo) {
let info = channel.info.as_ref().expect("assigned");
let mut flags = channel.offer.flags;

// Disable offer flags that are not supported by the current set of feature flags.
if !connection_info
.version
.feature_flags
Expand All @@ -3974,6 +3982,10 @@ impl<N: Notifier> MessageSender<'_, N> {
flags.set_confidential_external_memory(false);
}

if !connection_info.version.feature_flags.gpa_pinning() {
flags.set_require_pinned_external_memory(false);
}

// Send the monitor ID only if the guest supports MNF. MNF may also be disabled if the guest
// provided monitor pages but this server can only use server-allocated monitor pages
// (typically the case for OpenHCL on a hardware-isolated VM), but the guest didn't support
Expand Down
63 changes: 60 additions & 3 deletions vm/devices/vmbus/vmbus_server/src/channels/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ fn test_version_negotiation_feature_flags() {
);
}

#[test]
fn test_version_negotiation_gpa_pinning_requires_server_support() {
let requested_features = FeatureFlags::new().with_gpa_pinning(true);
let target_info = TargetInfo::new()
.with_sint(VMBUS_SINT)
.with_vtl(0)
.with_feature_flags(requested_features.into());

let mut env = TestEnv::new();
test_initiate_contact(
&mut env,
TestVersion::Supported {
version: Version::Copper,
expected_features: 0,
},
target_info.into(),
);

let mut env = TestEnv::with_params(false, true);
test_initiate_contact(
&mut env,
TestVersion::Supported {
version: Version::Copper,
expected_features: requested_features.into(),
},
target_info.into(),
);
}

#[test]
fn test_version_negotiation_interrupt_page() {
let mut env = TestEnv::new();
Expand Down Expand Up @@ -2062,7 +2091,7 @@ fn test_channel_id_order() {

#[test]
fn test_channel_id_order_absolute() {
let mut env = TestEnv::with_params(true);
let mut env = TestEnv::with_params(true, false);

let _offer_id1 = env.offer_with_order(3, 3, Some(1));
let _offer_id3 = env.offer_with_order(5, 5, Some(3));
Expand Down Expand Up @@ -2260,6 +2289,33 @@ fn test_confidential_channels_unsupported() {
.check_message(OutgoingMessage::new(&protocol::AllOffersDelivered {}));
}

#[test]
fn test_offer_requires_pinning_only_when_negotiated() {
for negotiate_gpa_pinning in [false, true] {
let mut env = TestEnv::with_params(false, true);
env.connect(
Version::Copper,
FeatureFlags::new().with_gpa_pinning(negotiate_gpa_pinning),
);

env.offer_with_flags(
1,
OfferFlags::new().with_require_pinned_external_memory(true),
);
env.c().handle_request_offers().unwrap();

let offer = env.notifier.get_message::<protocol::OfferChannel>();
assert_eq!(offer.channel_id, ChannelId(1));
assert_eq!(
offer.flags,
OfferFlags::new().with_require_pinned_external_memory(negotiate_gpa_pinning)
);

env.notifier
.check_message(OutgoingMessage::new(&protocol::AllOffersDelivered {}));
}
}

#[test]
fn test_confidential_channels_untrusted() {
let mut env = TestEnv::new();
Expand Down Expand Up @@ -2727,16 +2783,17 @@ struct TestEnv {

impl TestEnv {
fn new() -> Self {
Self::with_params(false)
Self::with_params(false, false)
}

fn with_params(assign_channel_id_on_offer: bool) -> Self {
fn with_params(assign_channel_id_on_offer: bool, support_gpa_pinning: bool) -> Self {
let (notifier, recv) = TestNotifier::new();
let server = Server::new(
Vtl::Vtl0,
MESSAGE_CONNECTION_ID,
0,
assign_channel_id_on_offer,
support_gpa_pinning,
);
Self {
server,
Expand Down
Loading
Loading