diff --git a/crates/ironrdp-pdu/src/rdp/client_info.rs b/crates/ironrdp-pdu/src/rdp/client_info.rs index 116363ef58..5ba502b7e9 100644 --- a/crates/ironrdp-pdu/src/rdp/client_info.rs +++ b/crates/ironrdp-pdu/src/rdp/client_info.rs @@ -149,8 +149,13 @@ impl<'de> Decode<'de> for ClientInfo { let code_page = src.read_u32(); let flags_with_compression_type = src.read_u32(); - let flags = ClientInfoFlags::from_bits(flags_with_compression_type & !COMPRESSION_TYPE_MASK) - .ok_or_else(|| invalid_field_err!("flags", "invalid ClientInfoFlags", in: src))?; + // [MS-RDPBCGR] 3.3.5.3.11 asks the server to validate lengths and to + // test the UNICODE flag, never to validate this bit set, and the + // INFO_* list has grown several times; refusing unknown bits would + // reject newer clients at the login step. Retain them instead + // (crate-wide policy since #1144) so re-encoding preserves the wire + // value. + let flags = ClientInfoFlags::from_bits_retain(flags_with_compression_type & !COMPRESSION_TYPE_MASK); let compression_type = CompressionType::from_u32((flags_with_compression_type & COMPRESSION_TYPE_MASK) >> 9) .ok_or_else(|| invalid_field_err!("flags", "invalid CompressionType", in: src))?; diff --git a/crates/ironrdp-testsuite-core/tests/pdu/rdp.rs b/crates/ironrdp-testsuite-core/tests/pdu/rdp.rs index 55fda55381..1be585007c 100644 --- a/crates/ironrdp-testsuite-core/tests/pdu/rdp.rs +++ b/crates/ironrdp-testsuite-core/tests/pdu/rdp.rs @@ -1,4 +1,5 @@ use ironrdp_core::{Encode as _, decode, encode_vec}; +use ironrdp_pdu::rdp::client_info::ClientInfo; use ironrdp_testsuite_core::capsets::*; use ironrdp_testsuite_core::client_info::*; use ironrdp_testsuite_core::rdp::*; @@ -308,6 +309,26 @@ fn from_buffer_correct_parses_client_info_pdu_unicode() { ); } +#[test] +fn client_info_with_undefined_flag_bits_decodes_and_retains_them() { + // [MS-RDPBCGR] 2.2.1.11.1.1's INFO_* list keeps growing; a client setting + // a bit this library does not know yet must not be refused at the login + // step (3.3.5.3.11 mandates no validation of this field). The unknown bit + // is retained rather than dropped so re-encoding preserves the wire value. + const UNDEFINED_BIT: u32 = 0x0000_0004; // undefined in 2.2.1.11.1.1 + + let mut buffer = CLIENT_INFO_BUFFER_UNICODE.to_vec(); + let flags = u32::from_le_bytes(buffer[4..8].try_into().unwrap()) | UNDEFINED_BIT; + buffer[4..8].copy_from_slice(&flags.to_le_bytes()); + + let client_info: ClientInfo = decode(buffer.as_slice()).expect("undefined INFO bits are not fatal"); + + assert_ne!(client_info.flags.bits() & UNDEFINED_BIT, 0); + + let reencoded = encode_vec(&client_info).unwrap(); + assert_eq!(reencoded, buffer); +} + #[test] fn from_buffer_correct_parses_client_info_pdu_unicode_without_optional_fields() { assert_eq!(