1 | /* |
2 | * Copyright (c) 2007-2020 Apple Inc. All rights reserved. |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
5 | * |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License |
8 | * Version 2.0 (the 'License'). You may not use this file except in |
9 | * compliance with the License. The rights granted to you under the License |
10 | * may not be used to create, or enable the creation or redistribution of, |
11 | * unlawful or unlicensed copies of an Apple operating system, or to |
12 | * circumvent, violate, or enable the circumvention or violation of, any |
13 | * terms of an Apple operating system software license agreement. |
14 | * |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. |
17 | * |
18 | * The Original Code and all software distributed under the License are |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
23 | * Please see the License for the specific language governing rights and |
24 | * limitations under the License. |
25 | * |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
27 | */ |
28 | |
29 | #include <nfs/nfs_conf.h> |
30 | #if CONFIG_NFS_SERVER |
31 | |
32 | /************* |
33 | * These functions implement RPCSEC_GSS security for the NFS client and server. |
34 | * The code is specific to the use of Kerberos v5 and the use of DES MAC MD5 |
35 | * protection as described in Internet RFC 2203 and 2623. |
36 | * |
37 | * In contrast to the original AUTH_SYS authentication, RPCSEC_GSS is stateful. |
38 | * It requires the client and server negotiate a secure connection as part of a |
39 | * security context. The context state is maintained in client and server structures. |
40 | * On the client side, each user of an NFS mount is assigned their own context, |
41 | * identified by UID, on their first use of the mount, and it persists until the |
42 | * unmount or until the context is renewed. Each user context has a corresponding |
43 | * server context which the server maintains until the client destroys it, or |
44 | * until the context expires. |
45 | * |
46 | * The client and server contexts are set up dynamically. When a user attempts |
47 | * to send an NFS request, if there is no context for the user, then one is |
48 | * set up via an exchange of NFS null procedure calls as described in RFC 2203. |
49 | * During this exchange, the client and server pass a security token that is |
50 | * forwarded via Mach upcall to the gssd, which invokes the GSS-API to authenticate |
51 | * the user to the server (and vice-versa). The client and server also receive |
52 | * a unique session key that can be used to digitally sign the credentials and |
53 | * verifier or optionally to provide data integrity and/or privacy. |
54 | * |
55 | * Once the context is complete, the client and server enter a normal data |
56 | * exchange phase - beginning with the NFS request that prompted the context |
57 | * creation. During this phase, the client's RPC header contains an RPCSEC_GSS |
58 | * credential and verifier, and the server returns a verifier as well. |
59 | * For simple authentication, the verifier contains a signed checksum of the |
60 | * RPC header, including the credential. The server's verifier has a signed |
61 | * checksum of the current sequence number. |
62 | * |
63 | * Each client call contains a sequence number that nominally increases by one |
64 | * on each request. The sequence number is intended to prevent replay attacks. |
65 | * Since the protocol can be used over UDP, there is some allowance for |
66 | * out-of-sequence requests, so the server checks whether the sequence numbers |
67 | * are within a sequence "window". If a sequence number is outside the lower |
68 | * bound of the window, the server silently drops the request. This has some |
69 | * implications for retransmission. If a request needs to be retransmitted, the |
70 | * client must bump the sequence number even if the request XID is unchanged. |
71 | * |
72 | * When the NFS mount is unmounted, the client sends a "destroy" credential |
73 | * to delete the server's context for each user of the mount. Since it's |
74 | * possible for the client to crash or disconnect without sending the destroy |
75 | * message, the server has a thread that reaps contexts that have been idle |
76 | * too long. |
77 | */ |
78 | |
79 | #include <sys/systm.h> |
80 | #include <sys/kauth.h> |
81 | #include <sys/mount_internal.h> |
82 | #include <sys/kpi_mbuf.h> |
83 | |
84 | #include <kern/host.h> |
85 | |
86 | #include <mach/host_priv.h> |
87 | #include <mach/vm_map.h> |
88 | #include <vm/vm_map.h> |
89 | #include <gssd/gssd_mach.h> |
90 | |
91 | #include <nfs/rpcv2.h> |
92 | #include <nfs/nfsproto.h> |
93 | #include <nfs/nfs.h> |
94 | #include <nfs/nfs_gss.h> |
95 | #include <nfs/xdr_subs.h> |
96 | #include <nfs/nfsm_subs.h> |
97 | #include <nfs/nfs_gss.h> |
98 | |
99 | #define NFS_GSS_MACH_MAX_RETRIES 3 |
100 | |
101 | #define NFSRV_GSS_DBG(...) NFSRV_DBG(NFSRV_FAC_GSS, 7, ## __VA_ARGS__) |
102 | |
103 | u_long nfs_gss_svc_ctx_hash; |
104 | struct nfs_gss_svc_ctx_hashhead *nfs_gss_svc_ctx_hashtbl; |
105 | static LCK_GRP_DECLARE(nfs_gss_svc_grp, "rpcsec_gss_svc" ); |
106 | static LCK_MTX_DECLARE(nfs_gss_svc_ctx_mutex, &nfs_gss_svc_grp); |
107 | uint32_t nfsrv_gss_context_ttl = GSS_CTX_EXPIRE; |
108 | #define GSS_SVC_CTX_TTL ((uint64_t)max(2*GSS_CTX_PEND, nfsrv_gss_context_ttl) * NSEC_PER_SEC) |
109 | |
110 | #define KRB5_MAX_MIC_SIZE 128 |
111 | static uint8_t xdrpad[] = { 0x00, 0x00, 0x00, 0x00}; |
112 | |
113 | static struct nfs_gss_svc_ctx *nfs_gss_svc_ctx_find(uint32_t); |
114 | static void nfs_gss_svc_ctx_insert(struct nfs_gss_svc_ctx *); |
115 | static void nfs_gss_svc_ctx_timer(void *, void *); |
116 | static int nfs_gss_svc_gssd_upcall(struct nfs_gss_svc_ctx *); |
117 | static int nfs_gss_svc_seqnum_valid(struct nfs_gss_svc_ctx *, uint32_t); |
118 | |
119 | /* This is only used by server code */ |
120 | static void nfs_gss_nfsm_chain(struct nfsm_chain *, mbuf_t); |
121 | |
122 | static void host_release_special_port(mach_port_t); |
123 | static void nfs_gss_mach_alloc_buffer(u_char *, size_t, vm_map_copy_t *); |
124 | static int nfs_gss_mach_vmcopyout(vm_map_copy_t, uint32_t, u_char *); |
125 | |
126 | static int nfs_gss_mchain_length(mbuf_t); |
127 | static int nfs_gss_append_chain(struct nfsm_chain *, mbuf_t); |
128 | static int nfs_gss_seqbits_size(uint32_t); |
129 | |
130 | thread_call_t nfs_gss_svc_ctx_timer_call; |
131 | int nfs_gss_timer_on = 0; |
132 | uint32_t nfs_gss_ctx_count = 0; |
133 | const uint32_t nfs_gss_ctx_max = GSS_SVC_MAXCONTEXTS; |
134 | |
135 | /* |
136 | * Common RPCSEC_GSS support routines |
137 | */ |
138 | |
139 | static errno_t |
140 | rpc_gss_prepend_32(mbuf_t *mb, uint32_t value) |
141 | { |
142 | int error; |
143 | uint32_t *data; |
144 | |
145 | #if 0 |
146 | data = mbuf_data(*mb); |
147 | /* |
148 | * If a wap token comes back and is not aligned |
149 | * get a new buffer (which should be aligned) to put the |
150 | * length in. |
151 | */ |
152 | if ((uintptr_t)data & 0x3) { |
153 | mbuf_t nmb; |
154 | |
155 | error = mbuf_get(MBUF_WAITOK, MBUF_TYPE_DATA, &nmb); |
156 | if (error) { |
157 | return error; |
158 | } |
159 | mbuf_setnext(nmb, *mb); |
160 | *mb = nmb; |
161 | } |
162 | #endif |
163 | error = mbuf_prepend(mbuf: mb, len: sizeof(uint32_t), how: MBUF_WAITOK); |
164 | if (error) { |
165 | return error; |
166 | } |
167 | |
168 | data = mbuf_data(mbuf: *mb); |
169 | *data = txdr_unsigned(value); |
170 | |
171 | return 0; |
172 | } |
173 | |
174 | /* |
175 | * Prepend the sequence number to the xdr encode argumen or result |
176 | * Sequence number is prepended in its own mbuf. |
177 | * |
178 | * On successful return mbp_head will point to the old mbuf chain |
179 | * prepended with a new mbuf that has the sequence number. |
180 | */ |
181 | |
182 | static errno_t |
183 | rpc_gss_data_create(mbuf_t *mbp_head, uint32_t seqnum) |
184 | { |
185 | int error; |
186 | mbuf_t mb; |
187 | struct nfsm_chain nmc; |
188 | struct nfsm_chain *nmcp = &nmc; |
189 | uint8_t *data; |
190 | |
191 | error = mbuf_get(how: MBUF_WAITOK, type: MBUF_TYPE_DATA, mbuf: &mb); |
192 | if (error) { |
193 | return error; |
194 | } |
195 | data = mbuf_data(mbuf: mb); |
196 | #if 0 |
197 | /* Reserve space for prepending */ |
198 | len = mbuf_maxlen(mb); |
199 | len = (len & ~0x3) - NFSX_UNSIGNED; |
200 | printf("%s: data = %p, len = %d\n" , __func__, data, (int)len); |
201 | error = mbuf_setdata(mb, data + len, 0); |
202 | if (error || mbuf_trailingspace(mb)) { |
203 | printf("%s: data = %p trailingspace = %d error = %d\n" , __func__, mbuf_data(mb), (int)mbuf_trailingspace(mb), error); |
204 | } |
205 | #endif |
206 | /* Reserve 16 words for prepending */ |
207 | error = mbuf_setdata(mbuf: mb, data: data + 16 * sizeof(uint32_t), len: 0); |
208 | nfsm_chain_init(nmcp, mb); |
209 | nfsm_chain_add_32(error, nmcp, seqnum); |
210 | nfsm_chain_build_done(error, nmcp); |
211 | if (error) { |
212 | return EINVAL; |
213 | } |
214 | mbuf_setnext(mbuf: nmcp->nmc_mcur, next: *mbp_head); |
215 | *mbp_head = nmcp->nmc_mhead; |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | /* |
221 | * Create an rpc_gss_integ_data_t given an argument or result in mb_head. |
222 | * On successful return mb_head will point to the rpc_gss_integ_data_t of length len. |
223 | * Note mb_head will now point to a 4 byte sequence number. len does not include |
224 | * any extra xdr padding. |
225 | * Returns 0 on success, else an errno_t |
226 | */ |
227 | |
228 | static errno_t |
229 | rpc_gss_integ_data_create(gss_ctx_id_t ctx, mbuf_t *mb_head, uint32_t seqnum, uint32_t *len) |
230 | { |
231 | uint32_t error; |
232 | uint32_t major; |
233 | uint32_t length; |
234 | gss_buffer_desc mic; |
235 | struct nfsm_chain nmc = {}; |
236 | |
237 | /* Length of the argument or result */ |
238 | length = nfs_gss_mchain_length(*mb_head); |
239 | if (len) { |
240 | *len = length; |
241 | } |
242 | error = rpc_gss_data_create(mbp_head: mb_head, seqnum); |
243 | if (error) { |
244 | return error; |
245 | } |
246 | |
247 | /* |
248 | * length is the length of the rpc_gss_data |
249 | */ |
250 | length += NFSX_UNSIGNED; /* Add the sequence number to the length */ |
251 | major = gss_krb5_get_mic_mbuf(&error, ctx, 0, *mb_head, 0, length, &mic); |
252 | if (major != GSS_S_COMPLETE) { |
253 | printf("gss_krb5_get_mic_mbuf failed %d\n" , error); |
254 | return error; |
255 | } |
256 | |
257 | error = rpc_gss_prepend_32(mb: mb_head, value: length); |
258 | if (error) { |
259 | return error; |
260 | } |
261 | |
262 | nfsm_chain_dissect_init(error, &nmc, *mb_head); |
263 | /* Append GSS mic token by advancing rpc_gss_data_t length + NFSX_UNSIGNED (size of the length field) */ |
264 | nfsm_chain_adv(error, &nmc, length + NFSX_UNSIGNED); |
265 | nfsm_chain_finish_mbuf(error, &nmc); // Force the mic into its own sub chain. |
266 | nfsm_chain_add_32(error, &nmc, mic.length); |
267 | nfsm_chain_add_opaque(error, &nmc, mic.value, mic.length); |
268 | nfsm_chain_build_done(error, &nmc); |
269 | gss_release_buffer(NULL, &mic); |
270 | |
271 | // printmbuf("rpc_gss_integ_data_create done", *mb_head, 0, 0); |
272 | assert(nmc.nmc_mhead == *mb_head); |
273 | |
274 | return error; |
275 | } |
276 | |
277 | /* |
278 | * Create an rpc_gss_priv_data_t out of the supplied raw arguments or results in mb_head. |
279 | * On successful return mb_head will point to a wrap token of lenght len. |
280 | * Note len does not include any xdr padding |
281 | * Returns 0 on success, else an errno_t |
282 | */ |
283 | static errno_t |
284 | rpc_gss_priv_data_create(gss_ctx_id_t ctx, mbuf_t *mb_head, uint32_t seqnum, uint32_t *len) |
285 | { |
286 | uint32_t error; |
287 | uint32_t major; |
288 | struct nfsm_chain nmc; |
289 | uint32_t pad; |
290 | uint32_t length; |
291 | |
292 | error = rpc_gss_data_create(mbp_head: mb_head, seqnum); |
293 | if (error) { |
294 | return error; |
295 | } |
296 | |
297 | length = nfs_gss_mchain_length(*mb_head); |
298 | major = gss_krb5_wrap_mbuf(&error, ctx, 1, 0, mb_head, 0, length, NULL); |
299 | if (major != GSS_S_COMPLETE) { |
300 | return error; |
301 | } |
302 | |
303 | length = nfs_gss_mchain_length(*mb_head); |
304 | if (len) { |
305 | *len = length; |
306 | } |
307 | pad = nfsm_pad(length); |
308 | |
309 | /* Prepend the opaque length of rep rpc_gss_priv_data */ |
310 | error = rpc_gss_prepend_32(mb: mb_head, value: length); |
311 | |
312 | if (error) { |
313 | return error; |
314 | } |
315 | if (pad) { |
316 | nfsm_chain_dissect_init(error, &nmc, *mb_head); |
317 | /* Advance the opauque size of length and length data */ |
318 | nfsm_chain_adv(error, &nmc, NFSX_UNSIGNED + length); |
319 | nfsm_chain_finish_mbuf(error, &nmc); |
320 | nfsm_chain_add_opaque_nopad(error, &nmc, xdrpad, pad); |
321 | nfsm_chain_build_done(error, &nmc); |
322 | } |
323 | |
324 | return error; |
325 | } |
326 | |
327 | /************* |
328 | * |
329 | * Server functions |
330 | */ |
331 | |
332 | /* |
333 | * Initialization when NFS starts |
334 | */ |
335 | void |
336 | nfs_gss_svc_init(void) |
337 | { |
338 | nfs_gss_svc_ctx_hashtbl = hashinit(SVC_CTX_HASHSZ, M_TEMP, hashmask: &nfs_gss_svc_ctx_hash); |
339 | |
340 | nfs_gss_svc_ctx_timer_call = thread_call_allocate(func: nfs_gss_svc_ctx_timer, NULL); |
341 | } |
342 | |
343 | /* |
344 | * Find a server context based on a handle value received |
345 | * in an RPCSEC_GSS credential. |
346 | */ |
347 | static struct nfs_gss_svc_ctx * |
348 | nfs_gss_svc_ctx_find(uint32_t handle) |
349 | { |
350 | struct nfs_gss_svc_ctx_hashhead *head; |
351 | struct nfs_gss_svc_ctx *cp; |
352 | uint64_t timenow; |
353 | |
354 | if (handle == 0) { |
355 | return NULL; |
356 | } |
357 | |
358 | head = &nfs_gss_svc_ctx_hashtbl[SVC_CTX_HASH(handle)]; |
359 | /* |
360 | * Don't return a context that is going to expire in GSS_CTX_PEND seconds |
361 | */ |
362 | clock_interval_to_deadline(GSS_CTX_PEND, NSEC_PER_SEC, result: &timenow); |
363 | |
364 | lck_mtx_lock(lck: &nfs_gss_svc_ctx_mutex); |
365 | |
366 | LIST_FOREACH(cp, head, gss_svc_entries) { |
367 | if (cp->gss_svc_handle == handle) { |
368 | if (timenow > cp->gss_svc_incarnation + GSS_SVC_CTX_TTL) { |
369 | /* |
370 | * Context has or is about to expire. Don't use. |
371 | * We'll return null and the client will have to create |
372 | * a new context. |
373 | */ |
374 | cp->gss_svc_handle = 0; |
375 | /* |
376 | * Make sure though that we stay around for GSS_CTX_PEND seconds |
377 | * for other threads that might be using the context. |
378 | */ |
379 | cp->gss_svc_incarnation = timenow; |
380 | |
381 | cp = NULL; |
382 | break; |
383 | } |
384 | lck_mtx_lock(lck: &cp->gss_svc_mtx); |
385 | cp->gss_svc_refcnt++; |
386 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
387 | break; |
388 | } |
389 | } |
390 | |
391 | lck_mtx_unlock(lck: &nfs_gss_svc_ctx_mutex); |
392 | |
393 | return cp; |
394 | } |
395 | |
396 | /* |
397 | * Insert a new server context into the hash table |
398 | * and start the context reap thread if necessary. |
399 | */ |
400 | static void |
401 | nfs_gss_svc_ctx_insert(struct nfs_gss_svc_ctx *cp) |
402 | { |
403 | struct nfs_gss_svc_ctx_hashhead *head; |
404 | struct nfs_gss_svc_ctx *p; |
405 | |
406 | lck_mtx_lock(lck: &nfs_gss_svc_ctx_mutex); |
407 | |
408 | /* |
409 | * Give the client a random handle so that if we reboot |
410 | * it's unlikely the client will get a bad context match. |
411 | * Make sure it's not zero or already assigned. |
412 | */ |
413 | retry: |
414 | cp->gss_svc_handle = random(); |
415 | if (cp->gss_svc_handle == 0) { |
416 | goto retry; |
417 | } |
418 | head = &nfs_gss_svc_ctx_hashtbl[SVC_CTX_HASH(cp->gss_svc_handle)]; |
419 | LIST_FOREACH(p, head, gss_svc_entries) |
420 | if (p->gss_svc_handle == cp->gss_svc_handle) { |
421 | goto retry; |
422 | } |
423 | |
424 | clock_interval_to_deadline(GSS_CTX_PEND, NSEC_PER_SEC, |
425 | result: &cp->gss_svc_incarnation); |
426 | LIST_INSERT_HEAD(head, cp, gss_svc_entries); |
427 | nfs_gss_ctx_count++; |
428 | |
429 | if (!nfs_gss_timer_on) { |
430 | nfs_gss_timer_on = 1; |
431 | |
432 | nfs_interval_timer_start(nfs_gss_svc_ctx_timer_call, |
433 | min(GSS_TIMER_PERIOD, b: max(GSS_CTX_TTL_MIN, b: nfsrv_gss_context_ttl)) * MSECS_PER_SEC); |
434 | } |
435 | |
436 | lck_mtx_unlock(lck: &nfs_gss_svc_ctx_mutex); |
437 | } |
438 | |
439 | /* |
440 | * This function is called via the kernel's callout |
441 | * mechanism. It runs only when there are |
442 | * cached RPCSEC_GSS contexts. |
443 | */ |
444 | void |
445 | nfs_gss_svc_ctx_timer(__unused void *param1, __unused void *param2) |
446 | { |
447 | struct nfs_gss_svc_ctx *cp, *next; |
448 | uint64_t timenow; |
449 | int contexts = 0; |
450 | int i; |
451 | |
452 | lck_mtx_lock(lck: &nfs_gss_svc_ctx_mutex); |
453 | clock_get_uptime(result: &timenow); |
454 | |
455 | NFSRV_GSS_DBG("is running\n" ); |
456 | |
457 | /* |
458 | * Scan all the hash chains |
459 | */ |
460 | for (i = 0; i < SVC_CTX_HASHSZ; i++) { |
461 | /* |
462 | * For each hash chain, look for entries |
463 | * that haven't been used in a while. |
464 | */ |
465 | LIST_FOREACH_SAFE(cp, &nfs_gss_svc_ctx_hashtbl[i], gss_svc_entries, next) { |
466 | contexts++; |
467 | if (timenow > cp->gss_svc_incarnation + |
468 | (cp->gss_svc_handle ? GSS_SVC_CTX_TTL : 0) |
469 | && cp->gss_svc_refcnt == 0) { |
470 | /* |
471 | * A stale context - remove it |
472 | */ |
473 | LIST_REMOVE(cp, gss_svc_entries); |
474 | NFSRV_GSS_DBG("Removing contex for %d\n" , cp->gss_svc_uid); |
475 | if (cp->gss_svc_seqbits) { |
476 | kfree_data(cp->gss_svc_seqbits, nfs_gss_seqbits_size(cp->gss_svc_seqwin)); |
477 | } |
478 | lck_mtx_destroy(lck: &cp->gss_svc_mtx, grp: &nfs_gss_svc_grp); |
479 | kfree_type(struct nfs_gss_svc_ctx, cp); |
480 | contexts--; |
481 | } |
482 | } |
483 | } |
484 | |
485 | nfs_gss_ctx_count = contexts; |
486 | |
487 | /* |
488 | * If there are still some cached contexts left, |
489 | * set up another callout to check on them later. |
490 | */ |
491 | nfs_gss_timer_on = nfs_gss_ctx_count > 0; |
492 | if (nfs_gss_timer_on) { |
493 | nfs_interval_timer_start(nfs_gss_svc_ctx_timer_call, |
494 | min(GSS_TIMER_PERIOD, b: max(GSS_CTX_TTL_MIN, b: nfsrv_gss_context_ttl)) * MSECS_PER_SEC); |
495 | } |
496 | |
497 | lck_mtx_unlock(lck: &nfs_gss_svc_ctx_mutex); |
498 | } |
499 | |
500 | /* |
501 | * Here the server receives an RPCSEC_GSS credential in an |
502 | * RPC call header. First there's some checking to make sure |
503 | * the credential is appropriate - whether the context is still |
504 | * being set up, or is complete. Then we use the handle to find |
505 | * the server's context and validate the verifier, which contains |
506 | * a signed checksum of the RPC header. If the verifier checks |
507 | * out, we extract the user's UID and groups from the context |
508 | * and use it to set up a UNIX credential for the user's request. |
509 | */ |
510 | int |
511 | nfs_gss_svc_cred_get(struct nfsrv_descript *nd, struct nfsm_chain *nmc) |
512 | { |
513 | uint32_t vers, proc, seqnum, service; |
514 | uint32_t handle, handle_len; |
515 | uint32_t major; |
516 | struct nfs_gss_svc_ctx *cp = NULL; |
517 | uint32_t flavor = 0; |
518 | int error = 0; |
519 | uint32_t arglen; |
520 | size_t argsize, start, ; |
521 | gss_buffer_desc cksum; |
522 | struct nfsm_chain nmc_tmp; |
523 | mbuf_t reply_mbuf, prev_mbuf, pad_mbuf; |
524 | |
525 | vers = proc = seqnum = service = handle_len = 0; |
526 | arglen = 0; |
527 | |
528 | nfsm_chain_get_32(error, nmc, vers); |
529 | if (vers != RPCSEC_GSS_VERS_1) { |
530 | error = NFSERR_AUTHERR | AUTH_REJECTCRED; |
531 | goto nfsmout; |
532 | } |
533 | |
534 | nfsm_chain_get_32(error, nmc, proc); |
535 | nfsm_chain_get_32(error, nmc, seqnum); |
536 | nfsm_chain_get_32(error, nmc, service); |
537 | nfsm_chain_get_32(error, nmc, handle_len); |
538 | if (error) { |
539 | goto nfsmout; |
540 | } |
541 | |
542 | /* |
543 | * Make sure context setup/destroy is being done with a nullproc |
544 | */ |
545 | if (proc != RPCSEC_GSS_DATA && nd->nd_procnum != NFSPROC_NULL) { |
546 | error = NFSERR_AUTHERR | RPCSEC_GSS_CREDPROBLEM; |
547 | goto nfsmout; |
548 | } |
549 | |
550 | /* |
551 | * If the sequence number is greater than the max |
552 | * allowable, reject and have the client init a |
553 | * new context. |
554 | */ |
555 | if (seqnum > GSS_MAXSEQ) { |
556 | error = NFSERR_AUTHERR | RPCSEC_GSS_CTXPROBLEM; |
557 | goto nfsmout; |
558 | } |
559 | |
560 | nd->nd_sec = |
561 | service == RPCSEC_GSS_SVC_NONE ? RPCAUTH_KRB5 : |
562 | service == RPCSEC_GSS_SVC_INTEGRITY ? RPCAUTH_KRB5I : |
563 | service == RPCSEC_GSS_SVC_PRIVACY ? RPCAUTH_KRB5P : 0; |
564 | |
565 | if (proc == RPCSEC_GSS_INIT) { |
566 | /* |
567 | * Limit the total number of contexts |
568 | */ |
569 | if (nfs_gss_ctx_count > nfs_gss_ctx_max) { |
570 | error = NFSERR_AUTHERR | RPCSEC_GSS_CTXPROBLEM; |
571 | goto nfsmout; |
572 | } |
573 | |
574 | /* |
575 | * Set up a new context |
576 | */ |
577 | cp = kalloc_type(struct nfs_gss_svc_ctx, |
578 | Z_WAITOK | Z_ZERO | Z_NOFAIL); |
579 | lck_mtx_init(lck: &cp->gss_svc_mtx, grp: &nfs_gss_svc_grp, LCK_ATTR_NULL); |
580 | cp->gss_svc_refcnt = 1; |
581 | } else { |
582 | /* |
583 | * Use the handle to find the context |
584 | */ |
585 | if (handle_len != sizeof(handle)) { |
586 | error = NFSERR_AUTHERR | RPCSEC_GSS_CREDPROBLEM; |
587 | goto nfsmout; |
588 | } |
589 | nfsm_chain_get_32(error, nmc, handle); |
590 | if (error) { |
591 | goto nfsmout; |
592 | } |
593 | cp = nfs_gss_svc_ctx_find(handle); |
594 | if (cp == NULL) { |
595 | error = NFSERR_AUTHERR | RPCSEC_GSS_CTXPROBLEM; |
596 | goto nfsmout; |
597 | } |
598 | } |
599 | |
600 | cp->gss_svc_proc = proc; |
601 | |
602 | if (proc == RPCSEC_GSS_DATA || proc == RPCSEC_GSS_DESTROY) { |
603 | struct posix_cred temp_pcred; |
604 | |
605 | if (cp->gss_svc_seqwin == 0) { |
606 | /* |
607 | * Context isn't complete |
608 | */ |
609 | error = NFSERR_AUTHERR | RPCSEC_GSS_CTXPROBLEM; |
610 | goto nfsmout; |
611 | } |
612 | |
613 | if (!nfs_gss_svc_seqnum_valid(cp, seqnum)) { |
614 | /* |
615 | * Sequence number is bad |
616 | */ |
617 | error = EINVAL; // drop the request |
618 | goto nfsmout; |
619 | } |
620 | |
621 | /* |
622 | * Validate the verifier. |
623 | * The verifier contains an encrypted checksum |
624 | * of the call header from the XID up to and |
625 | * including the credential. We compute the |
626 | * checksum and compare it with what came in |
627 | * the verifier. |
628 | */ |
629 | header_len = nfsm_chain_offset(nmc); |
630 | nfsm_chain_get_32(error, nmc, flavor); |
631 | nfsm_chain_get_32(error, nmc, cksum.length); |
632 | if (error) { |
633 | goto nfsmout; |
634 | } |
635 | if (flavor != RPCSEC_GSS || cksum.length > KRB5_MAX_MIC_SIZE) { |
636 | error = NFSERR_AUTHERR | AUTH_BADVERF; |
637 | } else { |
638 | cksum.value = kalloc_data(cksum.length, Z_WAITOK | Z_NOFAIL); |
639 | nfsm_chain_get_opaque(error, nmc, cksum.length, cksum.value); |
640 | } |
641 | if (error) { |
642 | goto nfsmout; |
643 | } |
644 | |
645 | /* Now verify the client's call header checksum */ |
646 | major = gss_krb5_verify_mic_mbuf((uint32_t *)&error, cp->gss_svc_ctx_id, nmc->nmc_mhead, 0, header_len, &cksum, NULL); |
647 | (void)gss_release_buffer(NULL, &cksum); |
648 | if (major != GSS_S_COMPLETE) { |
649 | printf("Server header: gss_krb5_verify_mic_mbuf failed %d\n" , error); |
650 | error = NFSERR_AUTHERR | RPCSEC_GSS_CTXPROBLEM; |
651 | goto nfsmout; |
652 | } |
653 | |
654 | nd->nd_gss_seqnum = seqnum; |
655 | |
656 | /* |
657 | * Set up the user's cred |
658 | */ |
659 | bzero(s: &temp_pcred, n: sizeof(temp_pcred)); |
660 | temp_pcred.cr_uid = cp->gss_svc_uid; |
661 | bcopy(src: cp->gss_svc_gids, dst: temp_pcred.cr_groups, |
662 | n: sizeof(gid_t) * cp->gss_svc_ngroups); |
663 | temp_pcred.cr_ngroups = (short)cp->gss_svc_ngroups; |
664 | |
665 | nd->nd_cr = posix_cred_create(pcred: &temp_pcred); |
666 | if (nd->nd_cr == NULL) { |
667 | error = ENOMEM; |
668 | goto nfsmout; |
669 | } |
670 | clock_get_uptime(result: &cp->gss_svc_incarnation); |
671 | |
672 | /* |
673 | * If the call arguments are integrity or privacy protected |
674 | * then we need to check them here. |
675 | */ |
676 | switch (service) { |
677 | case RPCSEC_GSS_SVC_NONE: |
678 | /* nothing to do */ |
679 | break; |
680 | case RPCSEC_GSS_SVC_INTEGRITY: |
681 | /* |
682 | * Here's what we expect in the integrity call args: |
683 | * |
684 | * - length of seq num + call args (4 bytes) |
685 | * - sequence number (4 bytes) |
686 | * - call args (variable bytes) |
687 | * - length of checksum token |
688 | * - checksum of seqnum + call args |
689 | */ |
690 | nfsm_chain_get_32(error, nmc, arglen); // length of args |
691 | if (arglen > NFS_MAXPACKET) { |
692 | error = EBADRPC; |
693 | goto nfsmout; |
694 | } |
695 | |
696 | nmc_tmp = *nmc; |
697 | nfsm_chain_adv(error, &nmc_tmp, arglen); |
698 | nfsm_chain_get_32(error, &nmc_tmp, cksum.length); |
699 | cksum.value = NULL; |
700 | if (cksum.length > 0 && cksum.length < GSS_MAX_MIC_LEN) { |
701 | cksum.value = kalloc_data(cksum.length, Z_WAITOK | Z_NOFAIL); |
702 | } else { |
703 | error = EBADRPC; |
704 | goto nfsmout; |
705 | } |
706 | nfsm_chain_get_opaque(error, &nmc_tmp, cksum.length, cksum.value); |
707 | |
708 | /* Verify the checksum over the call args */ |
709 | start = nfsm_chain_offset(nmc); |
710 | |
711 | major = gss_krb5_verify_mic_mbuf((uint32_t *)&error, cp->gss_svc_ctx_id, |
712 | nmc->nmc_mhead, start, arglen, &cksum, NULL); |
713 | kfree_data(cksum.value, cksum.length); |
714 | if (major != GSS_S_COMPLETE) { |
715 | printf("Server args: gss_krb5_verify_mic_mbuf failed %d\n" , error); |
716 | error = EBADRPC; |
717 | goto nfsmout; |
718 | } |
719 | |
720 | /* |
721 | * Get the sequence number prepended to the args |
722 | * and compare it against the one sent in the |
723 | * call credential. |
724 | */ |
725 | nfsm_chain_get_32(error, nmc, seqnum); |
726 | if (seqnum != nd->nd_gss_seqnum) { |
727 | error = EBADRPC; // returns as GARBAGEARGS |
728 | goto nfsmout; |
729 | } |
730 | break; |
731 | case RPCSEC_GSS_SVC_PRIVACY: |
732 | /* |
733 | * Here's what we expect in the privacy call args: |
734 | * |
735 | * - length of wrap token |
736 | * - wrap token (37-40 bytes) |
737 | */ |
738 | prev_mbuf = nmc->nmc_mcur; |
739 | nfsm_chain_get_32(error, nmc, arglen); // length of args |
740 | if (arglen > NFS_MAXPACKET) { |
741 | error = EBADRPC; |
742 | goto nfsmout; |
743 | } |
744 | |
745 | /* Get the wrap token (current mbuf in the chain starting at the current offset) */ |
746 | start = nmc->nmc_ptr - (caddr_t)mbuf_data(mbuf: nmc->nmc_mcur); |
747 | |
748 | /* split out the wrap token */ |
749 | argsize = arglen; |
750 | error = gss_normalize_mbuf(nmc->nmc_mcur, start, &argsize, &reply_mbuf, &pad_mbuf, 0); |
751 | if (error) { |
752 | goto nfsmout; |
753 | } |
754 | |
755 | assert(argsize == arglen); |
756 | if (pad_mbuf) { |
757 | assert(nfsm_pad(arglen) == mbuf_len(pad_mbuf)); |
758 | mbuf_free(mbuf: pad_mbuf); |
759 | } else { |
760 | assert(nfsm_pad(arglen) == 0); |
761 | } |
762 | |
763 | major = gss_krb5_unwrap_mbuf((uint32_t *)&error, cp->gss_svc_ctx_id, &reply_mbuf, 0, arglen, NULL, NULL); |
764 | if (major != GSS_S_COMPLETE) { |
765 | printf("%s: gss_krb5_unwrap_mbuf failes %d\n" , __func__, error); |
766 | goto nfsmout; |
767 | } |
768 | |
769 | /* Now replace the wrapped arguments with the unwrapped ones */ |
770 | mbuf_setnext(mbuf: prev_mbuf, next: reply_mbuf); |
771 | nmc->nmc_mcur = reply_mbuf; |
772 | nmc->nmc_ptr = mbuf_data(mbuf: reply_mbuf); |
773 | nmc->nmc_left = mbuf_len(mbuf: reply_mbuf); |
774 | |
775 | /* |
776 | * - sequence number (4 bytes) |
777 | * - call args |
778 | */ |
779 | |
780 | // nfsm_chain_reverse(nmc, nfsm_pad(toklen)); |
781 | |
782 | /* |
783 | * Get the sequence number prepended to the args |
784 | * and compare it against the one sent in the |
785 | * call credential. |
786 | */ |
787 | nfsm_chain_get_32(error, nmc, seqnum); |
788 | if (seqnum != nd->nd_gss_seqnum) { |
789 | printf("%s: Sequence number mismatch seqnum = %d nd->nd_gss_seqnum = %d\n" , |
790 | __func__, seqnum, nd->nd_gss_seqnum); |
791 | printmbuf("reply_mbuf" , nmc->nmc_mhead, 0, 0); |
792 | printf("reply_mbuf %p nmc_head %p\n" , reply_mbuf, nmc->nmc_mhead); |
793 | error = EBADRPC; // returns as GARBAGEARGS |
794 | goto nfsmout; |
795 | } |
796 | break; |
797 | } |
798 | } else { |
799 | uint32_t verflen; |
800 | /* |
801 | * If the proc is RPCSEC_GSS_INIT or RPCSEC_GSS_CONTINUE_INIT |
802 | * then we expect a null verifier. |
803 | */ |
804 | nfsm_chain_get_32(error, nmc, flavor); |
805 | nfsm_chain_get_32(error, nmc, verflen); |
806 | if (error || flavor != RPCAUTH_NULL || verflen > 0) { |
807 | error = NFSERR_AUTHERR | RPCSEC_GSS_CREDPROBLEM; |
808 | } |
809 | if (error) { |
810 | if (proc == RPCSEC_GSS_INIT) { |
811 | lck_mtx_destroy(lck: &cp->gss_svc_mtx, grp: &nfs_gss_svc_grp); |
812 | kfree_type(struct nfs_gss_svc_ctx, cp); |
813 | cp = NULL; |
814 | } |
815 | goto nfsmout; |
816 | } |
817 | } |
818 | |
819 | nd->nd_gss_context = cp; |
820 | return 0; |
821 | nfsmout: |
822 | if (cp) { |
823 | nfs_gss_svc_ctx_deref(cp); |
824 | } |
825 | return error; |
826 | } |
827 | |
828 | /* |
829 | * Insert the server's verifier into the RPC reply header. |
830 | * It contains a signed checksum of the sequence number that |
831 | * was received in the RPC call. |
832 | * Then go on to add integrity or privacy if necessary. |
833 | */ |
834 | int |
835 | nfs_gss_svc_verf_put(struct nfsrv_descript *nd, struct nfsm_chain *nmc) |
836 | { |
837 | struct nfs_gss_svc_ctx *cp; |
838 | int error = 0; |
839 | gss_buffer_desc cksum, seqbuf; |
840 | uint32_t network_seqnum; |
841 | cp = nd->nd_gss_context; |
842 | uint32_t major; |
843 | |
844 | if (cp->gss_svc_major != GSS_S_COMPLETE) { |
845 | /* |
846 | * If the context isn't yet complete |
847 | * then return a null verifier. |
848 | */ |
849 | nfsm_chain_add_32(error, nmc, RPCAUTH_NULL); |
850 | nfsm_chain_add_32(error, nmc, 0); |
851 | return error; |
852 | } |
853 | |
854 | /* |
855 | * Compute checksum of the request seq number |
856 | * If it's the final reply of context setup |
857 | * then return the checksum of the context |
858 | * window size. |
859 | */ |
860 | seqbuf.length = NFSX_UNSIGNED; |
861 | if (cp->gss_svc_proc == RPCSEC_GSS_INIT || |
862 | cp->gss_svc_proc == RPCSEC_GSS_CONTINUE_INIT) { |
863 | network_seqnum = htonl(cp->gss_svc_seqwin); |
864 | } else { |
865 | network_seqnum = htonl(nd->nd_gss_seqnum); |
866 | } |
867 | seqbuf.value = &network_seqnum; |
868 | |
869 | major = gss_krb5_get_mic((uint32_t *)&error, cp->gss_svc_ctx_id, 0, &seqbuf, &cksum); |
870 | if (major != GSS_S_COMPLETE) { |
871 | return error; |
872 | } |
873 | |
874 | /* |
875 | * Now wrap it in a token and add |
876 | * the verifier to the reply. |
877 | */ |
878 | nfsm_chain_add_32(error, nmc, RPCSEC_GSS); |
879 | nfsm_chain_add_32(error, nmc, cksum.length); |
880 | nfsm_chain_add_opaque(error, nmc, cksum.value, cksum.length); |
881 | gss_release_buffer(NULL, &cksum); |
882 | |
883 | return error; |
884 | } |
885 | |
886 | /* |
887 | * The results aren't available yet, but if they need to be |
888 | * checksummed for integrity protection or encrypted, then |
889 | * we can record the start offset here, insert a place-holder |
890 | * for the results length, as well as the sequence number. |
891 | * The rest of the work is done later by nfs_gss_svc_protect_reply() |
892 | * when the results are available. |
893 | */ |
894 | int |
895 | nfs_gss_svc_prepare_reply(struct nfsrv_descript *nd, struct nfsm_chain *nmc) |
896 | { |
897 | struct nfs_gss_svc_ctx *cp = nd->nd_gss_context; |
898 | int error = 0; |
899 | |
900 | if (cp->gss_svc_proc == RPCSEC_GSS_INIT || |
901 | cp->gss_svc_proc == RPCSEC_GSS_CONTINUE_INIT) { |
902 | return 0; |
903 | } |
904 | |
905 | switch (nd->nd_sec) { |
906 | case RPCAUTH_KRB5: |
907 | /* Nothing to do */ |
908 | break; |
909 | case RPCAUTH_KRB5I: |
910 | case RPCAUTH_KRB5P: |
911 | nd->nd_gss_mb = nmc->nmc_mcur; // record current mbuf |
912 | nfsm_chain_finish_mbuf(error, nmc); // split the chain here |
913 | break; |
914 | } |
915 | |
916 | return error; |
917 | } |
918 | |
919 | /* |
920 | * The results are checksummed or encrypted for return to the client |
921 | */ |
922 | int |
923 | nfs_gss_svc_protect_reply(struct nfsrv_descript *nd, mbuf_t mrep __unused) |
924 | { |
925 | struct nfs_gss_svc_ctx *cp = nd->nd_gss_context; |
926 | struct nfsm_chain nmrep_res, *nmc_res = &nmrep_res; |
927 | mbuf_t mb, results; |
928 | uint32_t reslen; |
929 | int error = 0; |
930 | |
931 | /* XXX |
932 | * Using a reference to the mbuf where we previously split the reply |
933 | * mbuf chain, we split the mbuf chain argument into two mbuf chains, |
934 | * one that allows us to prepend a length field or token, (nmc_pre) |
935 | * and the second which holds just the results that we're going to |
936 | * checksum and/or encrypt. When we're done, we join the chains back |
937 | * together. |
938 | */ |
939 | |
940 | mb = nd->nd_gss_mb; // the mbuf where we split |
941 | results = mbuf_next(mbuf: mb); // first mbuf in the results |
942 | error = mbuf_setnext(mbuf: mb, NULL); // disconnect the chains |
943 | if (error) { |
944 | return error; |
945 | } |
946 | nfs_gss_nfsm_chain(nmc_res, mb); // set up the prepend chain |
947 | nfsm_chain_build_done(error, nmc_res); |
948 | if (error) { |
949 | return error; |
950 | } |
951 | |
952 | if (nd->nd_sec == RPCAUTH_KRB5I) { |
953 | error = rpc_gss_integ_data_create(ctx: cp->gss_svc_ctx_id, mb_head: &results, seqnum: nd->nd_gss_seqnum, len: &reslen); |
954 | } else { |
955 | /* RPCAUTH_KRB5P */ |
956 | error = rpc_gss_priv_data_create(ctx: cp->gss_svc_ctx_id, mb_head: &results, seqnum: nd->nd_gss_seqnum, len: &reslen); |
957 | } |
958 | nfs_gss_append_chain(nmc_res, results); // Append the results mbufs |
959 | nfsm_chain_build_done(error, nmc_res); |
960 | |
961 | return error; |
962 | } |
963 | |
964 | /* |
965 | * This function handles the context setup calls from the client. |
966 | * Essentially, it implements the NFS null procedure calls when |
967 | * an RPCSEC_GSS credential is used. |
968 | * This is the context maintenance function. It creates and |
969 | * destroys server contexts at the whim of the client. |
970 | * During context creation, it receives GSS-API tokens from the |
971 | * client, passes them up to gssd, and returns a received token |
972 | * back to the client in the null procedure reply. |
973 | */ |
974 | int |
975 | nfs_gss_svc_ctx_init(struct nfsrv_descript *nd, struct nfsrv_sock *slp, mbuf_t *mrepp) |
976 | { |
977 | struct nfs_gss_svc_ctx *cp = NULL; |
978 | int error = 0; |
979 | int autherr = 0; |
980 | struct nfsm_chain *nmreq, nmrep; |
981 | int sz; |
982 | |
983 | nmreq = &nd->nd_nmreq; |
984 | nfsm_chain_null(&nmrep); |
985 | *mrepp = NULL; |
986 | cp = nd->nd_gss_context; |
987 | nd->nd_repstat = 0; |
988 | |
989 | switch (cp->gss_svc_proc) { |
990 | case RPCSEC_GSS_INIT: |
991 | nfs_gss_svc_ctx_insert(cp); |
992 | OS_FALLTHROUGH; |
993 | |
994 | case RPCSEC_GSS_CONTINUE_INIT: |
995 | /* Get the token from the request */ |
996 | nfsm_chain_get_32(error, nmreq, cp->gss_svc_tokenlen); |
997 | cp->gss_svc_token = NULL; |
998 | if (cp->gss_svc_tokenlen > 0 && cp->gss_svc_tokenlen < GSS_MAX_TOKEN_LEN) { |
999 | cp->gss_svc_token = kalloc_data(cp->gss_svc_tokenlen, Z_WAITOK); |
1000 | } |
1001 | if (cp->gss_svc_token == NULL) { |
1002 | autherr = RPCSEC_GSS_CREDPROBLEM; |
1003 | break; |
1004 | } |
1005 | nfsm_chain_get_opaque(error, nmreq, cp->gss_svc_tokenlen, cp->gss_svc_token); |
1006 | |
1007 | /* Use the token in a gss_accept_sec_context upcall */ |
1008 | error = nfs_gss_svc_gssd_upcall(cp); |
1009 | if (error) { |
1010 | autherr = RPCSEC_GSS_CREDPROBLEM; |
1011 | if (error == NFSERR_EAUTH) { |
1012 | error = 0; |
1013 | } |
1014 | break; |
1015 | } |
1016 | |
1017 | /* |
1018 | * If the context isn't complete, pass the new token |
1019 | * back to the client for another round. |
1020 | */ |
1021 | if (cp->gss_svc_major != GSS_S_COMPLETE) { |
1022 | break; |
1023 | } |
1024 | |
1025 | /* |
1026 | * Now the server context is complete. |
1027 | * Finish setup. |
1028 | */ |
1029 | clock_get_uptime(result: &cp->gss_svc_incarnation); |
1030 | |
1031 | cp->gss_svc_seqwin = GSS_SVC_SEQWINDOW; |
1032 | cp->gss_svc_seqbits = kalloc_data(nfs_gss_seqbits_size(cp->gss_svc_seqwin), Z_WAITOK | Z_ZERO); |
1033 | if (cp->gss_svc_seqbits == NULL) { |
1034 | autherr = RPCSEC_GSS_CREDPROBLEM; |
1035 | break; |
1036 | } |
1037 | break; |
1038 | |
1039 | case RPCSEC_GSS_DATA: |
1040 | /* Just a nullproc ping - do nothing */ |
1041 | break; |
1042 | |
1043 | case RPCSEC_GSS_DESTROY: |
1044 | /* |
1045 | * Don't destroy the context immediately because |
1046 | * other active requests might still be using it. |
1047 | * Instead, schedule it for destruction after |
1048 | * GSS_CTX_PEND time has elapsed. |
1049 | */ |
1050 | cp = nfs_gss_svc_ctx_find(handle: cp->gss_svc_handle); |
1051 | if (cp != NULL) { |
1052 | cp->gss_svc_handle = 0; // so it can't be found |
1053 | lck_mtx_lock(lck: &cp->gss_svc_mtx); |
1054 | clock_interval_to_deadline(GSS_CTX_PEND, NSEC_PER_SEC, |
1055 | result: &cp->gss_svc_incarnation); |
1056 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
1057 | } |
1058 | break; |
1059 | default: |
1060 | autherr = RPCSEC_GSS_CREDPROBLEM; |
1061 | break; |
1062 | } |
1063 | |
1064 | /* Now build the reply */ |
1065 | |
1066 | if (nd->nd_repstat == 0) { |
1067 | nd->nd_repstat = autherr ? (NFSERR_AUTHERR | autherr) : NFSERR_RETVOID; |
1068 | } |
1069 | sz = 7 * NFSX_UNSIGNED + nfsm_rndup(cp->gss_svc_tokenlen); // size of results |
1070 | error = nfsrv_rephead(nd, slp, &nmrep, sz); |
1071 | *mrepp = nmrep.nmc_mhead; |
1072 | if (error || autherr) { |
1073 | goto nfsmout; |
1074 | } |
1075 | |
1076 | if (cp->gss_svc_proc == RPCSEC_GSS_INIT || |
1077 | cp->gss_svc_proc == RPCSEC_GSS_CONTINUE_INIT) { |
1078 | nfsm_chain_add_32(error, &nmrep, sizeof(cp->gss_svc_handle)); |
1079 | nfsm_chain_add_32(error, &nmrep, cp->gss_svc_handle); |
1080 | |
1081 | nfsm_chain_add_32(error, &nmrep, cp->gss_svc_major); |
1082 | nfsm_chain_add_32(error, &nmrep, cp->gss_svc_minor); |
1083 | nfsm_chain_add_32(error, &nmrep, cp->gss_svc_seqwin); |
1084 | |
1085 | nfsm_chain_add_32(error, &nmrep, cp->gss_svc_tokenlen); |
1086 | if (cp->gss_svc_token != NULL) { |
1087 | nfsm_chain_add_opaque(error, &nmrep, cp->gss_svc_token, cp->gss_svc_tokenlen); |
1088 | kfree_data_addr(cp->gss_svc_token); |
1089 | } |
1090 | } |
1091 | |
1092 | nfsmout: |
1093 | if (autherr != 0) { |
1094 | nd->nd_gss_context = NULL; |
1095 | LIST_REMOVE(cp, gss_svc_entries); |
1096 | if (cp->gss_svc_seqbits != NULL) { |
1097 | kfree_data(cp->gss_svc_seqbits, nfs_gss_seqbits_size(cp->gss_svc_seqwin)); |
1098 | } |
1099 | if (cp->gss_svc_token != NULL) { |
1100 | kfree_data_addr(cp->gss_svc_token); |
1101 | } |
1102 | lck_mtx_destroy(lck: &cp->gss_svc_mtx, grp: &nfs_gss_svc_grp); |
1103 | kfree_type(struct nfs_gss_svc_ctx, cp); |
1104 | } |
1105 | |
1106 | nfsm_chain_build_done(error, &nmrep); |
1107 | if (error) { |
1108 | nfsm_chain_cleanup(&nmrep); |
1109 | *mrepp = NULL; |
1110 | } |
1111 | return error; |
1112 | } |
1113 | |
1114 | /* |
1115 | * This is almost a mirror-image of the client side upcall. |
1116 | * It passes and receives a token, but invokes gss_accept_sec_context. |
1117 | * If it's the final call of the context setup, then gssd also returns |
1118 | * the session key and the user's UID. |
1119 | */ |
1120 | static int |
1121 | nfs_gss_svc_gssd_upcall(struct nfs_gss_svc_ctx *cp) |
1122 | { |
1123 | kern_return_t kr; |
1124 | mach_port_t mp; |
1125 | int retry_cnt = 0; |
1126 | gssd_byte_buffer octx = NULL; |
1127 | uint32_t lucidlen = 0; |
1128 | void *lucid_ctx_buffer; |
1129 | uint32_t ret_flags; |
1130 | vm_map_copy_t itoken = NULL; |
1131 | gssd_byte_buffer otoken = NULL; |
1132 | mach_msg_type_number_t otokenlen; |
1133 | int error = 0; |
1134 | char svcname[] = "nfs" ; |
1135 | |
1136 | kr = host_get_gssd_port(host_priv_self(), &mp); |
1137 | if (kr != KERN_SUCCESS) { |
1138 | printf("nfs_gss_svc_gssd_upcall: can't get gssd port, status %x (%d)\n" , kr, kr); |
1139 | goto out; |
1140 | } |
1141 | if (!IPC_PORT_VALID(mp)) { |
1142 | printf("nfs_gss_svc_gssd_upcall: gssd port not valid\n" ); |
1143 | goto out; |
1144 | } |
1145 | |
1146 | if (cp->gss_svc_tokenlen > 0) { |
1147 | nfs_gss_mach_alloc_buffer(cp->gss_svc_token, cp->gss_svc_tokenlen, &itoken); |
1148 | } |
1149 | |
1150 | retry: |
1151 | printf("Calling mach_gss_accept_sec_context\n" ); |
1152 | kr = mach_gss_accept_sec_context( |
1153 | server: mp, |
1154 | intoken: (gssd_byte_buffer) itoken, intokenCnt: (mach_msg_type_number_t) cp->gss_svc_tokenlen, |
1155 | svc_namestr: svcname, |
1156 | gssd_flags: 0, |
1157 | context: &cp->gss_svc_context, |
1158 | cred_handle: &cp->gss_svc_cred_handle, |
1159 | flags: &ret_flags, |
1160 | uid: &cp->gss_svc_uid, |
1161 | gids: cp->gss_svc_gids, |
1162 | gidsCnt: &cp->gss_svc_ngroups, |
1163 | key: &octx, keyCnt: (mach_msg_type_number_t *) &lucidlen, |
1164 | outtoken: &otoken, outtokenCnt: &otokenlen, |
1165 | major_stat: &cp->gss_svc_major, |
1166 | minor_stat: &cp->gss_svc_minor); |
1167 | |
1168 | printf("mach_gss_accept_sec_context returned %d\n" , kr); |
1169 | if (kr != KERN_SUCCESS) { |
1170 | printf("nfs_gss_svc_gssd_upcall failed: %x (%d)\n" , kr, kr); |
1171 | if (kr == MIG_SERVER_DIED && cp->gss_svc_context == 0 && |
1172 | retry_cnt++ < NFS_GSS_MACH_MAX_RETRIES) { |
1173 | if (cp->gss_svc_tokenlen > 0) { |
1174 | nfs_gss_mach_alloc_buffer(cp->gss_svc_token, cp->gss_svc_tokenlen, &itoken); |
1175 | } |
1176 | goto retry; |
1177 | } |
1178 | host_release_special_port(mp); |
1179 | goto out; |
1180 | } |
1181 | |
1182 | host_release_special_port(mp); |
1183 | |
1184 | if (lucidlen > 0) { |
1185 | if (lucidlen > MAX_LUCIDLEN) { |
1186 | printf("nfs_gss_svc_gssd_upcall: bad context length (%d)\n" , lucidlen); |
1187 | vm_map_copy_discard(copy: (vm_map_copy_t) octx); |
1188 | vm_map_copy_discard(copy: (vm_map_copy_t) otoken); |
1189 | goto out; |
1190 | } |
1191 | lucid_ctx_buffer = kalloc_data(lucidlen, Z_WAITOK | Z_ZERO); |
1192 | error = nfs_gss_mach_vmcopyout((vm_map_copy_t) octx, lucidlen, lucid_ctx_buffer); |
1193 | if (error) { |
1194 | vm_map_copy_discard(copy: (vm_map_copy_t) octx); |
1195 | vm_map_copy_discard(copy: (vm_map_copy_t) otoken); |
1196 | kfree_data(lucid_ctx_buffer, lucidlen); |
1197 | goto out; |
1198 | } |
1199 | if (cp->gss_svc_ctx_id) { |
1200 | gss_krb5_destroy_context(cp->gss_svc_ctx_id); |
1201 | } |
1202 | cp->gss_svc_ctx_id = gss_krb5_make_context(lucid_ctx_buffer, lucidlen); |
1203 | kfree_data(lucid_ctx_buffer, lucidlen); |
1204 | if (cp->gss_svc_ctx_id == NULL) { |
1205 | printf("Failed to make context from lucid_ctx_buffer\n" ); |
1206 | goto out; |
1207 | } |
1208 | } |
1209 | |
1210 | /* Free context token used as input */ |
1211 | if (cp->gss_svc_token) { |
1212 | kfree_data(cp->gss_svc_token, cp->gss_svc_tokenlen); |
1213 | } |
1214 | cp->gss_svc_token = NULL; |
1215 | cp->gss_svc_tokenlen = 0; |
1216 | |
1217 | if (otokenlen > 0) { |
1218 | /* Set context token to gss output token */ |
1219 | cp->gss_svc_token = kalloc_data(otokenlen, Z_WAITOK); |
1220 | if (cp->gss_svc_token == NULL) { |
1221 | printf("nfs_gss_svc_gssd_upcall: could not allocate %d bytes\n" , otokenlen); |
1222 | vm_map_copy_discard(copy: (vm_map_copy_t) otoken); |
1223 | return ENOMEM; |
1224 | } |
1225 | error = nfs_gss_mach_vmcopyout((vm_map_copy_t) otoken, otokenlen, cp->gss_svc_token); |
1226 | if (error) { |
1227 | vm_map_copy_discard(copy: (vm_map_copy_t) otoken); |
1228 | kfree_data(cp->gss_svc_token, otokenlen); |
1229 | return NFSERR_EAUTH; |
1230 | } |
1231 | cp->gss_svc_tokenlen = otokenlen; |
1232 | } |
1233 | |
1234 | return 0; |
1235 | |
1236 | out: |
1237 | kfree_data(cp->gss_svc_token, cp->gss_svc_tokenlen); |
1238 | cp->gss_svc_tokenlen = 0; |
1239 | |
1240 | return NFSERR_EAUTH; |
1241 | } |
1242 | |
1243 | /* |
1244 | * Validate the sequence number in the credential as described |
1245 | * in RFC 2203 Section 5.3.3.1 |
1246 | * |
1247 | * Here the window of valid sequence numbers is represented by |
1248 | * a bitmap. As each sequence number is received, its bit is |
1249 | * set in the bitmap. An invalid sequence number lies below |
1250 | * the lower bound of the window, or is within the window but |
1251 | * has its bit already set. |
1252 | */ |
1253 | static int |
1254 | nfs_gss_svc_seqnum_valid(struct nfs_gss_svc_ctx *cp, uint32_t seq) |
1255 | { |
1256 | uint32_t *bits = cp->gss_svc_seqbits; |
1257 | uint32_t win = cp->gss_svc_seqwin; |
1258 | uint32_t i; |
1259 | |
1260 | lck_mtx_lock(lck: &cp->gss_svc_mtx); |
1261 | |
1262 | /* |
1263 | * If greater than the window upper bound, |
1264 | * move the window up, and set the bit. |
1265 | */ |
1266 | if (seq > cp->gss_svc_seqmax) { |
1267 | if (seq - cp->gss_svc_seqmax > win) { |
1268 | bzero(s: bits, n: nfs_gss_seqbits_size(win)); |
1269 | } else { |
1270 | for (i = cp->gss_svc_seqmax + 1; i < seq; i++) { |
1271 | win_resetbit(bits, i % win); |
1272 | } |
1273 | } |
1274 | win_setbit(bits, seq % win); |
1275 | cp->gss_svc_seqmax = seq; |
1276 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
1277 | return 1; |
1278 | } |
1279 | |
1280 | /* |
1281 | * Invalid if below the lower bound of the window |
1282 | */ |
1283 | if (seq <= cp->gss_svc_seqmax - win) { |
1284 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
1285 | return 0; |
1286 | } |
1287 | |
1288 | /* |
1289 | * In the window, invalid if the bit is already set |
1290 | */ |
1291 | if (win_getbit(bits, seq % win)) { |
1292 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
1293 | return 0; |
1294 | } |
1295 | win_setbit(bits, seq % win); |
1296 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
1297 | return 1; |
1298 | } |
1299 | |
1300 | /* |
1301 | * Drop a reference to a context |
1302 | * |
1303 | * Note that it's OK for the context to exist |
1304 | * with a refcount of zero. The refcount isn't |
1305 | * checked until we're about to reap an expired one. |
1306 | */ |
1307 | void |
1308 | nfs_gss_svc_ctx_deref(struct nfs_gss_svc_ctx *cp) |
1309 | { |
1310 | lck_mtx_lock(lck: &cp->gss_svc_mtx); |
1311 | if (cp->gss_svc_refcnt > 0) { |
1312 | cp->gss_svc_refcnt--; |
1313 | } else { |
1314 | printf("nfs_gss_ctx_deref: zero refcount\n" ); |
1315 | } |
1316 | lck_mtx_unlock(lck: &cp->gss_svc_mtx); |
1317 | } |
1318 | |
1319 | /* |
1320 | * Called at NFS server shutdown - destroy all contexts |
1321 | */ |
1322 | void |
1323 | nfs_gss_svc_cleanup(void) |
1324 | { |
1325 | struct nfs_gss_svc_ctx_hashhead *head; |
1326 | struct nfs_gss_svc_ctx *cp, *ncp; |
1327 | int i; |
1328 | |
1329 | lck_mtx_lock(lck: &nfs_gss_svc_ctx_mutex); |
1330 | |
1331 | /* |
1332 | * Run through all the buckets |
1333 | */ |
1334 | for (i = 0; i < SVC_CTX_HASHSZ; i++) { |
1335 | /* |
1336 | * Remove and free all entries in the bucket |
1337 | */ |
1338 | head = &nfs_gss_svc_ctx_hashtbl[i]; |
1339 | LIST_FOREACH_SAFE(cp, head, gss_svc_entries, ncp) { |
1340 | LIST_REMOVE(cp, gss_svc_entries); |
1341 | if (cp->gss_svc_seqbits) { |
1342 | kfree_data(cp->gss_svc_seqbits, nfs_gss_seqbits_size(cp->gss_svc_seqwin)); |
1343 | } |
1344 | lck_mtx_destroy(lck: &cp->gss_svc_mtx, grp: &nfs_gss_svc_grp); |
1345 | kfree_type(struct nfs_gss_svc_ctx, cp); |
1346 | } |
1347 | } |
1348 | |
1349 | lck_mtx_unlock(lck: &nfs_gss_svc_ctx_mutex); |
1350 | } |
1351 | |
1352 | /************* |
1353 | * The following functions are used by both client and server. |
1354 | */ |
1355 | |
1356 | /* |
1357 | * Release a host special port that was obtained by host_get_special_port |
1358 | * or one of its macros (host_get_gssd_port in this case). |
1359 | * This really should be in a public kpi. |
1360 | */ |
1361 | |
1362 | /* This should be in a public header if this routine is not */ |
1363 | static void |
1364 | host_release_special_port(mach_port_t mp) |
1365 | { |
1366 | if (IPC_PORT_VALID(mp)) { |
1367 | ipc_port_release_send(port: mp); |
1368 | } |
1369 | } |
1370 | |
1371 | /* |
1372 | * The token that is sent and received in the gssd upcall |
1373 | * has unbounded variable length. Mach RPC does not pass |
1374 | * the token in-line. Instead it uses page mapping to handle |
1375 | * these parameters. This function allocates a VM buffer |
1376 | * to hold the token for an upcall and copies the token |
1377 | * (received from the client) into it. The VM buffer is |
1378 | * marked with a src_destroy flag so that the upcall will |
1379 | * automatically de-allocate the buffer when the upcall is |
1380 | * complete. |
1381 | */ |
1382 | static void |
1383 | nfs_gss_mach_alloc_buffer(u_char *buf, size_t buflen, vm_map_copy_t *addr) |
1384 | { |
1385 | kern_return_t kr; |
1386 | vm_offset_t kmem_buf; |
1387 | vm_size_t tbuflen; |
1388 | |
1389 | *addr = NULL; |
1390 | if (buf == NULL || buflen == 0) { |
1391 | return; |
1392 | } |
1393 | |
1394 | tbuflen = vm_map_round_page(buflen, vm_map_page_mask(ipc_kernel_map)); |
1395 | |
1396 | if (tbuflen < buflen) { |
1397 | printf("nfs_gss_mach_alloc_buffer: vm_map_round_page failed\n" ); |
1398 | return; |
1399 | } |
1400 | |
1401 | kr = kmem_alloc(map: ipc_kernel_map, addrp: &kmem_buf, size: tbuflen, |
1402 | flags: KMA_DATA, VM_KERN_MEMORY_FILE); |
1403 | if (kr != 0) { |
1404 | printf("nfs_gss_mach_alloc_buffer: vm_allocate failed\n" ); |
1405 | return; |
1406 | } |
1407 | |
1408 | bcopy(src: buf, dst: (char *)kmem_buf, n: buflen); |
1409 | bzero(s: (char *)kmem_buf + buflen, n: tbuflen - buflen); |
1410 | |
1411 | kr = vm_map_unwire(map: ipc_kernel_map, start: kmem_buf, end: kmem_buf + tbuflen, FALSE); |
1412 | if (kr != 0) { |
1413 | printf("nfs_gss_mach_alloc_buffer: vm_map_unwire failed\n" ); |
1414 | return; |
1415 | } |
1416 | |
1417 | kr = vm_map_copyin(src_map: ipc_kernel_map, src_addr: (vm_map_address_t) kmem_buf, |
1418 | len: (vm_map_size_t) buflen, TRUE, copy_result: addr); |
1419 | if (kr != 0) { |
1420 | printf("nfs_gss_mach_alloc_buffer: vm_map_copyin failed\n" ); |
1421 | return; |
1422 | } |
1423 | } |
1424 | |
1425 | /* |
1426 | * Here we handle a token received from the gssd via an upcall. |
1427 | * The received token resides in an allocate VM buffer. |
1428 | * We copy the token out of this buffer to a chunk of malloc'ed |
1429 | * memory of the right size, then de-allocate the VM buffer. |
1430 | */ |
1431 | static int |
1432 | nfs_gss_mach_vmcopyout(vm_map_copy_t in, uint32_t len, u_char *out) |
1433 | { |
1434 | vm_map_offset_t map_data; |
1435 | vm_offset_t data; |
1436 | int error; |
1437 | |
1438 | error = vm_map_copyout(dst_map: ipc_kernel_map, dst_addr: &map_data, copy: in); |
1439 | if (error) { |
1440 | return error; |
1441 | } |
1442 | |
1443 | data = CAST_DOWN(vm_offset_t, map_data); |
1444 | bcopy(src: (void *) data, dst: out, n: len); |
1445 | vm_deallocate(target_task: ipc_kernel_map, address: data, size: len); |
1446 | |
1447 | return 0; |
1448 | } |
1449 | |
1450 | /* |
1451 | * Return the number of bytes in an mbuf chain. |
1452 | */ |
1453 | static int |
1454 | nfs_gss_mchain_length(mbuf_t mhead) |
1455 | { |
1456 | mbuf_t mb; |
1457 | int len = 0; |
1458 | |
1459 | for (mb = mhead; mb; mb = mbuf_next(mbuf: mb)) { |
1460 | len += mbuf_len(mbuf: mb); |
1461 | } |
1462 | |
1463 | return len; |
1464 | } |
1465 | |
1466 | /* |
1467 | * Return the size for the sequence numbers bitmap. |
1468 | */ |
1469 | static int |
1470 | nfs_gss_seqbits_size(uint32_t win) |
1471 | { |
1472 | return nfsm_rndup((win + 7) / 8); |
1473 | } |
1474 | |
1475 | /* |
1476 | * Append an args or results mbuf chain to the header chain |
1477 | */ |
1478 | static int |
1479 | nfs_gss_append_chain(struct nfsm_chain *nmc, mbuf_t mc) |
1480 | { |
1481 | int error = 0; |
1482 | mbuf_t mb, tail; |
1483 | |
1484 | /* Connect the mbuf chains */ |
1485 | error = mbuf_setnext(mbuf: nmc->nmc_mcur, next: mc); |
1486 | if (error) { |
1487 | return error; |
1488 | } |
1489 | |
1490 | /* Find the last mbuf in the chain */ |
1491 | tail = NULL; |
1492 | for (mb = mc; mb; mb = mbuf_next(mbuf: mb)) { |
1493 | tail = mb; |
1494 | } |
1495 | |
1496 | nmc->nmc_mcur = tail; |
1497 | nmc->nmc_ptr = (caddr_t) mbuf_data(mbuf: tail) + mbuf_len(mbuf: tail); |
1498 | nmc->nmc_left = mbuf_trailingspace(mbuf: tail); |
1499 | |
1500 | return 0; |
1501 | } |
1502 | |
1503 | /* |
1504 | * Convert an mbuf chain to an NFS mbuf chain |
1505 | */ |
1506 | static void |
1507 | nfs_gss_nfsm_chain(struct nfsm_chain *nmc, mbuf_t mc) |
1508 | { |
1509 | mbuf_t mb, tail; |
1510 | |
1511 | /* Find the last mbuf in the chain */ |
1512 | tail = NULL; |
1513 | for (mb = mc; mb; mb = mbuf_next(mbuf: mb)) { |
1514 | tail = mb; |
1515 | } |
1516 | |
1517 | nmc->nmc_mhead = mc; |
1518 | nmc->nmc_mcur = tail; |
1519 | nmc->nmc_ptr = (caddr_t) mbuf_data(mbuf: tail) + mbuf_len(mbuf: tail); |
1520 | nmc->nmc_left = mbuf_trailingspace(mbuf: tail); |
1521 | nmc->nmc_flags = 0; |
1522 | } |
1523 | |
1524 | #if 0 |
1525 | #define DISPLAYLEN 16 |
1526 | #define MAXDISPLAYLEN 256 |
1527 | |
1528 | static void |
1529 | hexdump(const char *msg, void *data, size_t len) |
1530 | { |
1531 | size_t i, j; |
1532 | u_char *d = data; |
1533 | char *p, disbuf[3 * DISPLAYLEN + 1]; |
1534 | |
1535 | printf("NFS DEBUG %s len=%d:\n" , msg, (uint32_t)len); |
1536 | if (len > MAXDISPLAYLEN) { |
1537 | len = MAXDISPLAYLEN; |
1538 | } |
1539 | |
1540 | for (i = 0; i < len; i += DISPLAYLEN) { |
1541 | for (p = disbuf, j = 0; (j + i) < len && j < DISPLAYLEN; j++, p += 3) { |
1542 | snprintf(p, 4, "%02x " , d[i + j]); |
1543 | } |
1544 | printf("\t%s\n" , disbuf); |
1545 | } |
1546 | } |
1547 | #endif |
1548 | |
1549 | #endif /* CONFIG_NFS_SERVER */ |
1550 | |