Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TransmitCall and TransmitReturn #215

Merged
merged 19 commits into from
Oct 11, 2023
Merged
Changes from 17 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
193 changes: 190 additions & 3 deletions crates/ironrdp-rdpdr/src/pdu/esc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub enum ScardCall {
GetStatusChangeCall(GetStatusChangeCall),
ConnectCall(ConnectCall),
HCardAndDispositionCall(HCardAndDispositionCall),
TransmitCall(TransmitCall),
Unsupported,
}

Expand All @@ -43,6 +44,7 @@ impl ScardCall {
ScardIoCtlCode::BeginTransaction => Ok(ScardCall::HCardAndDispositionCall(
HCardAndDispositionCall::decode(src)?,
)),
ScardIoCtlCode::Transmit => Ok(ScardCall::TransmitCall(TransmitCall::decode(src)?)),
_ => {
warn!(?io_ctl_code, "Unsupported ScardIoCtlCode");
// TODO: maybe this should be an error
Expand Down Expand Up @@ -943,7 +945,8 @@ impl ndr::Decode for ConnectCommon {
let context = ScardContext::decode_ptr(src, index)?;
ensure_size!(in: src, size: size_of::<u32>() * 2);
let share_mode = src.read_u32();
let preferred_protocols = CardProtocol::from_bits_truncate(src.read_u32());
let preferred_protocols = CardProtocol::from_bits(src.read_u32())
.ok_or_else(|| invalid_message_err!("decode_ptr", "ConnectCommon", "invalid CardProtocol"))?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity: why from_bits_truncate wasn’t doing the job? I wonder if from_bits_retain wouldn’t be a better alternative (keeping the original payload as-is)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was doing the job, I just noticed that from_bits was more precise so decided to switch to using that. I tried it here as well

let current_state = CardStateFlags::from_bits_truncate(src.read_u32());
let event_state = CardStateFlags::from_bits_truncate(src.read_u32());

but it turns out that we actually do sometimes get junk bits in those fields 🤷‍♂️.

Copy link
Member

@CBenoit CBenoit Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-thinking about this, I really think we should use from_bits_retain everywhere now. My understanding is that it wasn’t used up until now because this method didn’t exist yet in older versions of the bitflags crate.

As much as possible I want parsing to be lossless and to uphold the "round-trip" property (m = encode(decode(m)))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand how from_bits_retain works. What if there are undefined bits?

Copy link
Member

@CBenoit CBenoit Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those bits are ignored, but not unset (unlike from_bits_truncate), i.e.: the underlying u32 is set exactly to the value received from the network

Actually, let me correct myself: we should either use from_bits or from_bits_retain, but never from_bits_truncate, and I would tend to reach for from_bits_retain over from_bits

Rationale:

  • We want the round-tripping property to hold, and for this property to hold, parsing must be non destructive (lossless), but from_bits_truncate is destructive (undefined bits are discarded)
  • We generally want lenient parsing, ignoring unknown values as long as we don’t need them and/or as long as ignoring them is causing no harm, but from_bits is not lenient

However, it’s okay to use from_bits if strictness is required at some place, but I would like such places to be documented with a comment explaining why we have to refuse unknown flags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, yes, from_bits_retain is much better than truncation via from_bits_truncate, which may prevent a hard-to-catch bug for us in the future! We may want to make refactoring on the whole codebase, I created an issue to do refactoring in future #217

Ok(Self {
context,
share_mode,
Expand Down Expand Up @@ -977,10 +980,10 @@ bitflags! {
/// [2.2.1.2 REDIR_SCARDHANDLE]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/b6276356-7c5f-4d3e-be92-a6c85e58d008
#[derive(Debug)]
pub struct ScardHandle {
context: ScardContext,
pub context: ScardContext,
/// Shortcut: we always create 4-byte handle values.
/// The spec allows this field to have variable length.
value: u32,
pub value: u32,
}

impl ScardHandle {
Expand Down Expand Up @@ -1122,3 +1125,187 @@ impl rpce::HeaderlessDecode for HCardAndDispositionCall {
Ok(Self { handle, disposition })
}
}

/// [2.2.2.19 Transmit_Call]
///
/// [2.2.2.19 Transmit_Call]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/e3861cfa-e61b-4d64-b19d-f6b31e076beb
#[derive(Debug)]
pub struct TransmitCall {
pub handle: ScardHandle,
pub send_pci: SCardIORequest,
pub send_length: u32,
pub send_buffer: Vec<u8>,
pub recv_pci: Option<SCardIORequest>,
pub recv_buffer_is_null: bool,
pub recv_length: u32,
}

impl TransmitCall {
const NAME: &'static str = "Transmit_Call";

pub fn decode(src: &mut ReadCursor<'_>) -> PduResult<Self> {
Ok(rpce::Pdu::<Self>::decode(src)?.into_inner())
}
}

impl rpce::HeaderlessDecode for TransmitCall {
fn decode(src: &mut ReadCursor<'_>) -> PduResult<Self> {
let mut index = 0;
let mut handle = ScardHandle::decode_ptr(src, &mut index)?;
let mut send_pci = SCardIORequest::decode_ptr(src, &mut index)?;
ensure_size!(in: src, size: size_of::<u32>());
let _send_length = src.read_u32();
let _send_buffer_ptr = ndr::decode_ptr(src, &mut index)?;
let recv_pci_ptr = ndr::decode_ptr(src, &mut index)?;
ensure_size!(in: src, size: size_of::<u32>() * 2);
let recv_buffer_is_null = src.read_u32() == 1;
let recv_length = src.read_u32();

handle.decode_value(src)?;
send_pci.decode_value(src)?;

ensure_size!(in: src, size: size_of::<u32>());
let send_length = src.read_u32();
ensure_size!(in: src, size: send_length as usize);
let send_buffer = src.read_slice(send_length as usize).to_vec();
ibeckermayer marked this conversation as resolved.
Show resolved Hide resolved

let recv_pci = if recv_pci_ptr != 0 {
let mut recv_pci = SCardIORequest::decode_ptr(src, &mut index)?;
recv_pci.decode_value(src)?;
Some(recv_pci)
} else {
None
};

Ok(Self {
handle,
send_pci,
send_length,
send_buffer,
recv_pci,
recv_buffer_is_null,
recv_length,
})
}
}

/// [2.2.1.8 SCardIO_Request]
///
/// [2.2.1.8 SCardIO_Request]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/f6e15da8-5bc0-4ef6-b28a-ce88e8415621
#[derive(Debug, Clone)]
pub struct SCardIORequest {
pub protocol: CardProtocol,
pub extra_bytes_length: u32,
pub extra_bytes: Vec<u8>,
}

impl SCardIORequest {
const NAME: &'static str = "SCardIO_Request";
}

impl ndr::Decode for SCardIORequest {
fn decode_ptr(src: &mut ReadCursor<'_>, index: &mut u32) -> PduResult<Self>
where
Self: Sized,
{
ensure_size!(in: src, size: size_of::<u32>() * 2);
let protocol = CardProtocol::from_bits(src.read_u32())
.ok_or_else(|| invalid_message_err!("decode_ptr", "SCardIORequest", "invalid CardProtocol"))?;
let extra_bytes_length = src.read_u32();
let _extra_bytes_ptr = ndr::decode_ptr(src, index)?;
let extra_bytes = Vec::new();
Ok(Self {
protocol,
extra_bytes_length,
extra_bytes,
})
}

fn decode_value(&mut self, src: &mut ReadCursor<'_>) -> PduResult<()> {
ensure_size!(in: src, size: self.extra_bytes_length as usize);
self.extra_bytes = src.read_slice(self.extra_bytes_length as usize).to_vec();
ibeckermayer marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
}

impl ndr::Encode for SCardIORequest {
fn encode_ptr(&self, index: &mut u32, dst: &mut WriteCursor<'_>) -> PduResult<()> {
ensure_size!(in: dst, size: self.size_ptr());
dst.write_u32(self.protocol.bits());
ndr::encode_ptr(Some(self.extra_bytes_length), index, dst)
}

fn encode_value(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
ensure_size!(in: dst, size: self.size_value());
dst.write_slice(&self.extra_bytes);
Ok(())
}

fn size_ptr(&self) -> usize {
4 /* dwProtocol */ + ndr::ptr_size(true)
}

fn size_value(&self) -> usize {
self.extra_bytes_length as usize
}
}

/// [2.2.3.11 Transmit_Return]
///
/// [2.2.3.11 Transmit_Return]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpesc/252cffd0-58b8-434d-9e1b-0d547544fb0f
#[derive(Debug)]
pub struct TransmitReturn {
pub return_code: ReturnCode,
pub recv_pci: Option<SCardIORequest>,
pub recv_buffer: Vec<u8>,
}

impl TransmitReturn {
const NAME: &'static str = "Transmit_Return";

pub fn new(return_code: ReturnCode, recv_pci: Option<SCardIORequest>, recv_buffer: Vec<u8>) -> rpce::Pdu<Self> {
rpce::Pdu(Self {
return_code,
recv_pci,
recv_buffer,
})
}
}

impl rpce::HeaderlessEncode for TransmitReturn {
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
ensure_size!(in: dst, size: self.size());
dst.write_u32(self.return_code.into());

let mut index = 0;
if let Some(recv_pci) = &self.recv_pci {
recv_pci.encode_ptr(&mut index, dst)?;
recv_pci.encode_value(dst)?;
} else {
dst.write_u32(0); // null value
}

let recv_buffer_len: u32 = cast_length!("TransmitReturn", "recv_buffer_len", self.recv_buffer.len())?;
ndr::encode_ptr(Some(recv_buffer_len), &mut index, dst)?;
dst.write_u32(recv_buffer_len);
dst.write_slice(&self.recv_buffer);

Ok(())
}

fn name(&self) -> &'static str {
Self::NAME
}

fn size(&self) -> usize {
self.return_code.size() // dst.write_u32(self.return_code.into());
+ if let Some(recv_pci) = &self.recv_pci {
recv_pci.size()
} else {
4 // null value
}
+ ndr::ptr_size(true) // ndr::encode_ptr(Some(recv_buffer_len), &mut index, dst)?;
+ 4 // dst.write_u32(recv_buffer_len);
+ self.recv_buffer.len() // dst.write_slice(&self.recv_buffer);
}
}