1/*
2 * Copyright (c) 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 <os/refcnt.h>
30
31#include <kern/ipc_kobject.h>
32#include <kern/ipc_tt.h>
33#include <kern/task_ident.h>
34
35#include <mach/mach_types.h>
36#include <mach/task.h>
37#include <mach/notify.h>
38#include <mach/kern_return.h>
39
40#include <security/mac_mach_internal.h>
41#include <kern/task_ident.h>
42#include <corpses/task_corpse.h>
43
44struct proc_ident {
45 uint64_t p_uniqueid;
46 pid_t p_pid;
47 int p_idversion;
48};
49
50extern void* proc_find_ident(struct proc_ident const *i);
51extern int proc_rele(void* p);
52extern task_t proc_task(void* p);
53extern struct proc_ident proc_ident(void* p);
54extern kern_return_t task_conversion_eval(task_t caller, task_t victim, int flavor);
55
56/* Exported to kexts */
57extern typeof(task_id_token_port_name_to_task) task_id_token_port_name_to_task_external;
58
59struct task_id_token {
60 struct proc_ident ident;
61 ipc_port_t port;
62 uint64_t task_uniqueid; /* for corpse task */
63 os_refcnt_t tidt_refs;
64};
65
66static ZONE_DEFINE_TYPE(task_id_token_zone, "task_id_token",
67 struct task_id_token, ZC_ZFREE_CLEARMEM);
68
69void task_id_token_set_port(task_id_token_t token, ipc_port_t port);
70
71static void
72tidt_reference(task_id_token_t token)
73{
74 if (token == TASK_ID_TOKEN_NULL) {
75 return;
76 }
77 os_ref_retain(rc: &token->tidt_refs);
78}
79
80static void
81tidt_release(task_id_token_t token)
82{
83 ipc_port_t port;
84
85 if (token == TASK_ID_TOKEN_NULL) {
86 return;
87 }
88
89 if (os_ref_release(rc: &token->tidt_refs) > 0) {
90 return;
91 }
92
93 /* last ref */
94 port = token->port;
95
96 if (IP_VALID(port)) {
97#if CONFIG_PROC_RESOURCE_LIMITS
98 /*
99 * Ports of type IKOT_TASK_FATAL use task_ident objects to avoid holding a task reference
100 * and are created to send resource limit notifications
101 */
102 int kotype = ip_kotype(port);
103 if (kotype == IKOT_TASK_ID_TOKEN || kotype == IKOT_TASK_FATAL) {
104 ipc_kobject_dealloc_port(port, 0, kotype);
105 } else {
106 panic("%s: unexpected kotype of port %p: got %d",
107 __func__, port, kotype);
108 }
109#else /* CONFIG_PROC_RESOURCE_LIMITS */
110 ipc_kobject_dealloc_port(port, mscount: 0, type: IKOT_TASK_ID_TOKEN);
111#endif /* CONFIG_PROC_RESOURCE_LIMITS */
112 }
113
114 zfree(task_id_token_zone, token);
115}
116
117void
118task_id_token_release(task_id_token_t token)
119{
120 tidt_release(token);
121}
122
123static void
124task_id_token_no_senders(ipc_port_t port, __unused mach_port_mscount_t mscount)
125{
126 task_id_token_t token;
127
128 token = ipc_kobject_get_stable(port, type: IKOT_TASK_ID_TOKEN);
129 assert(token != NULL);
130 assert(port->ip_srights == 0);
131
132 tidt_release(token); /* consumes ref given by notification */
133}
134
135IPC_KOBJECT_DEFINE(IKOT_TASK_ID_TOKEN,
136 .iko_op_stable = true,
137 .iko_op_no_senders = task_id_token_no_senders);
138
139kern_return_t
140task_create_identity_token(
141 task_t task,
142 task_id_token_t *tokenp)
143{
144 task_id_token_t token;
145 void *bsd_info = NULL;
146
147 if (task == TASK_NULL || task == kernel_task) {
148 return KERN_INVALID_ARGUMENT;
149 }
150
151 token = zalloc_flags(task_id_token_zone, Z_ZERO | Z_WAITOK | Z_NOFAIL);
152
153 task_lock(task);
154
155 bsd_info = get_bsdtask_info(task);
156 if (task_is_a_corpse(task)) {
157 token->task_uniqueid = task->task_uniqueid;
158 } else if (task->active && bsd_info != NULL) {
159 /* must check if the task is active to avoid a UAF - rdar://91431693 */
160 token->ident = proc_ident(p: bsd_info);
161 } else {
162 task_unlock(task);
163 zfree(task_id_token_zone, token);
164 return KERN_INVALID_ARGUMENT;
165 }
166
167 task_unlock(task);
168
169 token->port = IP_NULL;
170 /* this reference will be donated to no-senders notification */
171 os_ref_init_count(&token->tidt_refs, NULL, 1);
172
173 *tokenp = token;
174
175 return KERN_SUCCESS;
176}
177
178/* Produces (corpse) task reference, does not consume token reference */
179kern_return_t
180task_identity_token_get_task_grp(
181 task_id_token_t token,
182 task_t *taskp,
183 task_grp_t grp)
184{
185 kern_return_t kr;
186 task_t task;
187
188 if (token == TASK_ID_TOKEN_NULL) {
189 return KERN_INVALID_ARGUMENT;
190 }
191
192 if (token->task_uniqueid) {
193 kr = find_corpse_task_by_uniqueid_grp(uid: token->task_uniqueid, target: &task, grp); /* produces ref */
194 if (kr) {
195 return KERN_NOT_FOUND;
196 }
197 assert(task_is_a_corpse(task));
198 } else {
199 void* p = proc_find_ident(i: &token->ident);
200 if (p == NULL) {
201 return KERN_NOT_FOUND;
202 }
203 task = proc_task(p);
204 task_reference_grp(task, grp); /* produces ref */
205 proc_rele(p);
206 }
207
208 *taskp = task;
209
210 return KERN_SUCCESS;
211}
212
213/* Produces task port send right, does not consume token reference */
214kern_return_t
215task_identity_token_get_task_port(
216 task_id_token_t token,
217 task_flavor_t flavor,
218 mach_port_t *portp)
219{
220 task_t task;
221 kern_return_t kr;
222
223 if (token == TASK_ID_TOKEN_NULL) {
224 return KERN_INVALID_ARGUMENT;
225 }
226
227 if (flavor > TASK_FLAVOR_MAX) {
228 return KERN_INVALID_ARGUMENT;
229 }
230
231 if (token->task_uniqueid) {
232 /*
233 * For corpses, the control port reference would hold the corpse,
234 * only allow conversion to control port for now.
235 */
236 if (flavor != TASK_FLAVOR_CONTROL) {
237 return KERN_INVALID_ARGUMENT;
238 }
239 }
240
241 if ((kr = task_identity_token_get_task_grp(token, taskp: &task, grp: TASK_GRP_KERNEL)) != KERN_SUCCESS) {
242 return kr;
243 }
244
245 assert(task != TASK_NULL);
246 assert(token != TASK_ID_TOKEN_NULL);
247
248 /* holding a ref on (corpse) task */
249
250 if (flavor == TASK_FLAVOR_CONTROL && task == current_task()) {
251 *portp = convert_task_to_port_pinned(task); /* consumes task ref */
252 return KERN_SUCCESS;
253 }
254
255 if (flavor <= TASK_FLAVOR_READ &&
256 task_conversion_eval(caller: current_task(), victim: task, flavor)) {
257 task_deallocate(task);
258 return KERN_INVALID_ARGUMENT;
259 }
260
261#if CONFIG_MACF
262
263 if (task != current_task()) {
264 if (mac_task_check_task_id_token_get_task(t: task, flavor)) {
265 task_deallocate(task);
266 return KERN_DENIED;
267 }
268 }
269#endif
270
271 *portp = convert_task_to_port_with_flavor(task, flavor, grp: TASK_GRP_KERNEL);
272 /* task ref consumed */
273
274 return KERN_SUCCESS;
275}
276
277/* Produces task reference */
278static kern_return_t
279task_id_token_port_name_to_task_grp(
280 mach_port_name_t name,
281 task_t *task,
282 task_grp_t grp)
283{
284 kern_return_t kr;
285 task_id_token_t token;
286
287 token = port_name_to_task_id_token(name); /* produces ref */
288 kr = task_identity_token_get_task_grp(token, taskp: task, grp);
289
290 tidt_release(token); /* consumes ref */
291
292 return kr;
293}
294/* Used by kexts only */
295kern_return_t
296task_id_token_port_name_to_task_external(
297 mach_port_name_t name,
298 task_t *task)
299{
300 return task_id_token_port_name_to_task_grp(name, task, grp: TASK_GRP_EXTERNAL);
301}
302/* Used by kernel proper */
303kern_return_t
304task_id_token_port_name_to_task(
305 mach_port_name_t name,
306 task_t *task)
307{
308 return task_id_token_port_name_to_task_grp(name, task, grp: TASK_GRP_KERNEL);
309}
310
311/* Produces token reference */
312task_id_token_t
313convert_port_to_task_id_token(
314 ipc_port_t port)
315{
316 task_id_token_t token = TASK_ID_TOKEN_NULL;
317
318 if (IP_VALID(port)) {
319 token = ipc_kobject_get_stable(port, type: IKOT_TASK_ID_TOKEN);
320 if (token != TASK_ID_TOKEN_NULL) {
321 zone_require(zone: task_id_token_zone, addr: token);
322 tidt_reference(token);
323 }
324 }
325 return token;
326}
327
328/* Consumes token reference */
329ipc_port_t
330convert_task_id_token_to_port(
331 task_id_token_t token)
332{
333 __assert_only bool kr;
334
335 if (token == TASK_ID_TOKEN_NULL) {
336 return IP_NULL;
337 }
338
339 zone_require(zone: task_id_token_zone, addr: token);
340
341 kr = ipc_kobject_make_send_lazy_alloc_port(port_store: &token->port,
342 kobject: token, type: IKOT_TASK_ID_TOKEN, alloc_opts: IPC_KOBJECT_ALLOC_NONE);
343 assert(kr == TRUE); /* no-senders notification is armed, consumes token ref */
344
345 return token->port;
346}
347
348#if CONFIG_PROC_RESOURCE_LIMITS
349
350/* Should be used only by ports of type IKOT_TASK_FATAL at allocation time */
351void
352task_id_token_set_port(
353 task_id_token_t token,
354 ipc_port_t port)
355{
356 assert(token && port && (ip_kotype(port) == IKOT_TASK_FATAL));
357 token->port = port;
358}
359#endif /* CONFIG_PROC_RESOURCE_LIMITS */
360