Skip to content

Commit c85894c

Browse files
committed
Add minimal KDC MS-RPCE (NDR) encoder/decoder
Add NDR marshalling functions for S4U_DELEGATION_INFO PAC buffers. [[email protected]: added safety checks; made minor style changes; edited commit message]
1 parent ee4e3c5 commit c85894c

File tree

5 files changed

+516
-2
lines changed

5 files changed

+516
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ local.properties
256256
/src/kdc/kdc5_err.[ch]
257257
/src/kdc/krb5kdc
258258
/src/kdc/rtest
259+
/src/kdc/t_ndr
259260
/src/kdc/t_replay
260261

261262
/src/lib/k5sprt32.def

src/kdc/Makefile.in

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ SRCS= \
2020
$(srcdir)/kdc_preauth_ec.c \
2121
$(srcdir)/kdc_preauth_encts.c \
2222
$(srcdir)/main.c \
23+
$(srcdir)/ndr.c \
2324
$(srcdir)/policy.c \
2425
$(srcdir)/extern.c \
2526
$(srcdir)/replay.c \
@@ -43,6 +44,7 @@ OBJS= \
4344
kdc_preauth_ec.o \
4445
kdc_preauth_encts.o \
4546
main.o \
47+
ndr.o \
4648
policy.o \
4749
extern.o \
4850
replay.o \
@@ -69,10 +71,14 @@ krb5kdc: $(OBJS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(APPUTILS_DEPLIB) $(VE
6971
rtest: $(RT_OBJS) $(KDB5_DEPLIBS) $(KADM_COMM_DEPLIBS) $(KRB5_BASE_DEPLIBS)
7072
$(CC_LINK) -o rtest $(RT_OBJS) $(KDB5_LIBS) $(KADM_COMM_LIBS) $(KRB5_BASE_LIBS)
7173

72-
check-unix: rtest runenv.sh
74+
t_ndr: t_ndr.o ndr.o $(KRB5_BASE_DEPLIBS)
75+
$(CC_LINK) -o $@ t_ndr.o ndr.o $(KRB5_BASE_LIBS)
76+
77+
check-unix: rtest runenv.sh t_ndr
7378
$(RUN_TEST) $(srcdir)/rtscript > test.out
7479
cmp test.out $(srcdir)/rtest.good
7580
$(RM) test.out
81+
$(RUN_TEST) ./t_ndr > /dev/null
7682

7783
T_REPLAY_OBJS=t_replay.o
7884

@@ -92,4 +98,4 @@ install:
9298

9399
clean:
94100
$(RM) kdc5_err.h kdc5_err.c krb5kdc rtest.o rtest t_replay.o t_replay
95-
101+
$(RM) t_ndr.o t_ndr

src/kdc/deps

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ $(OUTPRE)main.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
173173
$(top_srcdir)/include/socket-utils.h extern.h kdc5_err.h \
174174
kdc_audit.h kdc_util.h main.c policy.h realm_data.h \
175175
reqstate.h
176+
$(OUTPRE)ndr.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
177+
$(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
178+
$(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_DEPS) \
179+
$(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
180+
$(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-input.h \
181+
$(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
182+
$(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
183+
$(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
184+
$(top_srcdir)/include/k5-utf8.h $(top_srcdir)/include/kdb.h \
185+
$(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
186+
$(top_srcdir)/include/krb5/kdcpreauth_plugin.h $(top_srcdir)/include/krb5/plugin.h \
187+
$(top_srcdir)/include/net-server.h $(top_srcdir)/include/port-sockets.h \
188+
$(top_srcdir)/include/socket-utils.h kdc_util.h ndr.c \
189+
realm_data.h reqstate.h
176190
$(OUTPRE)policy.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
177191
$(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
178192
$(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_DEPS) \

src/kdc/ndr.c

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
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+
{
68+
const uint8_t *bytes;
69+
uint32_t actual_count;
70+
71+
/* Maximum count. */
72+
(void)k5_input_get_uint32_le(in);
73+
/* Offset - all zeroes, "should" not be checked. */
74+
(void)k5_input_get_uint32_le(in);
75+
76+
actual_count = k5_input_get_uint32_le(in);
77+
if (actual_count > UINT32_MAX / 2)
78+
return ERANGE;
79+
80+
bytes = k5_input_get_bytes(in, actual_count * 2);
81+
if (bytes == NULL || k5_utf16le_to_utf8(bytes, actual_count * 2, out) != 0)
82+
return EINVAL;
83+
84+
/* Always align on 4. */
85+
if (actual_count % 2 == 1)
86+
(void)k5_input_get_uint16_le(in);
87+
88+
return 0;
89+
}
90+
91+
static krb5_error_code
92+
enc_wchar_pointer(const char *utf8, struct encoded_wchars *encoded_out)
93+
{
94+
krb5_error_code ret;
95+
struct k5buf b;
96+
size_t utf16len, num_wchars;
97+
uint8_t *utf16;
98+
99+
k5_buf_init_dynamic(&b);
100+
101+
ret = k5_utf8_to_utf16le(utf8, &utf16, &utf16len);
102+
if (ret)
103+
return ret;
104+
105+
num_wchars = utf16len / 2;
106+
107+
k5_buf_add_uint32_le(&b, num_wchars + 1);
108+
k5_buf_add_uint32_le(&b, 0);
109+
k5_buf_add_uint32_le(&b, num_wchars);
110+
k5_buf_add_len(&b, utf16, utf16len);
111+
112+
free(utf16);
113+
114+
if (num_wchars % 2 == 1)
115+
k5_buf_add_uint16_le(&b, 0);
116+
117+
ret = k5_buf_status(&b);
118+
if (ret)
119+
return ret;
120+
121+
encoded_out->bytes_len = b.len;
122+
encoded_out->num_wchars = num_wchars;
123+
encoded_out->encoded = b.data;
124+
return 0;
125+
}
126+
127+
/*
128+
* Decode a delegation info structure, leaving room to add an additional
129+
* service.
130+
*
131+
* MS-PAC 2.9:
132+
*
133+
* typedef struct _S4U_DELEGATION_INFO {
134+
* RPC_UNICODE_STRING S4U2proxyTarget;
135+
* ULONG TransitedListSize;
136+
* [size_is(TransitedListSize)] PRPC_UNICODE_STRING S4UTransitedServices;
137+
* } S4U_DELEGATION_INFO, *PS4U_DELEGATION_INFO;
138+
*/
139+
krb5_error_code
140+
ndr_dec_delegation_info(krb5_data *data, struct pac_s4u_delegation_info **out)
141+
{
142+
krb5_error_code ret;
143+
struct pac_s4u_delegation_info *di = NULL;
144+
struct k5input in;
145+
uint32_t i, object_buffer_length;
146+
uint8_t version, endianness, common_header_length;
147+
148+
*out = NULL;
149+
150+
di = k5alloc(sizeof(*di), &ret);
151+
if (di == NULL)
152+
return ret;
153+
154+
k5_input_init(&in, data->data, data->length);
155+
156+
/* Common Type Header - MS-RPCE 2.2.6.1 */
157+
version = k5_input_get_byte(&in);
158+
endianness = k5_input_get_byte(&in);
159+
common_header_length = k5_input_get_uint16_le(&in);
160+
(void)k5_input_get_uint32_le(&in); /* Filler - 0xcccccccc. */
161+
if (version != 1 || endianness != 0x10 || common_header_length != 8) {
162+
ret = EINVAL;
163+
goto error;
164+
}
165+
166+
/* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
167+
object_buffer_length = k5_input_get_uint32_le(&in);
168+
if (data->length < 16 || object_buffer_length != data->length - 16) {
169+
ret = EINVAL;
170+
goto error;
171+
}
172+
173+
(void)k5_input_get_uint32_le(&in); /* Filler - 0. */
174+
175+
/* This code doesn't handle re-used pointers, which could come into play in
176+
* the unlikely case of a delegation loop. */
177+
178+
/* Pointer. Microsoft always starts at 00 00 02 00 */
179+
(void)k5_input_get_uint32_le(&in);
180+
/* Length of proxy target - 2 */
181+
(void)k5_input_get_uint16_le(&in);
182+
/* Length of proxy target */
183+
(void)k5_input_get_uint16_le(&in);
184+
/* Another pointer - 04 00 02 00. Microsoft increments by 4 (le). */
185+
(void)k5_input_get_uint32_le(&in);
186+
187+
/* Transited services length - header version. */
188+
(void)k5_input_get_uint32_le(&in);
189+
190+
/* More pointer: 08 00 02 00 */
191+
(void)k5_input_get_uint32_le(&in);
192+
193+
ret = dec_wchar_pointer(&in, &di->proxy_target);
194+
if (ret)
195+
goto error;
196+
di->transited_services_length = k5_input_get_uint32_le(&in);
197+
198+
/* Here, we have encoded 2 bytes of length, 2 bytes of (length + 2), and 4
199+
* bytes of pointer, for each element (deferred pointers). */
200+
if (di->transited_services_length > UINT32_MAX / 8) {
201+
ret = ERANGE;
202+
goto error;
203+
}
204+
(void)k5_input_get_bytes(&in, 8 * di->transited_services_length);
205+
206+
/* Since we're likely to add another entry, leave a blank at the end. */
207+
di->transited_services = k5calloc(di->transited_services_length + 1,
208+
sizeof(char *), &ret);
209+
if (di->transited_services == NULL)
210+
goto error;
211+
212+
for (i = 0; i < di->transited_services_length; i++) {
213+
ret = dec_wchar_pointer(&in, &di->transited_services[i]);
214+
if (ret)
215+
goto error;
216+
}
217+
218+
ret = in.status;
219+
if (ret)
220+
goto error;
221+
222+
*out = di;
223+
return 0;
224+
225+
error:
226+
ndr_free_delegation_info(di);
227+
return ret;
228+
}
229+
230+
/* Empirically, Microsoft starts pointers at 00 00 02 00, and if treated little
231+
* endian, they increase by 4. */
232+
static inline void
233+
write_ptr(struct k5buf *buf, uint32_t *pointer)
234+
{
235+
if (*pointer == 0)
236+
*pointer = 0x00020000;
237+
k5_buf_add_uint32_le(buf, *pointer);
238+
*pointer += 4;
239+
}
240+
241+
krb5_error_code
242+
ndr_enc_delegation_info(struct pac_s4u_delegation_info *in, krb5_data *out)
243+
{
244+
krb5_error_code ret;
245+
size_t i;
246+
struct k5buf b;
247+
struct encoded_wchars pt_encoded = { 0 }, *tss_encoded = NULL;
248+
uint32_t pointer = 0;
249+
250+
/* Encode ahead of time since we need the lengths. */
251+
ret = enc_wchar_pointer(in->proxy_target, &pt_encoded);
252+
if (ret)
253+
goto cleanup;
254+
255+
tss_encoded = k5calloc(in->transited_services_length, sizeof(*tss_encoded),
256+
&ret);
257+
if (tss_encoded == NULL)
258+
goto cleanup;
259+
260+
k5_buf_init_dynamic(&b);
261+
262+
/* Common Type Header - MS-RPCE 2.2.6.1 */
263+
k5_buf_add_len(&b, "\x01\x10\x08\x00", 4);
264+
k5_buf_add_uint32_le(&b, 0xcccccccc);
265+
266+
/* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
267+
k5_buf_add_uint32_le(&b, 0); /* Skip over where payload length goes. */
268+
k5_buf_add_uint32_le(&b, 0); /* Filler - all zeroes. */
269+
270+
write_ptr(&b, &pointer);
271+
k5_buf_add_uint16_le(&b, 2 * pt_encoded.num_wchars);
272+
k5_buf_add_uint16_le(&b, 2 * (pt_encoded.num_wchars + 1));
273+
write_ptr(&b, &pointer);
274+
275+
k5_buf_add_uint32_le(&b, in->transited_services_length);
276+
write_ptr(&b, &pointer);
277+
278+
k5_buf_add_len(&b, pt_encoded.encoded, pt_encoded.bytes_len);
279+
280+
k5_buf_add_uint32_le(&b, in->transited_services_length);
281+
282+
/* Deferred pointers. */
283+
for (i = 0; i < in->transited_services_length; i++) {
284+
ret = enc_wchar_pointer(in->transited_services[i], &tss_encoded[i]);
285+
if (ret)
286+
goto cleanup;
287+
288+
k5_buf_add_uint16_le(&b, 2 * tss_encoded[i].num_wchars);
289+
k5_buf_add_uint16_le(&b, 2 * (tss_encoded[i].num_wchars + 1));
290+
write_ptr(&b, &pointer);
291+
}
292+
293+
for (i = 0; i < in->transited_services_length; i++)
294+
k5_buf_add_len(&b, tss_encoded[i].encoded, tss_encoded[i].bytes_len);
295+
296+
/* Now, pad to 8 bytes. RPC_UNICODE_STRING is aligned on 4 bytes. */
297+
if (b.len % 8 != 0)
298+
k5_buf_add_uint32_le(&b, 0);
299+
300+
/* Record the payload length where we skipped over it previously. */
301+
if (b.data != NULL)
302+
store_32_le(b.len - 0x10, ((uint8_t *)b.data) + 8);
303+
304+
ret = k5_buf_status(&b);
305+
if (ret)
306+
goto cleanup;
307+
308+
*out = make_data(b.data, b.len);
309+
b.data = NULL;
310+
311+
cleanup:
312+
free(b.data);
313+
free(pt_encoded.encoded);
314+
for (i = 0; tss_encoded != NULL && i < in->transited_services_length; i++)
315+
free(tss_encoded[i].encoded);
316+
free(tss_encoded);
317+
return ret;
318+
}
319+
320+
void
321+
ndr_free_delegation_info(struct pac_s4u_delegation_info *di)
322+
{
323+
uint32_t i;
324+
325+
if (di == NULL)
326+
return;
327+
free(di->proxy_target);
328+
for (i = 0; i < di->transited_services_length; i++)
329+
free(di->transited_services[i]);
330+
free(di->transited_services);
331+
free(di);
332+
}

0 commit comments

Comments
 (0)