Coverage Report

Created: 2025-10-28 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/krb5/src/kdc/ndr.c
Line
Count
Source
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* kdc/ndr.c - NDR encoding and decoding functions */
3
/*
4
 * Copyright (C) 2021 by Red Hat, Inc.
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 * * Redistributions of source code must retain the above copyright
12
 *   notice, this list of conditions and the following disclaimer.
13
 *
14
 * * Redistributions in binary form must reproduce the above copyright
15
 *   notice, this list of conditions and the following disclaimer in
16
 *   the documentation and/or other materials provided with the
17
 *   distribution.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30
 * OF THE POSSIBILITY OF SUCH DAMAGE.
31
 */
32
33
#include "k5-int.h"
34
#include "k5-input.h"
35
#include "k5-buf.h"
36
#include "k5-utf8.h"
37
#include "kdc_util.h"
38
39
struct encoded_wchars {
40
    uint16_t bytes_len;
41
    uint16_t num_wchars;
42
    uint8_t *encoded;
43
};
44
45
/*
46
 * MS-DTYP 2.3.10:
47
 *
48
 * typedef struct _RPC_UNICODE_STRING {
49
 *     unsigned short Length;
50
 *     unsigned short MaximumLength;
51
 *     [size_is(MaximumLength/2), length_is(Length/2)] WCHAR* Buffer;
52
 * } RPC_UNICODE_STRING, *PRPC_UNICODE_STRING;
53
 *
54
 * Note that Buffer is not a String - there's no termination.
55
 *
56
 * We don't actually decode Length and MaximumLength here - this is a
57
 * conformant-varying array, which means that (per DCE-1.1-RPC 14.3.7.2) where
58
 * those actually appear in the serialized data is variable depending on
59
 * whether the string is at top level of the struct or not.  (This also
60
 * affects where the pointer identifier appears.)
61
 *
62
 * See MS-RPCE 4.7 for what an RPC_UNICODE_STRING looks like when not at
63
 * top-level.
64
 */
65
static krb5_error_code
66
dec_wchar_pointer(struct k5input *in, char **out)
67
1.34k
{
68
1.34k
    const uint8_t *bytes;
69
1.34k
    uint32_t actual_count;
70
71
    /* Maximum count. */
72
1.34k
    (void)k5_input_get_uint32_le(in);
73
    /* Offset - all zeroes, "should" not be checked. */
74
1.34k
    (void)k5_input_get_uint32_le(in);
75
76
1.34k
    actual_count = k5_input_get_uint32_le(in);
77
1.34k
    if (actual_count > UINT32_MAX / 2)
78
34
        return ERANGE;
79
80
1.31k
    bytes = k5_input_get_bytes(in, actual_count * 2);
81
1.31k
    if (bytes == NULL || k5_utf16le_to_utf8(bytes, actual_count * 2, out) != 0)
82
160
        return EINVAL;
83
84
    /* Always align on 4. */
85
1.15k
    if (actual_count % 2 == 1)
86
261
        (void)k5_input_get_uint16_le(in);
87
88
1.15k
    return 0;
89
1.31k
}
90
91
static krb5_error_code
92
enc_wchar_pointer(const char *utf8, struct encoded_wchars *encoded_out)
93
561
{
94
561
    krb5_error_code ret;
95
561
    struct k5buf b;
96
561
    size_t utf16len, num_wchars;
97
561
    uint8_t *utf16;
98
99
561
    ret = k5_utf8_to_utf16le(utf8, &utf16, &utf16len);
100
561
    if (ret)
101
0
        return ret;
102
103
561
    num_wchars = utf16len / 2;
104
105
561
    k5_buf_init_dynamic(&b);
106
561
    k5_buf_add_uint32_le(&b, num_wchars + 1);
107
561
    k5_buf_add_uint32_le(&b, 0);
108
561
    k5_buf_add_uint32_le(&b, num_wchars);
109
561
    k5_buf_add_len(&b, utf16, utf16len);
110
111
561
    free(utf16);
112
113
561
    if (num_wchars % 2 == 1)
114
142
        k5_buf_add_uint16_le(&b, 0);
115
116
561
    ret = k5_buf_status(&b);
117
561
    if (ret)
118
0
        return ret;
119
120
561
    encoded_out->bytes_len = b.len;
121
561
    encoded_out->num_wchars = num_wchars;
122
561
    encoded_out->encoded = b.data;
123
561
    return 0;
124
561
}
125
126
/*
127
 * Decode a delegation info structure, leaving room to add an additional
128
 * service.
129
 *
130
 * MS-PAC 2.9:
131
 *
132
 * typedef struct _S4U_DELEGATION_INFO {
133
 *     RPC_UNICODE_STRING S4U2proxyTarget;
134
 *     ULONG TransitedListSize;
135
 *     [size_is(TransitedListSize)] PRPC_UNICODE_STRING S4UTransitedServices;
136
 * } S4U_DELEGATION_INFO, *PS4U_DELEGATION_INFO;
137
 */
138
krb5_error_code
139
ndr_dec_delegation_info(krb5_data *data, struct pac_s4u_delegation_info **out)
140
676
{
141
676
    krb5_error_code ret;
142
676
    struct pac_s4u_delegation_info *di = NULL;
143
676
    struct k5input in;
144
676
    uint32_t i, object_buffer_length, nservices;
145
676
    uint8_t version, endianness, common_header_length;
146
147
676
    *out = NULL;
148
149
676
    di = k5alloc(sizeof(*di), &ret);
150
676
    if (di == NULL)
151
0
        return ret;
152
153
676
    k5_input_init(&in, data->data, data->length);
154
155
    /* Common Type Header - MS-RPCE 2.2.6.1 */
156
676
    version = k5_input_get_byte(&in);
157
676
    endianness = k5_input_get_byte(&in);
158
676
    common_header_length = k5_input_get_uint16_le(&in);
159
676
    (void)k5_input_get_uint32_le(&in); /* Filler - 0xcccccccc. */
160
676
    if (version != 1 || endianness != 0x10 || common_header_length != 8) {
161
28
        ret = EINVAL;
162
28
        goto error;
163
28
    }
164
165
    /* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
166
648
    object_buffer_length = k5_input_get_uint32_le(&in);
167
648
    if (data->length < 16 || object_buffer_length != data->length - 16) {
168
87
        ret = EINVAL;
169
87
        goto error;
170
87
    }
171
172
561
    (void)k5_input_get_uint32_le(&in); /* Filler - 0. */
173
174
    /* This code doesn't handle re-used pointers, which could come into play in
175
     * the unlikely case of a delegation loop. */
176
177
    /* Pointer.  Microsoft always starts at 00 00 02 00 */
178
561
    (void)k5_input_get_uint32_le(&in);
179
    /* Length of proxy target - 2 */
180
561
    (void)k5_input_get_uint16_le(&in);
181
    /* Length of proxy target */
182
561
    (void)k5_input_get_uint16_le(&in);
183
    /* Another pointer - 04 00 02 00.  Microsoft increments by 4 (le). */
184
561
    (void)k5_input_get_uint32_le(&in);
185
186
    /* Transited services length - header version. */
187
561
    (void)k5_input_get_uint32_le(&in);
188
189
    /* More pointer: 08 00 02 00 */
190
561
    (void)k5_input_get_uint32_le(&in);
191
192
561
    ret = dec_wchar_pointer(&in, &di->proxy_target);
193
561
    if (ret)
194
150
        goto error;
195
411
    nservices = k5_input_get_uint32_le(&in);
196
197
    /* Here, we have encoded 2 bytes of length, 2 bytes of (length + 2), and 4
198
     * bytes of pointer, for each element (deferred pointers). */
199
411
    if (nservices > data->length / 8) {
200
57
        ret = ERANGE;
201
57
        goto error;
202
57
    }
203
354
    (void)k5_input_get_bytes(&in, 8 * nservices);
204
205
    /* Since we're likely to add another entry, leave a blank at the end. */
206
354
    di->transited_services = k5calloc(nservices + 1, sizeof(char *), &ret);
207
354
    if (di->transited_services == NULL)
208
0
        goto error;
209
210
1.09k
    for (i = 0; i < nservices; i++) {
211
788
        ret = dec_wchar_pointer(&in, &di->transited_services[i]);
212
788
        if (ret)
213
44
            goto error;
214
744
        di->transited_services_length++;
215
744
    }
216
217
310
    ret = in.status;
218
310
    if (ret)
219
141
        goto error;
220
221
169
    *out = di;
222
169
    return 0;
223
224
507
error:
225
507
    ndr_free_delegation_info(di);
226
507
    return ret;
227
310
}
228
229
/* Empirically, Microsoft starts pointers at 00 00 02 00, and if treated little
230
 * endian, they increase by 4. */
231
static inline void
232
write_ptr(struct k5buf *buf, uint32_t *pointer)
233
899
{
234
899
    if (*pointer == 0)
235
169
        *pointer = 0x00020000;
236
899
    k5_buf_add_uint32_le(buf, *pointer);
237
899
    *pointer += 4;
238
899
}
239
240
krb5_error_code
241
ndr_enc_delegation_info(struct pac_s4u_delegation_info *in, krb5_data *out)
242
169
{
243
169
    krb5_error_code ret;
244
169
    size_t i;
245
169
    struct k5buf b = EMPTY_K5BUF;
246
169
    struct encoded_wchars pt_encoded = { 0 }, *tss_encoded = NULL;
247
169
    uint32_t pointer = 0;
248
249
    /* Encode ahead of time since we need the lengths. */
250
169
    ret = enc_wchar_pointer(in->proxy_target, &pt_encoded);
251
169
    if (ret)
252
0
        goto cleanup;
253
254
169
    tss_encoded = k5calloc(in->transited_services_length, sizeof(*tss_encoded),
255
169
                           &ret);
256
169
    if (tss_encoded == NULL)
257
0
        goto cleanup;
258
259
169
    k5_buf_init_dynamic(&b);
260
261
    /* Common Type Header - MS-RPCE 2.2.6.1 */
262
169
    k5_buf_add_len(&b, "\x01\x10\x08\x00", 4);
263
169
    k5_buf_add_uint32_le(&b, 0xcccccccc);
264
265
    /* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
266
169
    k5_buf_add_uint32_le(&b, 0); /* Skip over where payload length goes. */
267
169
    k5_buf_add_uint32_le(&b, 0); /* Filler - all zeroes. */
268
269
169
    write_ptr(&b, &pointer);
270
169
    k5_buf_add_uint16_le(&b, 2 * pt_encoded.num_wchars);
271
169
    k5_buf_add_uint16_le(&b, 2 * (pt_encoded.num_wchars + 1));
272
169
    write_ptr(&b, &pointer);
273
274
169
    k5_buf_add_uint32_le(&b, in->transited_services_length);
275
169
    write_ptr(&b, &pointer);
276
277
169
    k5_buf_add_len(&b, pt_encoded.encoded, pt_encoded.bytes_len);
278
279
169
    k5_buf_add_uint32_le(&b, in->transited_services_length);
280
281
    /* Deferred pointers. */
282
561
    for (i = 0; i < in->transited_services_length; i++) {
283
392
        ret = enc_wchar_pointer(in->transited_services[i], &tss_encoded[i]);
284
392
        if (ret)
285
0
            goto cleanup;
286
287
392
        k5_buf_add_uint16_le(&b, 2 * tss_encoded[i].num_wchars);
288
392
        k5_buf_add_uint16_le(&b, 2 * (tss_encoded[i].num_wchars + 1));
289
392
        write_ptr(&b, &pointer);
290
392
    }
291
292
561
    for (i = 0; i < in->transited_services_length; i++)
293
392
        k5_buf_add_len(&b, tss_encoded[i].encoded, tss_encoded[i].bytes_len);
294
295
    /* Now, pad to 8 bytes.  RPC_UNICODE_STRING is aligned on 4 bytes. */
296
169
    if (b.len % 8 != 0)
297
73
        k5_buf_add_uint32_le(&b, 0);
298
299
    /* Record the payload length where we skipped over it previously. */
300
169
    if (b.data != NULL)
301
169
        store_32_le(b.len - 0x10, ((uint8_t *)b.data) + 8);
302
303
169
    ret = k5_buf_status(&b);
304
169
    if (ret)
305
0
        goto cleanup;
306
307
169
    *out = make_data(b.data, b.len);
308
169
    b.data = NULL;
309
310
169
cleanup:
311
169
    free(b.data);
312
169
    free(pt_encoded.encoded);
313
561
    for (i = 0; tss_encoded != NULL && i < in->transited_services_length; i++)
314
392
        free(tss_encoded[i].encoded);
315
169
    free(tss_encoded);
316
169
    return ret;
317
169
}
318
319
void
320
ndr_free_delegation_info(struct pac_s4u_delegation_info *di)
321
1.18k
{
322
1.18k
    uint32_t i;
323
324
1.18k
    if (di == NULL)
325
507
        return;
326
676
    free(di->proxy_target);
327
1.42k
    for (i = 0; i < di->transited_services_length; i++)
328
744
        free(di->transited_services[i]);
329
676
    free(di->transited_services);
330
676
    free(di);
331
676
}