Coverage Report

Created: 2026-01-16 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/libcli/smb/smb2cli_ioctl.c
Line
Count
Source
1
/*
2
   Unix SMB/CIFS implementation.
3
   smb2 lib
4
   Copyright (C) Stefan Metzmacher 2011
5
6
   This program is free software; you can redistribute it and/or modify
7
   it under the terms of the GNU General Public License as published by
8
   the Free Software Foundation; either version 3 of the License, or
9
   (at your option) any later version.
10
11
   This program is distributed in the hope that it will be useful,
12
   but WITHOUT ANY WARRANTY; without even the implied warranty of
13
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
   GNU General Public License for more details.
15
16
   You should have received a copy of the GNU General Public License
17
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
#include "includes.h"
21
#include "system/network.h"
22
#include "lib/util/tevent_ntstatus.h"
23
#include "smb_common.h"
24
#include "smbXcli_base.h"
25
#include "librpc/gen_ndr/ndr_ioctl.h"
26
27
struct smb2cli_ioctl_state {
28
  uint8_t fixed[0x38];
29
  uint8_t dyn_pad[1];
30
  uint32_t max_input_length;
31
  uint32_t max_output_length;
32
  struct iovec *recv_iov;
33
  bool out_valid;
34
  DATA_BLOB out_input_buffer;
35
  DATA_BLOB out_output_buffer;
36
  uint32_t ctl_code;
37
};
38
39
static void smb2cli_ioctl_done(struct tevent_req *subreq);
40
41
struct tevent_req *smb2cli_ioctl_send(TALLOC_CTX *mem_ctx,
42
              struct tevent_context *ev,
43
              struct smbXcli_conn *conn,
44
              uint32_t timeout_msec,
45
              struct smbXcli_session *session,
46
              struct smbXcli_tcon *tcon,
47
              uint64_t in_fid_persistent,
48
              uint64_t in_fid_volatile,
49
              uint32_t in_ctl_code,
50
              uint32_t in_max_input_length,
51
              const DATA_BLOB *in_input_buffer,
52
              uint32_t in_max_output_length,
53
              const DATA_BLOB *in_output_buffer,
54
              uint32_t in_flags)
55
0
{
56
0
  struct tevent_req *req, *subreq;
57
0
  struct smb2cli_ioctl_state *state;
58
0
  uint8_t *fixed;
59
0
  uint8_t *dyn;
60
0
  size_t dyn_len;
61
0
  uint32_t input_buffer_offset = 0;
62
0
  uint32_t input_buffer_length = 0;
63
0
  uint32_t output_buffer_offset = 0;
64
0
  uint32_t output_buffer_length = 0;
65
0
  uint32_t pad_length = 0;
66
0
  uint64_t tmp64;
67
0
  uint32_t max_dyn_len = 0;
68
69
0
  req = tevent_req_create(mem_ctx, &state,
70
0
        struct smb2cli_ioctl_state);
71
0
  if (req == NULL) {
72
0
    return NULL;
73
0
  }
74
0
  state->ctl_code = in_ctl_code;
75
0
  state->max_input_length = in_max_input_length;
76
0
  state->max_output_length = in_max_output_length;
77
78
0
  tmp64 = in_max_input_length;
79
0
  tmp64 += in_max_output_length;
80
0
  if (tmp64 > UINT32_MAX) {
81
0
    max_dyn_len = UINT32_MAX;
82
0
  } else {
83
0
    max_dyn_len = tmp64;
84
0
  }
85
86
0
  if (in_input_buffer) {
87
0
    input_buffer_offset = SMB2_HDR_BODY+0x38;
88
0
    input_buffer_length = in_input_buffer->length;
89
0
  }
90
91
0
  if (in_output_buffer) {
92
0
    output_buffer_offset = SMB2_HDR_BODY+0x38;
93
0
    output_buffer_length = in_output_buffer->length;
94
0
    if (input_buffer_length > 0 && output_buffer_length > 0) {
95
0
      uint32_t tmp;
96
0
      output_buffer_offset += input_buffer_length;
97
0
      tmp = output_buffer_offset;
98
0
      output_buffer_offset = NDR_ROUND(output_buffer_offset, 8);
99
0
      pad_length = output_buffer_offset - tmp;
100
0
    }
101
0
  }
102
103
0
  fixed = state->fixed;
104
105
0
  SSVAL(fixed, 0x00, 0x39);
106
0
  SSVAL(fixed, 0x02, 0); /* reserved */
107
0
  SIVAL(fixed, 0x04, in_ctl_code);
108
0
  SBVAL(fixed, 0x08, in_fid_persistent);
109
0
  SBVAL(fixed, 0x10, in_fid_volatile);
110
0
  SIVAL(fixed, 0x18, input_buffer_offset);
111
0
  SIVAL(fixed, 0x1C, input_buffer_length);
112
0
  SIVAL(fixed, 0x20, in_max_input_length);
113
0
  SIVAL(fixed, 0x24, output_buffer_offset);
114
0
  SIVAL(fixed, 0x28, output_buffer_length);
115
0
  SIVAL(fixed, 0x2C, in_max_output_length);
116
0
  SIVAL(fixed, 0x30, in_flags);
117
0
  SIVAL(fixed, 0x34, 0); /* reserved */
118
119
0
  if (input_buffer_length > 0 && output_buffer_length > 0) {
120
0
    size_t avail = UINT32_MAX - (input_buffer_length + pad_length);
121
0
    size_t ofs = output_buffer_offset - input_buffer_offset;
122
123
0
    if (avail < output_buffer_length) {
124
0
      tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
125
0
      return tevent_req_post(req, ev);
126
0
    }
127
128
0
    dyn_len = input_buffer_length + output_buffer_length + pad_length;
129
130
0
    dyn = talloc_zero_array(state, uint8_t, dyn_len);
131
0
    if (tevent_req_nomem(dyn, req)) {
132
0
      return tevent_req_post(req, ev);
133
0
    }
134
0
    memcpy(dyn, in_input_buffer->data,
135
0
           in_input_buffer->length);
136
0
    memcpy(dyn + ofs, in_output_buffer->data,
137
0
           in_output_buffer->length);
138
0
  } else if (input_buffer_length > 0) {
139
0
    dyn = in_input_buffer->data;
140
0
    dyn_len = in_input_buffer->length;
141
0
  } else if (output_buffer_length > 0) {
142
0
    dyn = in_output_buffer->data;
143
0
    dyn_len = in_output_buffer->length;
144
0
  } else {
145
0
    dyn = state->dyn_pad;
146
0
    dyn_len = sizeof(state->dyn_pad);
147
0
  }
148
149
0
  subreq = smb2cli_req_send(state, ev, conn, SMB2_OP_IOCTL,
150
0
          0, 0, /* flags */
151
0
          timeout_msec,
152
0
          tcon,
153
0
          session,
154
0
          state->fixed, sizeof(state->fixed),
155
0
          dyn, dyn_len,
156
0
          max_dyn_len);
157
0
  if (tevent_req_nomem(subreq, req)) {
158
0
    return tevent_req_post(req, ev);
159
0
  }
160
0
  tevent_req_set_callback(subreq, smb2cli_ioctl_done, req);
161
0
  return req;
162
0
}
163
164
static void smb2cli_ioctl_done(struct tevent_req *subreq)
165
0
{
166
0
  struct tevent_req *req =
167
0
    tevent_req_callback_data(subreq,
168
0
    struct tevent_req);
169
0
  struct smb2cli_ioctl_state *state =
170
0
    tevent_req_data(req,
171
0
    struct smb2cli_ioctl_state);
172
0
  NTSTATUS status;
173
0
  NTSTATUS error;
174
0
  struct iovec *iov;
175
0
  uint8_t *fixed;
176
0
  DATA_BLOB dyn_buffer = data_blob_null;
177
0
  uint32_t dyn_ofs = SMB2_HDR_BODY + 0x30;
178
0
  uint32_t input_min_offset;
179
0
  uint32_t input_buffer_offset;
180
0
  uint32_t input_buffer_length;
181
0
  uint32_t input_next_offset;
182
0
  uint32_t output_min_offset;
183
0
  uint32_t output_buffer_offset;
184
0
  uint32_t output_buffer_length;
185
0
  uint32_t output_next_offset;
186
0
  static const struct smb2cli_req_expected_response expected[] = {
187
0
  {
188
0
    .status = NT_STATUS_OK,
189
0
    .body_size = 0x31
190
0
  },
191
0
  {
192
0
    .status = STATUS_BUFFER_OVERFLOW,
193
0
    .body_size = 0x31
194
0
  },
195
0
  {
196
    /*
197
     * We need to make sure that
198
     * a response with NT_STATUS_FILE_CLOSED
199
     * without signing generates NT_STATUS_ACCESS_DENIED
200
     * if the request was signed.
201
     */
202
0
    .status = NT_STATUS_FILE_CLOSED,
203
0
    .body_size = 0x09,
204
0
  },
205
0
  {
206
    /*
207
     * a normal error
208
     */
209
0
    .status = NT_STATUS_INVALID_PARAMETER,
210
0
    .body_size = 0x09
211
0
  },
212
0
  {
213
    /*
214
     * a special case for FSCTL_SRV_COPYCHUNK_*
215
     */
216
0
    .status = NT_STATUS_INVALID_PARAMETER,
217
0
    .body_size = 0x31
218
0
  },
219
0
  };
220
221
0
  status = smb2cli_req_recv(subreq, state, &iov,
222
0
          expected, ARRAY_SIZE(expected));
223
0
  TALLOC_FREE(subreq);
224
0
  if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
225
0
    switch (state->ctl_code) {
226
0
    case FSCTL_SRV_COPYCHUNK:
227
0
    case FSCTL_SRV_COPYCHUNK_WRITE:
228
0
      break;
229
0
    default:
230
0
      tevent_req_nterror(req, status);
231
0
      return;
232
0
    }
233
234
0
    if (iov[1].iov_len != 0x30) {
235
0
      tevent_req_nterror(req,
236
0
          NT_STATUS_INVALID_NETWORK_RESPONSE);
237
0
      return;
238
0
    }
239
0
  } else if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)) {
240
    /* no error */
241
0
  } else {
242
0
    if (tevent_req_nterror(req, status)) {
243
0
      return;
244
0
    }
245
0
  }
246
247
  /*
248
   * At this stage we're sure that got a body size of 0x31,
249
   * either with NT_STATUS_OK, STATUS_BUFFER_OVERFLOW or
250
   * NT_STATUS_INVALID_PARAMETER.
251
   */
252
253
0
  state->recv_iov = iov;
254
0
  fixed = (uint8_t *)iov[1].iov_base;
255
0
  dyn_buffer = data_blob_const((uint8_t *)iov[2].iov_base,
256
0
             iov[2].iov_len);
257
258
0
  input_buffer_offset = IVAL(fixed, 0x18);
259
0
  input_buffer_length = IVAL(fixed, 0x1C);
260
0
  output_buffer_offset = IVAL(fixed, 0x20);
261
0
  output_buffer_length = IVAL(fixed, 0x24);
262
263
0
  input_min_offset = dyn_ofs;
264
0
  input_next_offset = dyn_ofs;
265
0
  error = smb2cli_parse_dyn_buffer(dyn_ofs,
266
0
           dyn_buffer,
267
0
           input_min_offset,
268
0
           input_buffer_offset,
269
0
           input_buffer_length,
270
0
           state->max_input_length,
271
0
           &input_next_offset,
272
0
           &state->out_input_buffer);
273
0
  if (tevent_req_nterror(req, error)) {
274
0
    return;
275
0
  }
276
277
  /*
278
   * If output data is returned, the output offset MUST be set to
279
   * InputOffset + InputCount rounded up to a multiple of 8.
280
   */
281
0
  output_min_offset = NDR_ROUND(input_next_offset, 8);
282
0
  output_next_offset = 0; /* this variable is completely ignored */
283
0
  error = smb2cli_parse_dyn_buffer(dyn_ofs,
284
0
           dyn_buffer,
285
0
           output_min_offset,
286
0
           output_buffer_offset,
287
0
           output_buffer_length,
288
0
           state->max_output_length,
289
0
           &output_next_offset,
290
0
           &state->out_output_buffer);
291
0
  if (tevent_req_nterror(req, error)) {
292
0
    return;
293
0
  }
294
295
0
  state->out_valid = true;
296
297
0
  if (tevent_req_nterror(req, status)) {
298
0
    return;
299
0
  }
300
301
0
  tevent_req_done(req);
302
0
}
303
304
NTSTATUS smb2cli_ioctl_recv(struct tevent_req *req,
305
          TALLOC_CTX *mem_ctx,
306
          DATA_BLOB *out_input_buffer,
307
          DATA_BLOB *out_output_buffer)
308
0
{
309
0
  struct smb2cli_ioctl_state *state =
310
0
    tevent_req_data(req,
311
0
    struct smb2cli_ioctl_state);
312
0
  NTSTATUS status = NT_STATUS_OK;
313
314
0
  if (tevent_req_is_nterror(req, &status) && !state->out_valid) {
315
0
    if (out_input_buffer) {
316
0
      *out_input_buffer = data_blob_null;
317
0
    }
318
0
    if (out_output_buffer) {
319
0
      *out_output_buffer = data_blob_null;
320
0
    }
321
0
    tevent_req_received(req);
322
0
    return status;
323
0
  }
324
325
0
  talloc_steal(mem_ctx, state->recv_iov);
326
0
  if (out_input_buffer) {
327
0
    *out_input_buffer = state->out_input_buffer;
328
0
  }
329
0
  if (out_output_buffer) {
330
0
    *out_output_buffer = state->out_output_buffer;
331
0
  }
332
333
0
  tevent_req_received(req);
334
0
  return status;
335
0
}
336
337
NTSTATUS smb2cli_ioctl(struct smbXcli_conn *conn,
338
           uint32_t timeout_msec,
339
           struct smbXcli_session *session,
340
           struct smbXcli_tcon *tcon,
341
           uint64_t in_fid_persistent,
342
           uint64_t in_fid_volatile,
343
           uint32_t in_ctl_code,
344
           uint32_t in_max_input_length,
345
           const DATA_BLOB *in_input_buffer,
346
           uint32_t in_max_output_length,
347
           const DATA_BLOB *in_output_buffer,
348
           uint32_t in_flags,
349
           TALLOC_CTX *mem_ctx,
350
           DATA_BLOB *out_input_buffer,
351
           DATA_BLOB *out_output_buffer)
352
0
{
353
0
  TALLOC_CTX *frame = talloc_stackframe();
354
0
  struct tevent_context *ev;
355
0
  struct tevent_req *req;
356
0
  NTSTATUS status = NT_STATUS_NO_MEMORY;
357
358
0
  if (smbXcli_conn_has_async_calls(conn)) {
359
    /*
360
     * Can't use sync call while an async call is in flight
361
     */
362
0
    status = NT_STATUS_INVALID_PARAMETER_MIX;
363
0
    goto fail;
364
0
  }
365
0
  ev = samba_tevent_context_init(frame);
366
0
  if (ev == NULL) {
367
0
    goto fail;
368
0
  }
369
0
  req = smb2cli_ioctl_send(frame, ev, conn, timeout_msec,
370
0
         session, tcon,
371
0
         in_fid_persistent,
372
0
         in_fid_volatile,
373
0
         in_ctl_code,
374
0
         in_max_input_length,
375
0
         in_input_buffer,
376
0
         in_max_output_length,
377
0
         in_output_buffer,
378
0
         in_flags);
379
0
  if (req == NULL) {
380
0
    goto fail;
381
0
  }
382
0
  if (!tevent_req_poll_ntstatus(req, ev, &status)) {
383
0
    goto fail;
384
0
  }
385
0
  status = smb2cli_ioctl_recv(req, mem_ctx,
386
0
            out_input_buffer,
387
0
            out_output_buffer);
388
0
 fail:
389
0
  TALLOC_FREE(frame);
390
0
  return status;
391
0
}
392
393
struct smb2cli_ioctl_pipe_wait_state {
394
  DATA_BLOB in_blob;
395
  DATA_BLOB out_blob;
396
};
397
398
static void smb2cli_ioctl_pipe_wait_done(struct tevent_req *subreq);
399
400
struct tevent_req *smb2cli_ioctl_pipe_wait_send(TALLOC_CTX *mem_ctx,
401
            struct tevent_context *ev,
402
            struct smbXcli_conn *conn,
403
            uint32_t timeout_msec,
404
            struct smbXcli_session *session,
405
            struct smbXcli_tcon *tcon,
406
            const char *pipe_name,
407
            uint64_t pipe_wait_timeout)
408
0
{
409
0
  struct tevent_req *req = NULL;
410
0
  struct tevent_req *subreq = NULL;
411
0
  struct smb2cli_ioctl_pipe_wait_state *state = NULL;
412
0
  struct fsctl_pipe_wait fsctl = {0};
413
0
  enum ndr_err_code err;
414
415
0
  req = tevent_req_create(mem_ctx, &state,
416
0
        struct smb2cli_ioctl_pipe_wait_state);
417
0
  if (req == NULL) {
418
0
    return NULL;
419
0
  }
420
421
0
  state->out_blob = data_blob_string_const("");
422
423
0
  fsctl.pipe_name = pipe_name;
424
0
  fsctl.timeout = pipe_wait_timeout;
425
0
  fsctl.timeout_specified = pipe_wait_timeout > 0 ? 1 : 0;
426
427
0
  err = ndr_push_struct_blob(&state->in_blob, mem_ctx, &fsctl,
428
0
           (ndr_push_flags_fn_t)ndr_push_fsctl_pipe_wait);
429
0
  if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
430
0
    return NULL;
431
0
  }
432
433
0
  subreq = smb2cli_ioctl_send(mem_ctx, ev, conn, timeout_msec,
434
0
            session, tcon,
435
0
            UINT64_MAX, UINT64_MAX,
436
0
            FSCTL_PIPE_WAIT,
437
0
            0, &state->in_blob,
438
0
            0, &state->out_blob,
439
0
            SMB2_IOCTL_FLAG_IS_FSCTL);
440
0
  if (tevent_req_nomem(subreq, req)) {
441
0
    return tevent_req_post(subreq, ev);
442
0
  }
443
0
  tevent_req_set_callback(subreq, smb2cli_ioctl_pipe_wait_done, req);
444
445
0
  return req;
446
0
}
447
448
static void smb2cli_ioctl_pipe_wait_done(struct tevent_req *subreq)
449
0
{
450
0
  struct tevent_req *req = tevent_req_callback_data(
451
0
    subreq, struct tevent_req);
452
0
  struct smb2cli_ioctl_pipe_wait_state *state = tevent_req_data(
453
0
    req, struct smb2cli_ioctl_pipe_wait_state);
454
0
  NTSTATUS status;
455
456
0
  status = smb2cli_ioctl_recv(subreq, state, NULL, NULL);
457
0
  TALLOC_FREE(subreq);
458
0
  if (tevent_req_nterror(req, status)) {
459
0
    return;
460
0
  }
461
462
0
  tevent_req_done(req);
463
0
}
464
465
466
NTSTATUS smb2cli_ioctl_pipe_wait_recv(struct tevent_req *req)
467
0
{
468
0
  NTSTATUS status;
469
470
0
  if (tevent_req_is_nterror(req, &status)) {
471
0
    tevent_req_received(req);
472
0
    return status;
473
0
  }
474
475
0
  tevent_req_received(req);
476
0
  return NT_STATUS_OK;
477
0
}
478
479
NTSTATUS smb2cli_ioctl_pipe_wait(struct smbXcli_conn *conn,
480
         uint32_t timeout_msec,
481
         struct smbXcli_session *session,
482
         struct smbXcli_tcon *tcon,
483
         const char *pipe_name,
484
         uint64_t pipe_wait_timeout)
485
0
{
486
0
  TALLOC_CTX *frame = talloc_stackframe();
487
0
  struct tevent_context *ev = NULL;
488
0
  struct tevent_req *req = NULL;
489
0
  NTSTATUS status = NT_STATUS_NO_MEMORY;
490
491
0
  if (smbXcli_conn_has_async_calls(conn)) {
492
    /*
493
     * Can't use sync call while an async call is in flight
494
     */
495
0
    status = NT_STATUS_INVALID_PARAMETER_MIX;
496
0
    goto fail;
497
0
  }
498
499
0
  ev = samba_tevent_context_init(frame);
500
0
  if (ev == NULL) {
501
0
    goto fail;
502
0
  }
503
504
0
  req = smb2cli_ioctl_pipe_wait_send(frame, ev, conn, timeout_msec,
505
0
             session, tcon,
506
0
             pipe_name, pipe_wait_timeout);
507
0
  if (req == NULL) {
508
0
    goto fail;
509
0
  }
510
0
  if (!tevent_req_poll_ntstatus(req, ev, &status)) {
511
0
    goto fail;
512
0
  }
513
514
0
  status = smb2cli_ioctl_pipe_wait_recv(req);
515
516
0
fail:
517
  TALLOC_FREE(frame);
518
0
  return status;
519
0
}