Skip to content

Commit 822d4d4

Browse files
SvenGrootCopilot
andauthored
vmbus_server: allow gpadls for revoked channels (#3959)
This change provides a corrected fix for the issue described in #3649. If a guest sends a GpadlHeader message for a channel that was revoked on the host, this should be allowed to succeed. The guest likely sent the message before it received the RescindOffer message, so it is not expecting failures, and in particular Linux does not handle it well if a failure does occur here. This change allows the gpadl to be created, and also lets subsequent teardown requests to succeed should the guest choose to send them before releasing the channel. This matches the existing behavior in Hyper-V. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent a5f055d commit 822d4d4

2 files changed

Lines changed: 83 additions & 34 deletions

File tree

vm/devices/vmbus/vmbus_server/src/channels.rs

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,9 @@ impl ChannelList {
10831083
}
10841084

10851085
/// Gets a channel by guest channel ID.
1086+
///
1087+
/// It is an error to call this function on a channel that has been released
1088+
/// by the guest, since the guest should not be using that ID anymore.
10861089
fn get_by_channel_id_mut(
10871090
&mut self,
10881091
assigned_channels: &AssignedChannels,
@@ -2741,27 +2744,28 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
27412744
Ok(())
27422745
}
27432746

2744-
/// Sends a GPADL to the device when `ranges` is Some. Returns false if the
2745-
/// GPADL should be removed because the channel is already revoked.
2746-
#[must_use]
2747-
fn gpadl_updated(
2747+
/// Sends a GPADL to the device after the full list of ranges was received.
2748+
fn gpadl_completed(
27482749
mut sender: MessageSender<'_, N>,
27492750
offer_id: OfferId,
27502751
channel: &Channel,
27512752
gpadl_id: GpadlId,
2752-
gpadl: &Gpadl,
2753-
) -> bool {
2753+
gpadl: &mut Gpadl,
2754+
) {
27542755
if channel.state.is_revoked() {
27552756
let channel_id = channel.info.as_ref().expect("assigned").channel_id;
2756-
sender.send_gpadl_created(channel_id, gpadl_id, protocol::STATUS_UNSUCCESSFUL);
2757-
false
2757+
2758+
// A gpadl for a channel that was revoked but still referenced is
2759+
// allowed. In this case there is no channel to notify so
2760+
// immediately send a success response.
2761+
gpadl.state = GpadlState::Accepted;
2762+
sender.send_gpadl_created(channel_id, gpadl_id, protocol::STATUS_SUCCESS);
27582763
} else {
2759-
// Notify the channel if the GPADL is done.
2764+
// Notify the channel of the completed GPADL.
27602765
sender.notifier.notify(
27612766
offer_id,
27622767
Action::Gpadl(gpadl_id, gpadl.count, gpadl.buf.clone()),
27632768
);
2764-
true
27652769
}
27662770
}
27672771

@@ -2793,12 +2797,22 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
27932797
Entry::Occupied(_) => return Err(ChannelError::DuplicateGpadlId),
27942798
};
27952799

2796-
// If we're not done, track the offer ID for GPADL body requests
2797-
// N.B. The above only checks if the combination of (gpadl_id, offer_id) is unique, which
2798-
// allows for a guest to reuse a gpadl ID in use by a reserved channel (which it may
2799-
// not know about). But for in-progress GPADLs we need to ensure the gpadl ID itself
2800-
// is unique, since the body message doesn't include a channel ID.
2801-
if !done {
2800+
if done {
2801+
Self::gpadl_completed(
2802+
self.inner
2803+
.pending_messages
2804+
.sender(self.notifier, self.inner.state.is_paused()),
2805+
offer_id,
2806+
channel,
2807+
input.gpadl_id,
2808+
gpadl,
2809+
)
2810+
} else {
2811+
// If we're not done, track the offer ID for GPADL body requests
2812+
// N.B. The above only checks if the combination of (gpadl_id, offer_id) is unique,
2813+
// which allows for a guest to reuse a gpadl ID in use by a reserved channel (which
2814+
// it may not know about). But for in-progress GPADLs we need to ensure the gpadl
2815+
// ID itself is unique, since the body message doesn't include a channel ID.
28022816
match self.inner.incomplete_gpadls.entry(input.gpadl_id) {
28032817
Entry::Vacant(entry) => {
28042818
entry.insert(offer_id);
@@ -2815,20 +2829,6 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
28152829
}
28162830
}
28172831
}
2818-
2819-
if done
2820-
&& !Self::gpadl_updated(
2821-
self.inner
2822-
.pending_messages
2823-
.sender(self.notifier, self.inner.state.is_paused()),
2824-
offer_id,
2825-
channel,
2826-
input.gpadl_id,
2827-
gpadl,
2828-
)
2829-
{
2830-
self.inner.gpadls.remove(&(input.gpadl_id, offer_id));
2831-
}
28322832
Ok(())
28332833
}
28342834

@@ -2881,17 +2881,15 @@ impl<'a, N: 'a + Notifier> ServerWithNotifier<'a, N> {
28812881
Ok(done) => {
28822882
if done {
28832883
self.inner.incomplete_gpadls.remove(&input.gpadl_id);
2884-
if !Self::gpadl_updated(
2884+
Self::gpadl_completed(
28852885
self.inner
28862886
.pending_messages
28872887
.sender(self.notifier, self.inner.state.is_paused()),
28882888
offer_id,
28892889
channel,
28902890
input.gpadl_id,
28912891
gpadl,
2892-
) {
2893-
self.inner.gpadls.remove(&(input.gpadl_id, offer_id));
2894-
}
2892+
)
28952893
}
28962894
}
28972895
Err(err) => {

vm/devices/vmbus/vmbus_server/src/tests.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,57 @@ async fn test_pause_resume(spawner: DefaultDriver) {
552552
assert!(matches!(poll!(channel.request_recv.next()), Poll::Pending));
553553
}
554554

555+
#[async_test]
556+
async fn test_gpadl_on_revoked_channel(spawner: DefaultDriver) {
557+
// Verify that the guest can create and tear down a GPADL for a channel that has been revoked
558+
// by the host but not yet released by the guest.
559+
let mut env = TestEnv::new(spawner);
560+
let channel = env.offer(1, false).await;
561+
env.vmbus.start();
562+
env.connect(1, protocol::FeatureFlags::new(), false).await;
563+
564+
// Revoke the channel from the host side. This sends a RESCIND_CHANNEL_OFFER to the guest, but
565+
// leaves the channel in the server's state until the guest releases it.
566+
channel
567+
.server_request_send
568+
.call(ChannelServerRequest::Revoke, ())
569+
.await
570+
.unwrap();
571+
env.expect_response(protocol::MessageType::RESCIND_CHANNEL_OFFER)
572+
.await;
573+
574+
// Create a GPADL for the revoked channel. The server should accept it.
575+
env.synic.send_message_core(
576+
OutgoingMessage::with_data(
577+
&protocol::GpadlHeader {
578+
channel_id: ChannelId(1),
579+
gpadl_id: GpadlId(10),
580+
count: 1,
581+
len: 16,
582+
},
583+
[1u64, 0u64].as_bytes(),
584+
),
585+
false,
586+
);
587+
let created = env.get_response::<protocol::GpadlCreated>().await;
588+
assert_eq!(created.channel_id, ChannelId(1));
589+
assert_eq!(created.gpadl_id, GpadlId(10));
590+
assert_eq!(created.status, protocol::STATUS_SUCCESS);
591+
592+
// Tear down the GPADL. The server should complete the teardown without notifying the device.
593+
env.synic.send_message(protocol::GpadlTeardown {
594+
channel_id: ChannelId(1),
595+
gpadl_id: GpadlId(10),
596+
});
597+
env.expect_response(protocol::MessageType::GPADL_TORNDOWN)
598+
.await;
599+
600+
// Release the channel so the server can clean up.
601+
env.synic.send_message(protocol::RelIdReleased {
602+
channel_id: ChannelId(1),
603+
});
604+
}
605+
555606
struct TestDeviceState {
556607
id: u32,
557608
started: bool,

0 commit comments

Comments
 (0)