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
15 changes: 11 additions & 4 deletions crates/ironrdp-pdu/src/gcc/core_data/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,17 @@ impl<'de> Decode<'de> for ClientCoreOptionalData {

try_or_return!(src.try_read_u8(), optional_data);

optional_data.server_selected_protocol = Some(
SecurityProtocol::from_bits(try_or_return!(src.try_read_u32(), optional_data))
.ok_or_else(|| invalid_field_err!("serverSelectedProtocol", "invalid security protocol", in: src))?,
);
// Echo of the selectedProtocol from the X.224 RDP Negotiation
// Response, which nego.rs already decodes with from_bits_retain; the
// echo must not be stricter than the original. [MS-RDPBCGR]
// 3.3.5.3.3's mandated handling is a value comparison against what
// the server actually sent (SHOULD drop on mismatch), which needs
// the value to survive decode. The protocol list has grown three
// times (RDSTLS, HYBRID_EX, RDSAAD).
optional_data.server_selected_protocol = Some(SecurityProtocol::from_bits_retain(try_or_return!(
src.try_read_u32(),
optional_data
)));

optional_data.desktop_physical_width = Some(try_or_return!(src.try_read_u32(), optional_data));
// physical height must be present, if the physical width is present
Expand Down
13 changes: 9 additions & 4 deletions crates/ironrdp-pdu/src/gcc/core_data/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,15 @@ impl<'de> Decode<'de> for ServerCoreOptionalData {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
let mut optional_data = Self::default();

optional_data.client_requested_protocols = Some(
SecurityProtocol::from_bits(try_or_return!(src.try_read_u32(), optional_data))
.ok_or_else(|| invalid_field_err!("clientReqProtocols", "invalid server security protocol", in: src))?,
);
// Echo of the requestedProtocols from the X.224 RDP Negotiation
// Request, which nego.rs already decodes with from_bits_retain; the
// echo must not be stricter than the original, and the client
// compares it against what it actually requested rather than
// validating the bit set.
optional_data.client_requested_protocols = Some(SecurityProtocol::from_bits_retain(try_or_return!(
src.try_read_u32(),
optional_data
)));

optional_data.early_capability_flags = Some(
ServerEarlyCapabilityFlags::from_bits(try_or_return!(src.try_read_u32(), optional_data))
Expand Down
43 changes: 43 additions & 0 deletions crates/ironrdp-testsuite-core/tests/pdu/gcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,49 @@ fn from_buffer_correctly_parses_server_core_data_without_optional_fields() {
assert_eq!(*SERVER_CORE_DATA, decode(buffer).unwrap());
}

#[test]
fn client_core_data_with_undefined_protocol_echo_bits_decodes_and_retains_them() {
// serverSelectedProtocol echoes the X.224 negotiation value, which
// nego.rs already accepts with retain; the echo must not be stricter.
// [MS-RDPBCGR] 3.3.5.3.3 handles this field by value comparison, so the
// value has to survive decode. The bit is retained so re-encoding
// preserves the wire value.
const UNDEFINED_BIT: u32 = 0x0000_0020; // undefined in 2.2.1.1.1

let mut buffer = CLIENT_OPTIONAL_CORE_DATA_TO_SERVER_SELECTED_PROTOCOL_BUFFER.to_vec();
let at = buffer.len() - 4;
let protocol = u32::from_le_bytes(buffer[at..].try_into().unwrap()) | UNDEFINED_BIT;
buffer[at..].copy_from_slice(&protocol.to_le_bytes());

let data: ClientCoreData = decode(buffer.as_slice()).expect("undefined echo bits are not fatal");

assert_ne!(
data.optional_data.server_selected_protocol.unwrap().bits() & UNDEFINED_BIT,
0
);
assert_eq!(encode_vec(&data).unwrap(), buffer);
}

#[test]
fn server_core_data_with_undefined_protocol_echo_bits_decodes_and_retains_them() {
// clientRequestedProtocols echoes the client's own X.224 request; the
// client compares it against what it actually requested rather than
// validating the bit set.
const UNDEFINED_BIT: u32 = 0x0000_0020; // undefined in 2.2.1.1.1

let mut buffer = SERVER_CORE_DATA_TO_REQUESTED_PROTOCOL_BUFFER.to_vec();
let protocol = u32::from_le_bytes(buffer[4..8].try_into().unwrap()) | UNDEFINED_BIT;
buffer[4..8].copy_from_slice(&protocol.to_le_bytes());

let data: ServerCoreData = decode(buffer.as_slice()).expect("undefined echo bits are not fatal");

assert_ne!(
data.optional_data.client_requested_protocols.unwrap().bits() & UNDEFINED_BIT,
0
);
assert_eq!(encode_vec(&data).unwrap(), buffer);
}

#[test]
fn from_buffer_correctly_parses_server_core_data_without_few_optional_fields() {
let buffer = SERVER_CORE_DATA_TO_REQUESTED_PROTOCOL_BUFFER;
Expand Down
Loading