Coverage Report

Created: 2025-07-23 07:04

/src/samba/librpc/rpc/dcerpc_pkt_auth.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   Unix SMB/CIFS implementation.
3
   raw dcerpc operations
4
5
   Copyright (C) Andrew Tridgell 2003-2005
6
   Copyright (C) Jelmer Vernooij 2004-2005
7
8
   This program is free software; you can redistribute it and/or modify
9
   it under the terms of the GNU General Public License as published by
10
   the Free Software Foundation; either version 3 of the License, or
11
   (at your option) any later version.
12
13
   This program is distributed in the hope that it will be useful,
14
   but WITHOUT ANY WARRANTY; without even the implied warranty of
15
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
   GNU General Public License for more details.
17
18
   You should have received a copy of the GNU General Public License
19
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
*/
21
22
#include "replace.h"
23
#include "system/network.h"
24
#include <tevent.h>
25
#include "lib/util/util_file.h"
26
#include "lib/util/talloc_stack.h"
27
#include "lib/util/debug.h"
28
#include "lib/util/byteorder.h"
29
#include "lib/util/samba_util.h"
30
#include "librpc/rpc/dcerpc.h"
31
#include "librpc/rpc/dcerpc_util.h"
32
#include "librpc/rpc/dcerpc_pkt_auth.h"
33
#include "librpc/gen_ndr/ndr_dcerpc.h"
34
#include "rpc_common.h"
35
#include "lib/util/bitmap.h"
36
#include "auth/gensec/gensec.h"
37
#include "lib/util/mkdir_p.h"
38
#include "lib/crypto/gnutls_helpers.h"
39
#include <gnutls/crypto.h>
40
41
NTSTATUS dcerpc_ncacn_pull_pkt_auth(const struct dcerpc_auth *auth_state,
42
            struct gensec_security *gensec,
43
            bool check_pkt_auth_fields,
44
            TALLOC_CTX *mem_ctx,
45
            enum dcerpc_pkt_type ptype,
46
            uint8_t required_flags,
47
            uint8_t optional_flags,
48
            uint8_t payload_offset,
49
            DATA_BLOB *payload_and_verifier,
50
            DATA_BLOB *raw_packet,
51
            const struct ncacn_packet *pkt)
52
0
{
53
0
  NTSTATUS status;
54
0
  struct dcerpc_auth auth;
55
0
  uint32_t auth_length;
56
57
0
  if (auth_state == NULL) {
58
0
    return NT_STATUS_INTERNAL_ERROR;
59
0
  }
60
61
0
  status = dcerpc_verify_ncacn_packet_header(pkt, ptype,
62
0
          payload_and_verifier->length,
63
0
          required_flags, optional_flags);
64
0
  if (!NT_STATUS_IS_OK(status)) {
65
0
    return status;
66
0
  }
67
68
0
  switch (auth_state->auth_level) {
69
0
  case DCERPC_AUTH_LEVEL_PRIVACY:
70
0
  case DCERPC_AUTH_LEVEL_INTEGRITY:
71
0
  case DCERPC_AUTH_LEVEL_PACKET:
72
0
    break;
73
74
0
  case DCERPC_AUTH_LEVEL_CONNECT:
75
0
    if (pkt->auth_length != 0) {
76
0
      break;
77
0
    }
78
0
    return NT_STATUS_OK;
79
0
  case DCERPC_AUTH_LEVEL_NONE:
80
0
    if (pkt->auth_length != 0) {
81
0
      return NT_STATUS_ACCESS_DENIED;
82
0
    }
83
0
    return NT_STATUS_OK;
84
85
0
  default:
86
0
    return NT_STATUS_RPC_UNSUPPORTED_AUTHN_LEVEL;
87
0
  }
88
89
0
  if (pkt->auth_length == 0) {
90
0
    return NT_STATUS_RPC_PROTOCOL_ERROR;
91
0
  }
92
93
0
  if (gensec == NULL) {
94
0
    return NT_STATUS_INTERNAL_ERROR;
95
0
  }
96
97
0
  status = dcerpc_pull_auth_trailer(pkt, mem_ctx,
98
0
            payload_and_verifier,
99
0
            &auth, &auth_length, false);
100
0
  if (!NT_STATUS_IS_OK(status)) {
101
0
    return status;
102
0
  }
103
104
0
  if (payload_and_verifier->length < auth_length) {
105
    /*
106
     * should be checked in dcerpc_pull_auth_trailer()
107
     */
108
0
    return NT_STATUS_INTERNAL_ERROR;
109
0
  }
110
111
0
  payload_and_verifier->length -= auth_length;
112
113
0
  if (payload_and_verifier->length < auth.auth_pad_length) {
114
    /*
115
     * should be checked in dcerpc_pull_auth_trailer()
116
     */
117
0
    return NT_STATUS_INTERNAL_ERROR;
118
0
  }
119
120
0
  if (check_pkt_auth_fields) {
121
0
    if (auth.auth_type != auth_state->auth_type) {
122
0
      return NT_STATUS_ACCESS_DENIED;
123
0
    }
124
125
0
    if (auth.auth_level != auth_state->auth_level) {
126
0
      return NT_STATUS_ACCESS_DENIED;
127
0
    }
128
129
0
    if (auth.auth_context_id != auth_state->auth_context_id) {
130
0
      return NT_STATUS_ACCESS_DENIED;
131
0
    }
132
0
  }
133
134
  /* check signature or unseal the packet */
135
0
  switch (auth_state->auth_level) {
136
0
  case DCERPC_AUTH_LEVEL_PRIVACY:
137
0
    status = gensec_unseal_packet(gensec,
138
0
                raw_packet->data + payload_offset,
139
0
                payload_and_verifier->length,
140
0
                raw_packet->data,
141
0
                raw_packet->length -
142
0
                auth.credentials.length,
143
0
                &auth.credentials);
144
0
    if (!NT_STATUS_IS_OK(status)) {
145
0
      return NT_STATUS_RPC_SEC_PKG_ERROR;
146
0
    }
147
0
    memcpy(payload_and_verifier->data,
148
0
           raw_packet->data + payload_offset,
149
0
           payload_and_verifier->length);
150
0
    break;
151
152
0
  case DCERPC_AUTH_LEVEL_INTEGRITY:
153
0
  case DCERPC_AUTH_LEVEL_PACKET:
154
0
    status = gensec_check_packet(gensec,
155
0
               payload_and_verifier->data,
156
0
               payload_and_verifier->length,
157
0
               raw_packet->data,
158
0
               raw_packet->length -
159
0
               auth.credentials.length,
160
0
               &auth.credentials);
161
0
    if (!NT_STATUS_IS_OK(status)) {
162
0
      return NT_STATUS_RPC_SEC_PKG_ERROR;
163
0
    }
164
0
    break;
165
166
0
  case DCERPC_AUTH_LEVEL_CONNECT:
167
    /* for now we ignore possible signatures here */
168
0
    break;
169
170
0
  default:
171
0
    return NT_STATUS_RPC_UNSUPPORTED_AUTHN_LEVEL;
172
0
  }
173
174
  /*
175
   * remove the indicated amount of padding
176
   *
177
   * A possible overflow is checked above.
178
   */
179
0
  payload_and_verifier->length -= auth.auth_pad_length;
180
181
0
  return NT_STATUS_OK;
182
0
}
183
184
NTSTATUS dcerpc_ncacn_push_pkt_auth(const struct dcerpc_auth *auth_state,
185
            struct gensec_security *gensec,
186
            TALLOC_CTX *mem_ctx,
187
            DATA_BLOB *raw_packet,
188
            size_t sig_size,
189
            uint8_t payload_offset,
190
            const DATA_BLOB *payload,
191
            const struct ncacn_packet *pkt)
192
0
{
193
0
  TALLOC_CTX *frame = talloc_stackframe();
194
0
  NTSTATUS status;
195
0
  enum ndr_err_code ndr_err;
196
0
  struct ndr_push *ndr = NULL;
197
0
  uint32_t payload_length;
198
0
  uint32_t whole_length;
199
0
  DATA_BLOB blob = data_blob_null;
200
0
  DATA_BLOB sig = data_blob_null;
201
0
  struct dcerpc_auth _out_auth_info;
202
0
  struct dcerpc_auth *out_auth_info = NULL;
203
204
0
  *raw_packet = data_blob_null;
205
206
0
  if (auth_state == NULL) {
207
0
    TALLOC_FREE(frame);
208
0
    return NT_STATUS_INTERNAL_ERROR;
209
0
  }
210
211
0
  switch (auth_state->auth_level) {
212
0
  case DCERPC_AUTH_LEVEL_PRIVACY:
213
0
  case DCERPC_AUTH_LEVEL_INTEGRITY:
214
0
  case DCERPC_AUTH_LEVEL_PACKET:
215
0
    if (sig_size == 0) {
216
0
      TALLOC_FREE(frame);
217
0
      return NT_STATUS_INTERNAL_ERROR;
218
0
    }
219
220
0
    if (gensec == NULL) {
221
0
      TALLOC_FREE(frame);
222
0
      return NT_STATUS_INTERNAL_ERROR;
223
0
    }
224
225
0
    _out_auth_info = (struct dcerpc_auth) {
226
0
      .auth_type = auth_state->auth_type,
227
0
      .auth_level = auth_state->auth_level,
228
0
      .auth_context_id = auth_state->auth_context_id,
229
0
    };
230
0
    out_auth_info = &_out_auth_info;
231
0
    break;
232
233
0
  case DCERPC_AUTH_LEVEL_CONNECT:
234
    /*
235
     * TODO: let the gensec mech decide if it wants to generate a
236
     *       signature that might be needed for schannel...
237
     */
238
0
    if (sig_size != 0) {
239
0
      TALLOC_FREE(frame);
240
0
      return NT_STATUS_INTERNAL_ERROR;
241
0
    }
242
243
0
    if (gensec == NULL) {
244
0
      TALLOC_FREE(frame);
245
0
      return NT_STATUS_INTERNAL_ERROR;
246
0
    }
247
0
    break;
248
249
0
  case DCERPC_AUTH_LEVEL_NONE:
250
0
    if (sig_size != 0) {
251
0
      TALLOC_FREE(frame);
252
0
      return NT_STATUS_INTERNAL_ERROR;
253
0
    }
254
0
    break;
255
256
0
  default:
257
0
    TALLOC_FREE(frame);
258
0
    return NT_STATUS_INTERNAL_ERROR;
259
0
  }
260
261
0
  ndr = ndr_push_init_ctx(frame);
262
0
  if (ndr == NULL) {
263
0
    TALLOC_FREE(frame);
264
0
    return NT_STATUS_NO_MEMORY;
265
0
  }
266
267
0
  ndr_err = ndr_push_ncacn_packet(ndr, NDR_SCALARS|NDR_BUFFERS, pkt);
268
0
  if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
269
0
    TALLOC_FREE(frame);
270
0
    return ndr_map_error2ntstatus(ndr_err);
271
0
  }
272
273
0
  if (out_auth_info != NULL) {
274
    /*
275
     * pad to 16 byte multiple in the payload portion of the
276
     * packet. This matches what w2k3 does. Note that we can't use
277
     * ndr_push_align() as that is relative to the start of the
278
     * whole packet, whereas w2k8 wants it relative to the start
279
     * of the stub.
280
     */
281
0
    out_auth_info->auth_pad_length =
282
0
      DCERPC_AUTH_PAD_LENGTH(payload->length);
283
0
    ndr_err = ndr_push_zero(ndr, out_auth_info->auth_pad_length);
284
0
    if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
285
0
      TALLOC_FREE(frame);
286
0
      return ndr_map_error2ntstatus(ndr_err);
287
0
    }
288
289
0
    payload_length = payload->length +
290
0
      out_auth_info->auth_pad_length;
291
292
0
    ndr_err = ndr_push_dcerpc_auth(ndr, NDR_SCALARS|NDR_BUFFERS,
293
0
                 out_auth_info);
294
0
    if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
295
0
      TALLOC_FREE(frame);
296
0
      return ndr_map_error2ntstatus(ndr_err);
297
0
    }
298
299
0
    whole_length = ndr->offset;
300
301
0
    ndr_err = ndr_push_zero(ndr, sig_size);
302
0
    if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
303
0
      TALLOC_FREE(frame);
304
0
      return ndr_map_error2ntstatus(ndr_err);
305
0
    }
306
0
  } else {
307
0
    payload_length = payload->length;
308
0
    whole_length = ndr->offset;
309
0
  }
310
311
  /* extract the whole packet as a blob */
312
0
  blob = ndr_push_blob(ndr);
313
314
  /*
315
   * Setup the frag and auth length in the packet buffer.
316
   * This is needed if the GENSEC mech does AEAD signing
317
   * of the packet headers. The signature itself will be
318
   * appended later.
319
   */
320
0
  dcerpc_set_frag_length(&blob, blob.length);
321
0
  dcerpc_set_auth_length(&blob, sig_size);
322
323
  /* sign or seal the packet */
324
0
  switch (auth_state->auth_level) {
325
0
  case DCERPC_AUTH_LEVEL_PRIVACY:
326
0
    status = gensec_seal_packet(gensec,
327
0
              frame,
328
0
              blob.data + payload_offset,
329
0
              payload_length,
330
0
              blob.data,
331
0
              whole_length,
332
0
              &sig);
333
0
    if (!NT_STATUS_IS_OK(status)) {
334
0
      TALLOC_FREE(frame);
335
0
      return status;
336
0
    }
337
0
    break;
338
339
0
  case DCERPC_AUTH_LEVEL_INTEGRITY:
340
0
  case DCERPC_AUTH_LEVEL_PACKET:
341
0
    status = gensec_sign_packet(gensec,
342
0
              frame,
343
0
              blob.data + payload_offset,
344
0
              payload_length,
345
0
              blob.data,
346
0
              whole_length,
347
0
              &sig);
348
0
    if (!NT_STATUS_IS_OK(status)) {
349
0
      TALLOC_FREE(frame);
350
0
      return status;
351
0
    }
352
0
    break;
353
354
0
  case DCERPC_AUTH_LEVEL_CONNECT:
355
0
  case DCERPC_AUTH_LEVEL_NONE:
356
0
    break;
357
358
0
  default:
359
0
    TALLOC_FREE(frame);
360
0
    return NT_STATUS_INTERNAL_ERROR;
361
0
  }
362
363
0
  if (sig.length != sig_size) {
364
0
    TALLOC_FREE(frame);
365
0
    return NT_STATUS_RPC_SEC_PKG_ERROR;
366
0
  }
367
368
0
  if (sig_size != 0) {
369
0
    memcpy(blob.data + whole_length, sig.data, sig_size);
370
0
  }
371
372
0
  *raw_packet = blob;
373
0
  talloc_steal(mem_ctx, raw_packet->data);
374
0
  TALLOC_FREE(frame);
375
0
  return NT_STATUS_OK;
376
0
}
377
378
#ifdef DEVELOPER
379
380
/*
381
 * Save valid, well-formed DCE/RPC stubs to use as a seed for
382
 * ndr_fuzz_X
383
 */
384
void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx,
385
             DATA_BLOB raw_blob,
386
             const char *dump_dir,
387
             const char *iface_name,
388
             ndr_flags_type flags,
389
             int opnum,
390
             bool ndr64)
391
0
{
392
0
  char *fname = NULL;
393
0
  const char *sub_dir = NULL;
394
0
  TALLOC_CTX *temp_ctx = talloc_new(mem_ctx);
395
0
  DATA_BLOB blob;
396
0
  int ret, rc;
397
0
  uint8_t digest[20];
398
0
  DATA_BLOB digest_blob;
399
0
  char *digest_hex;
400
0
  uint16_t fuzz_flags = 0;
401
402
  /*
403
   * We want to save the 'stub' in a per-pipe subdirectory, with
404
   * the ndr_fuzz_X header 4 byte header. For the sake of
405
   * convenience (this is a developer only function), we mkdir
406
   * -p the sub-directories when they are needed.
407
   */
408
409
0
  if (dump_dir == NULL) {
410
0
    return;
411
0
  }
412
413
0
  temp_ctx = talloc_stackframe();
414
415
0
  sub_dir = talloc_asprintf(temp_ctx, "%s/%s",
416
0
          dump_dir,
417
0
          iface_name);
418
0
  if (sub_dir == NULL) {
419
0
    talloc_free(temp_ctx);
420
0
    return;
421
0
  }
422
0
  ret = mkdir_p(sub_dir, 0755);
423
0
  if (ret && errno != EEXIST) {
424
0
    DBG_ERR("could not create %s\n", sub_dir);
425
0
    talloc_free(temp_ctx);
426
0
    return;
427
0
  }
428
429
0
  blob.length = raw_blob.length + 4;
430
0
  blob.data = talloc_array(sub_dir,
431
0
         uint8_t,
432
0
         blob.length);
433
0
  if (blob.data == NULL) {
434
0
    DBG_ERR("could not allocate for fuzz seeds! (%s)\n",
435
0
      iface_name);
436
0
    talloc_free(temp_ctx);
437
0
    return;
438
0
  }
439
440
0
  if (ndr64) {
441
0
    fuzz_flags = 4;
442
0
  }
443
0
  if (flags & NDR_IN) {
444
0
    fuzz_flags |= 1;
445
0
  } else if (flags & NDR_OUT) {
446
0
    fuzz_flags |= 2;
447
0
  }
448
449
0
  SSVAL(blob.data, 0, fuzz_flags);
450
0
  SSVAL(blob.data, 2, opnum);
451
452
0
  memcpy(&blob.data[4],
453
0
         raw_blob.data,
454
0
         raw_blob.length);
455
456
  /*
457
   * This matches how oss-fuzz names the corpus input files, due
458
   * to a preference from libFuzzer
459
   */
460
0
  rc = gnutls_hash_fast(GNUTLS_DIG_SHA1,
461
0
            blob.data,
462
0
            blob.length,
463
0
            digest);
464
0
  if (rc < 0) {
465
    /*
466
     * This prints a better error message, eg if SHA1 is
467
     * disabled
468
     */
469
0
    NTSTATUS status = gnutls_error_to_ntstatus(rc,
470
0
              NT_STATUS_HASH_NOT_SUPPORTED);
471
0
    DBG_ERR("Failed to generate SHA1 to save fuzz seed: %s\n",
472
0
      nt_errstr(status));
473
0
    talloc_free(temp_ctx);
474
0
    return;
475
0
  }
476
477
0
  digest_blob.data = digest;
478
0
  digest_blob.length = sizeof(digest);
479
0
  digest_hex = data_blob_hex_string_lower(temp_ctx, &digest_blob);
480
481
0
  fname = talloc_asprintf(temp_ctx, "%s/%s",
482
0
        sub_dir,
483
0
        digest_hex);
484
0
  if (fname == NULL) {
485
0
    talloc_free(temp_ctx);
486
0
    return;
487
0
  }
488
489
  /*
490
   * If this fails, it is most likely because that file already
491
   * exists.  This is fine, it means we already have this
492
   * sample
493
   */
494
0
  file_save(fname,
495
0
      blob.data,
496
0
      blob.length);
497
498
0
  talloc_free(temp_ctx);
499
0
}
500
501
#endif /*if DEVELOPER, enveloping _dcesrv_save_ndr_fuzz_seed() */