From d43b1dd0dc2e91cbf8788ae991a836dbe5a4f6e9 Mon Sep 17 00:00:00 2001 From: meanaverage <18336095+meanaverage@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:41:52 -0700 Subject: [PATCH 1/2] fix(dvc): drop data for a channel that is not open instead of failing A server can send data on a dynamic channel before it sees the client decline that channel in its Create Response. GNOME Remote Desktop does this for AUDIO_PLAYBACK_DVC right after the Create Request, so a client without an audio channel failed `process` with "access to non existing DVC channel" and the session ended about a second after connecting. Such data has nowhere to go: log it and drop it. --- crates/ironrdp-dvc/src/client.rs | 18 ++++++---- .../tests/dvc/client.rs | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 27806d8639..d3ad7b2273 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -12,7 +12,7 @@ use ironrdp_pdu::{self as pdu, decode_err, encode_err, pdu_other_err}; use ironrdp_svc::{ChannelFlags, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; use pdu::PduResult; use pdu::gcc::ChannelName; -use tracing::debug; +use tracing::{debug, warn}; use crate::pdu::{ CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu, @@ -470,11 +470,17 @@ impl DrdynvcClient { fn process_data(&mut self, data: DrdynvcDataPdu) -> PduResult> { let channel_id = data.channel_id(); - let messages = self - .dynamic_channels - .get_by_channel_id_mut(channel_id) - .ok_or_else(|| pdu_other_err!("access to non existing DVC channel"))? - .process(data)?; + let Some(channel) = self.dynamic_channels.get_by_channel_id_mut(channel_id) else { + // A server can send data on a channel before it sees the client decline it in the + // Create Response (GNOME Remote Desktop does this for AUDIO_PLAYBACK_DVC). The data has + // nowhere to go; dropping it is enough, ending the session over it is not warranted. + warn!( + channel_id, + "Dropping data for a dynamic virtual channel that is not open" + ); + return Ok(Vec::new()); + }; + let messages = channel.process(data)?; encode_dvc_messages(channel_id, messages, ChannelFlags::empty()).map_err(|e| encode_err!(e)) } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/client.rs b/crates/ironrdp-testsuite-core/tests/dvc/client.rs index a7838de40a..5b2004a44c 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/client.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/client.rs @@ -88,6 +88,41 @@ fn exchange_capabilities(client: &mut DrdynvcClient) { client.process(&caps).expect("Capabilities Request should be processed"); } +#[test] +fn data_for_a_channel_that_is_not_open_is_dropped() { + // A server can send data on a channel before it sees the client decline it in the Create + // Response (GNOME Remote Desktop does this for AUDIO_PLAYBACK_DVC). The session must survive it. + let mut client = DrdynvcClient::new(); + client + .attach_established_dynamic_channel(7, RecordedDvc::default()) + .expect("recorded channel should attach"); + let data = |channel_id: u32, payload: &[u8]| { + encode_vec(&DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(DataPdu::new( + channel_id, + payload.to_vec(), + )))) + .expect("DVC data should encode") + }; + + assert!( + client + .process(&data(4, b"declined")) + .expect("data for a channel that is not open should be dropped, not fail") + .is_empty() + ); + assert!( + client + .process(&data(7, b"open")) + .expect("open channels are unaffected") + .is_empty() + ); + + let channel = client + .get_dvc_by_channel_id::(7) + .expect("recorded channel should remain attached"); + assert_eq!(channel.processor().received, vec![b"open".to_vec()]); +} + #[test] fn established_dynamic_channel_routes_recorded_data_without_negotiation() { let mut client = DrdynvcClient::new(); From 6407c22f4815fc8045ef402c0c160461ab8a2f15 Mon Sep 17 00:00:00 2001 From: meanaverage <18336095+meanaverage@users.noreply.github.com> Date: Thu, 1 Oct 2026 02:34:28 -0700 Subject: [PATCH 2/2] fix(dvc): log dropped data for an unopened channel at debug As with a declined channel on the server side, this is not a fault, and a server may keep sending such data, so a warning per PDU would flood the log. --- crates/ironrdp-dvc/src/client.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index d3ad7b2273..282457b1f0 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -12,7 +12,7 @@ use ironrdp_pdu::{self as pdu, decode_err, encode_err, pdu_other_err}; use ironrdp_svc::{ChannelFlags, CompressionCondition, SvcClientProcessor, SvcMessage, SvcProcessor}; use pdu::PduResult; use pdu::gcc::ChannelName; -use tracing::{debug, warn}; +use tracing::debug; use crate::pdu::{ CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu, @@ -474,7 +474,9 @@ impl DrdynvcClient { // A server can send data on a channel before it sees the client decline it in the // Create Response (GNOME Remote Desktop does this for AUDIO_PLAYBACK_DVC). The data has // nowhere to go; dropping it is enough, ending the session over it is not warranted. - warn!( + // Logged at debug, as `DrdynvcServer` logs a declined channel: it is not a fault, and a + // server may keep sending such data for as long as the session lasts. + debug!( channel_id, "Dropping data for a dynamic virtual channel that is not open" );