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 | } |