smb_msg/ioctl/
msg.rs

1use super::{
2    common::{IoctlBuffer, IoctlRequestContent},
3    fsctl::*,
4};
5#[cfg(feature = "server")]
6use binrw::io::TakeSeekExt;
7use binrw::prelude::*;
8use modular_bitfield::prelude::*;
9use smb_dtyp::binrw_util::prelude::*;
10use smb_msg_derive::*;
11
12#[cfg(feature = "client")]
13use std::io::SeekFrom;
14
15use crate::{
16    FileId,
17    dfsc::{ReqGetDfsReferral, ReqGetDfsReferralEx, RespGetDfsReferral},
18};
19
20/// SMB2 IOCTL request packet for issuing file system control or device control commands.
21///
22/// Used to send implementation-specific FSCTL/IOCTL commands across the network.
23/// The structure size is fixed at 57 bytes regardless of the buffer size.
24///
25/// MS-SMB2 2.2.31
26#[smb_request(size = 57)]
27pub struct IoctlRequest {
28    reserved: u16,
29    /// Control code of the FSCTL/IOCTL method to execute
30    pub ctl_code: u32,
31    /// File identifier on which to perform the command
32    pub file_id: FileId,
33    /// Offset from SMB2 header to input data buffer
34    #[bw(calc = PosMarker::default())]
35    #[br(temp)]
36    _input_offset: PosMarker<u32>,
37    /// Size in bytes of the input data
38    #[bw(calc = PosMarker::default())]
39    #[br(temp)]
40    _input_count: PosMarker<u32>,
41    /// Maximum bytes server can return for input data in response
42    pub max_input_response: u32,
43    /// Must be set to 0 by client
44    #[bw(calc = 0)]
45    #[br(assert(output_offset == 0))]
46    #[br(temp)]
47    output_offset: u32,
48    /// Must be set to 0 by client
49    #[bw(calc = 0)]
50    #[br(assert(output_count == 0))]
51    #[br(temp)]
52    output_count: u32,
53    /// Maximum bytes server can return for output data in response
54    pub max_output_response: u32,
55    /// Indicates whether this is an IOCTL (0x00000000) or FSCTL (0x00000001) request
56    pub flags: IoctlRequestFlags,
57    reserved: u32,
58
59    /// Variable-length buffer containing input data for the FSCTL/IOCTL command
60    #[bw(write_with = PosMarker::write_aoff_size, args(&_input_offset, &_input_count))]
61    #[br(map_stream = |s| s.take_seek(_input_count.value as u64), args(ctl_code, flags))]
62    pub buffer: IoctlReqData,
63}
64
65#[cfg(all(feature = "client", not(feature = "server")))]
66/// This is a helper trait that defines, for a certain FSCTL request type,
67/// the response type and their matching FSCTL code.
68pub trait FsctlRequest: for<'a> BinWrite<Args<'a> = ()> + Into<IoctlReqData> {
69    type Response: FsctlResponseContent;
70    const FSCTL_CODE: FsctlCodes;
71}
72
73#[cfg(all(feature = "server", not(feature = "client")))]
74/// This is a helper trait that defines, for a certain FSCTL request type,
75/// the response type and their matching FSCTL code.
76pub trait FsctlRequest: for<'a> BinRead<Args<'a> = ()> + Into<IoctlReqData> {
77    type Response: FsctlResponseContent;
78    const FSCTL_CODE: FsctlCodes;
79}
80
81#[cfg(all(feature = "server", feature = "client"))]
82/// This is a helper trait that defines, for a certain FSCTL request type,
83/// the response type and their matching FSCTL code.
84pub trait FsctlRequest:
85    for<'a> BinWrite<Args<'a> = ()> + for<'b> BinRead<Args<'b> = ()> + Into<IoctlReqData>
86{
87    type Response: FsctlResponseContent;
88    const FSCTL_CODE: FsctlCodes;
89}
90
91macro_rules! ioctl_req_data {
92    ($($fsctl:ident: $model:ty, $response:ty, )+) => {
93        pastey::paste! {
94
95#[smb_request_binrw]
96#[br(import(ctl_code: u32, flags: IoctlRequestFlags))]
97pub enum IoctlReqData {
98    $(
99        #[doc = concat!(
100            "Ioctl request for FSCTL code `",
101            stringify!($fsctl),
102            "`."
103        )]
104        #[br(pre_assert(ctl_code == FsctlCodes::$fsctl as u32 && flags.is_fsctl()))]
105        [<Fsctl $fsctl:camel>]($model),
106    )+
107
108    /// General, non-smb FSCTL ioctl buffer.
109    ///
110    /// In case of an unsupported FSCTL code, this variant can be used to
111    /// pass raw bytes.
112    Ioctl(IoctlBuffer),
113}
114
115impl IoctlReqData {
116    pub fn get_size(&self) -> u32 {
117        use IoctlReqData::*;
118        match self {
119            $(
120                [<Fsctl $fsctl:camel>](data) => data.get_bin_size(),
121            )+
122            Ioctl(data) => data.len() as u32,
123        }
124    }
125}
126
127$(
128    impl FsctlRequest for $model {
129        type Response = $response;
130        const FSCTL_CODE: FsctlCodes = FsctlCodes::$fsctl;
131    }
132
133    impl From<$model> for IoctlReqData {
134        fn from(model: $model) -> IoctlReqData {
135            IoctlReqData::[<Fsctl $fsctl:camel>](model)
136        }
137    }
138)+
139        }
140    }
141}
142
143// TODO: Enable non-fsctl ioctls. currently, we only support FSCTLs.
144ioctl_req_data! {
145    PipePeek: PipePeekRequest, PipePeekResponse,
146    SrvEnumerateSnapshots: SrvEnumerateSnapshotsRequest, SrvEnumerateSnapshotsResponse,
147    SrvRequestResumeKey: SrvRequestResumeKeyRequest, SrvRequestResumeKey,
148    QueryNetworkInterfaceInfo: QueryNetworkInterfaceInfoRequest, NetworkInterfacesInfo,
149    SrvCopychunk: SrvCopychunkCopy, SrvCopychunkResponse,
150    SrvCopychunkWrite: SrvCopyChunkCopyWrite, SrvCopychunkResponse,
151    SrvReadHash: SrvReadHashReq, SrvReadHashRes,
152    LmrRequestResiliency: NetworkResiliencyRequest, LmrRequestResiliencyResponse,
153    ValidateNegotiateInfo: ValidateNegotiateInfoRequest, ValidateNegotiateInfoResponse,
154    DfsGetReferrals: ReqGetDfsReferral, RespGetDfsReferral,
155    PipeWait: PipeWaitRequest, PipeWaitResponse,
156    PipeTransceive: PipeTransceiveRequest, PipeTransceiveResponse,
157    SetReparsePoint: SetReparsePointRequest, SetReparsePointResponse,
158    DfsGetReferralsEx: ReqGetDfsReferralEx, RespGetDfsReferral,
159    FileLevelTrim: FileLevelTrimRequest, FileLevelTrimResponse,
160    QueryAllocatedRanges: QueryAllocRangesItem, QueryAllocRangesResult,
161    OffloadRead: OffloadReadRequest, OffloadReadResponse,
162}
163
164/// Flags field indicating how to process the IOCTL operation.
165///
166/// MS-SMB2 2.2.31
167#[smb_dtyp::mbitfield]
168pub struct IoctlRequestFlags {
169    /// When true (0x00000001), indicates this is an FSCTL request.
170    /// When false (0x00000000), indicates this is an IOCTL request.
171    pub is_fsctl: bool,
172    #[skip]
173    __: B31,
174}
175
176/// SMB2 IOCTL response packet containing results of an IOCTL request.
177///
178/// Sent by server to transmit the results of a client SMB2 IOCTL request.
179/// The structure size is fixed at 49 bytes regardless of the buffer size.
180///
181/// MS-SMB2 2.2.32
182#[smb_response(size = 49)]
183pub struct IoctlResponse {
184    reserved: u16,
185    /// Control code of the FSCTL/IOCTL method that was executed
186    pub ctl_code: u32,
187    /// File identifier on which the command was performed
188    pub file_id: FileId,
189    /// Offset from SMB2 header to the Buffer field (should be set to buffer offset)
190    #[bw(calc = PosMarker::default())]
191    #[br(temp)]
192    input_offset: PosMarker<u32>,
193    /// Should be set to zero (exception for pass-through operations)
194    #[bw(assert(in_buffer.is_empty()))] // there is an exception for pass-through operations.
195    #[bw(try_calc = in_buffer.len().try_into())]
196    #[br(assert(input_count == 0))]
197    #[br(temp)]
198    input_count: u32,
199
200    /// Offset to output data buffer (either 0 or input_offset + input_count rounded to multiple of 8)
201    #[br(assert(output_offset.value == 0 || output_offset.value == input_offset.value + input_count))]
202    #[bw(calc = PosMarker::default())]
203    #[br(temp)]
204    output_offset: PosMarker<u32>,
205    /// Size in bytes of the output data
206    #[bw(try_calc = out_buffer.len().try_into())]
207    #[br(temp)]
208    output_count: u32,
209
210    /// Flags
211    reserved: u32,
212
213    reserved: u32,
214
215    /// Input data buffer (typically empty for responses except pass-through operations)
216    #[br(seek_before = SeekFrom::Start(input_offset.value.into()))]
217    #[br(count = input_count)]
218    #[bw(write_with = PosMarker::write_aoff, args(&input_offset))]
219    pub in_buffer: Vec<u8>,
220
221    /// Output data buffer containing results of the FSCTL/IOCTL operation
222    #[br(seek_before = SeekFrom::Start(output_offset.value.into()))]
223    #[br(count = output_count)]
224    #[bw(write_with = PosMarker::write_aoff, args(&output_offset))]
225    pub out_buffer: Vec<u8>,
226}
227
228impl IoctlResponse {
229    #[cfg(feature = "client")]
230    /// Parses the FSCTL response output buffer into the specified response type.
231    ///
232    /// Validates that the control code matches the expected FSCTL codes for the
233    /// response type before attempting to parse the output buffer.
234    ///
235    /// # Errors
236    ///
237    /// Returns `MissingFsctlDefinition` if the control code doesn't match
238    /// any of the expected FSCTL codes for the response type.
239    pub fn parse_fsctl<T>(&self) -> crate::Result<T>
240    where
241        T: FsctlResponseContent,
242    {
243        if !T::FSCTL_CODES.iter().any(|&f| f as u32 == self.ctl_code) {
244            return Err(crate::SmbMsgError::MissingFsctlDefinition(self.ctl_code));
245        }
246        let mut cursor = std::io::Cursor::new(&self.out_buffer);
247        Ok(T::read_le(&mut cursor).unwrap())
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use smb_tests::*;
254
255    use crate::*;
256
257    use super::*;
258
259    const REQ_IOCTL_BUFFER_CONTENT: &'static str = "0500000310000000980000000300000080000000010039000000000013f8a58f166fb54482c28f2dae140df50000000001000000000000000000020000000000010000000000000000000200000000000500000000000000010500000000000515000000173da72e955653f915dff280e9030000000000000000000000000000000000000000000001000000000000000000000002000000";
260
261    test_request! {
262        Ioctl {
263            ctl_code: FsctlCodes::PipeTransceive as u32,
264                file_id: [
265                    0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0,
266                    0x0,
267                ]
268                .into(),
269                max_input_response: 0,
270                max_output_response: 1024,
271                flags: IoctlRequestFlags::new().with_is_fsctl(true),
272                buffer: IoctlReqData::FsctlPipeTransceive(
273                    IoctlBuffer::from(
274                        hex_to_u8_array! {REQ_IOCTL_BUFFER_CONTENT}
275                    ).into(),
276                ),
277        } => const_format::concatcp!("3900000017c01100280500000c000000850000000c0000007800000098000000000000000000000000000000000400000100000000000000", REQ_IOCTL_BUFFER_CONTENT)
278    }
279
280    // Just to make things pretty; do NOT edit.
281    const IOCTL_TEST_BUFFER_CONTENT: &'static str = "05000203100000000401000003000000ec00000001000000000002000000000001000000000000000000020000000000200000000000000001000000000000000c000e000000000000000200000000000000020000000000070000000000000000000000000000000600000000000000410056004900560056004d00000000000400000000000000010400000000000515000000173da72e955653f915dff28001000000000000000000020000000000010000000000000001000000000000000a000c00000000000000020000000000000000000000000006000000000000000000000000000000050000000000000061007600690076006e0000000100000000000000";
282
283    test_response! {
284        Ioctl {
285                ctl_code: FsctlCodes::PipeTransceive as u32,
286                file_id: [
287                    0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0,
288                    0x0,
289                ]
290                .into(),
291                in_buffer: vec![],
292                out_buffer: smb_tests::hex_to_u8_array! {IOCTL_TEST_BUFFER_CONTENT},
293        } => const_format::concatcp!("3100000017c01100280500000c000000850000000c000000700000000000000070000000040100000000000000000000",IOCTL_TEST_BUFFER_CONTENT)
294    }
295}