Skip to content

Commit 2af5d70

Browse files
committed
refactor(rdpeusb): seperate raw and validated InternalIoControl
Keep INTERNAL_IO_CONTROL as a lossless wire representation so unknown IOCTL codes and buffer fields remain decodable. Convert raw PDUs into supported backend requests at the state-machine boundary. This centralizes semantic validation and makes invalid outbound requests unrepresentable. Signed-off-by: uchouT <i@uchout.moe>
1 parent d767d99 commit 2af5d70

4 files changed

Lines changed: 52 additions & 85 deletions

File tree

crates/ironrdp-rdpeusb/src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ impl DvcProcessor for UrbdrcDeviceClient {
616616
return Ok(Vec::new());
617617
};
618618

619-
let internal_io_ctl_packet = internal_io_ctl_pdu.into();
619+
let internal_io_ctl_packet = internal_io_ctl_pdu.try_into()?;
620620
if let Some(internal_io_ctl_response) =
621621
self.backend
622622
.internal_io_control(channel_id, request_id, internal_io_ctl_packet)?

crates/ironrdp-rdpeusb/src/io/mod.rs

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ pub struct IoControlCompletionResult {
7070
pub information: u32,
7171
/// Data produced by the request.
7272
///
73-
/// Its length must not exceed the request's [`IoControlPacket::output_buffer_size`] or
74-
/// [`InternalIoControlPacket::output_buffer_size`]. For failures other than an
73+
/// Its length must not exceed the request's output buffer size. For failures other than an
7574
/// insufficient-buffer result, this must be empty.
7675
pub output_buffer: Vec<u8>,
7776
}
@@ -141,34 +140,39 @@ impl IoControlPacket {
141140

142141
/// Backend-facing form of an RDPEUSB `INTERNAL_IO_CONTROL` request.
143142
#[derive(Debug, Clone)]
144-
pub struct InternalIoControlPacket {
145-
/// RDPEUSB-defined internal operation to perform.
146-
pub ioctl_code: UsbInternalIoctlCode,
147-
/// Raw input supplied to the operation.
148-
pub input_buffer: Vec<u8>,
149-
/// Maximum number of bytes that may be returned in the completion's output buffer.
150-
pub output_buffer_size: u32,
143+
pub enum InternalIoControlPacket {
144+
QueryBusTime,
151145
}
152146

153147
impl InternalIoControlPacket {
154148
pub(crate) fn into_pdu(self, msg_id: MessageId, req_id: RequestId, udev_iface: InterfaceId) -> InternalIoControl {
155-
InternalIoControl {
156-
msg_id,
157-
udev_iface,
158-
ioctl_code: self.ioctl_code,
159-
input_buffer: self.input_buffer,
160-
output_buffer_size: self.output_buffer_size,
161-
req_id,
149+
match self {
150+
Self::QueryBusTime => InternalIoControl {
151+
msg_id,
152+
udev_iface,
153+
ioctl_code: UsbInternalIoctlCode::QUERY_BUS_TIME,
154+
input_buffer: Vec::new(),
155+
output_buffer_size: 4,
156+
req_id,
157+
},
162158
}
163159
}
164160
}
165161

166-
impl From<InternalIoControl> for InternalIoControlPacket {
167-
fn from(value: InternalIoControl) -> Self {
168-
Self {
169-
ioctl_code: value.ioctl_code,
170-
input_buffer: value.input_buffer,
171-
output_buffer_size: value.output_buffer_size,
162+
impl TryFrom<InternalIoControl> for InternalIoControlPacket {
163+
type Error = PduError;
164+
fn try_from(value: InternalIoControl) -> PduResult<Self> {
165+
match value.ioctl_code {
166+
UsbInternalIoctlCode::QUERY_BUS_TIME => {
167+
if !value.input_buffer.is_empty() {
168+
return Err(pdu_other_err!("internal io control input buffer must be empty"));
169+
}
170+
if value.output_buffer_size != 4 {
171+
return Err(pdu_other_err!("internal io control output buffer size must be 4"));
172+
}
173+
Ok(Self::QueryBusTime)
174+
}
175+
_ => Err(pdu_other_err!("unsupported InternalIoControl ioctl code")),
172176
}
173177
}
174178
}

crates/ironrdp-rdpeusb/src/pdu/usb_dev/mod.rs

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,10 @@ impl IoControl {
228228
};
229229
let input_buffer_size = src.read_u32().try_into().map_err(|e| other_err!(source: e))?;
230230
ensure_size!(in: src,
231-
size: input_buffer_size /* InputBuffer */ + 4 /* OutputBufferSize */ + 4 /* RequestId */);
231+
size: input_buffer_size);
232232
// TODO: size limit
233233
let input_buffer = src.read_slice(input_buffer_size).to_vec();
234+
ensure_size!(in: src, size: 4 /*output buffer size */ + 4 /* request id */);
234235
let output_buffer_size = src.read_u32();
235236
let req_id = src.read_u32();
236237
let io_control = Self {
@@ -374,11 +375,11 @@ impl IoctlInternalUsb {
374375
/// [\[MS-RDPEUSB\] 2.2.13 USB Internal IO Control Code][1].
375376
///
376377
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/55d1cd44-eda3-4cba-931c-c3cb8b3c3c92
377-
#[repr(u32)]
378-
#[non_exhaustive]
379-
#[derive(Debug, PartialEq, Clone)]
378+
#[derive(Debug, PartialEq, Clone, Copy)]
380379
#[doc(alias = "IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME")]
381-
pub enum UsbInternalIoctlCode {
380+
pub struct UsbInternalIoctlCode(pub u32);
381+
382+
impl UsbInternalIoctlCode {
382383
/// [\[MS-RDPEUSB\] 2.2.13.1 IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME][1].
383384
///
384385
/// Sent when the server receives a request its system to query the device's current frame
@@ -389,11 +390,9 @@ pub enum UsbInternalIoctlCode {
389390
///
390391
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/68506bc9-fedc-4fc1-b826-3cdbb1988774
391392
#[doc(alias = "IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME")]
392-
IoctlTsusbgdIoctlUsbdiQueryBusTime = 0x00224000,
393+
pub const QUERY_BUS_TIME: Self = Self(0x00224000);
393394
}
394395

395-
const IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME: u32 = 0x00224000;
396-
397396
/// [\[MS-RDPEUSB\] 2.2.6.4 Internal IO Control Message (INTERNAL_IO_CONTROL)][1] message.
398397
///
399398
/// Sent from the server to the client to submit an internal IO control request to the USB device.
@@ -404,28 +403,18 @@ const IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME: u32 = 0x00224000;
404403
pub struct InternalIoControl {
405404
pub msg_id: MessageId,
406405
pub udev_iface: InterfaceId,
407-
// Should make adding new codes easier.
408406
pub ioctl_code: UsbInternalIoctlCode,
409-
/// As of **v20240423**, all codes used for this message require sending an empty input buffer.
410-
///
411-
/// * [MS-RDPEUSB 2.2.13 USB Internal IO Control Code][1]
412-
///
413-
/// [1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeusb/55d1cd44-eda3-4cba-931c-c3cb8b3c3c92
414407
pub input_buffer: Vec<u8>,
415408
pub output_buffer_size: u32,
416409
pub req_id: RequestIdIoctl,
417410
}
418411

419412
impl InternalIoControl {
420-
#[expect(clippy::identity_op, reason = "for developer documentation purposes?")]
421-
pub const PAYLOAD_SIZE: usize = 4 // IoControlCode
413+
pub const PAYLOAD_MIN_SIZE: usize = 4 // IoControlCode
422414
+ 4 // InputBufferSize
423-
+ 0 // InputBuffer
424415
+ 4 // OutputBufferSize
425416
+ 4; // RequestId
426417

427-
pub const FIXED_PART_SIZE: usize = SharedMsgHeader::SIZE_REQ /* Header */ + Self::PAYLOAD_SIZE;
428-
429418
pub fn header(&self) -> SharedMsgHeader {
430419
SharedMsgHeader {
431420
iface_id: self.udev_iface.with_mask(Mask::Proxy),
@@ -435,40 +424,23 @@ impl InternalIoControl {
435424
}
436425

437426
pub(crate) fn decode(src: &mut ReadCursor<'_>, msg_id: MessageId, udev_iface: InterfaceId) -> DecodeResult<Self> {
438-
ensure_size!(in: src, size: Self::PAYLOAD_SIZE);
427+
ensure_size!(in: src, size: Self::PAYLOAD_MIN_SIZE);
439428

440-
{
441-
let code = src.read_u32();
442-
if code != IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME {
443-
return Err(unsupported_value_err!(
444-
"INTERNAL_IO_CONTROL::IoControlCode",
445-
format!("{code:#X}")
446-
));
447-
}
448-
}
449-
{
450-
let size = src.read_u32(/* InputBufferSize */);
451-
if size != 0 {
452-
return Err(unsupported_value_err!(
453-
"INTERNAL_IO_CONTROL::InputBufferSize",
454-
format!("{size:#X}")
455-
));
456-
}
457-
}
429+
let code = src.read_u32();
430+
431+
let size = src.read_u32().try_into().map_err(|e| other_err!(source: e))?;
432+
ensure_size!(in: src, size: size);
433+
let input_buffer = src.read_slice(size).to_vec();
434+
435+
ensure_size!(in: src, size: 4 /*output buffer size */ + 4 /* request id */);
458436
let output_buffer_size = src.read_u32(/* OutputBufferSize */);
459-
if output_buffer_size != 0x4 {
460-
return Err(unsupported_value_err!(
461-
"INTERNAL_IO_CONTROL::OutputBufferSize",
462-
format!("{output_buffer_size:#X}")
463-
));
464-
}
465437
let req_id = src.read_u32();
466438

467439
Ok(Self {
468440
msg_id,
469441
udev_iface,
470-
ioctl_code: UsbInternalIoctlCode::IoctlTsusbgdIoctlUsbdiQueryBusTime,
471-
input_buffer: Vec::new(),
442+
ioctl_code: UsbInternalIoctlCode(code),
443+
input_buffer,
472444
output_buffer_size,
473445
req_id,
474446
})
@@ -477,12 +449,12 @@ impl InternalIoControl {
477449

478450
impl Encode for InternalIoControl {
479451
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
480-
ensure_fixed_part_size!(in: dst);
481-
452+
ensure_size!(in: dst, size: self.size());
482453
self.header().encode(dst)?;
483-
dst.write_u32(IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME); // IoControlCode
484-
dst.write_u32(0x0); // InputBufferSize
485-
dst.write_u32(0x4); // OutputBufferSize
454+
dst.write_u32(self.ioctl_code.0); // IoControlCode
455+
dst.write_u32(self.input_buffer.len().try_into().map_err(|e| other_err!(source: e))?); // InputBufferSize
456+
dst.write_slice(&self.input_buffer); // InputBuffer
457+
dst.write_u32(self.output_buffer_size); // OutputBufferSize
486458
dst.write_u32(self.req_id);
487459

488460
Ok(())
@@ -493,7 +465,7 @@ impl Encode for InternalIoControl {
493465
}
494466

495467
fn size(&self) -> usize {
496-
Self::FIXED_PART_SIZE
468+
SharedMsgHeader::SIZE_REQ + Self::PAYLOAD_MIN_SIZE + self.input_buffer.len()
497469
}
498470
}
499471

crates/ironrdp-rdpeusb/src/server.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -307,20 +307,11 @@ impl UrbdrcDeviceServer {
307307
let udev_iface = self.usb_device_iface()?;
308308
let request_id = self.request_id_alloc.alloc();
309309

310-
// Currently, INTERNAL_IO_CONTROL is specified with an empty input buffer and a fixed 4-byte output buffer.
311-
if !internal_io_ctl_packet.input_buffer.is_empty() {
312-
return Err(pdu_other_err!("internal io control input buffer must be empty"));
313-
}
314-
if internal_io_ctl_packet.output_buffer_size != 4 {
315-
return Err(pdu_other_err!("internal io control output buffer size must be 4"));
316-
}
317-
318-
let output_buffer_size = 4;
319310
let request = internal_io_ctl_packet.into_pdu(self.msg_alloc.alloc(), request_id, udev_iface);
320311
self.insert_pending_io(
321312
request_id,
322313
Pending::InternalIoCtl {
323-
max_output_buf_size: output_buffer_size,
314+
max_output_buf_size: request.output_buffer_size,
324315
},
325316
)?;
326317

0 commit comments

Comments
 (0)