1/*
2 * Copyright (c) 2013 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 <sys/param.h>
30#include <sys/malloc.h>
31#include <sys/queue.h>
32#include <sys/systm.h>
33#include <sys/priv.h>
34
35#include <sys/sysproto.h>
36#include <sys/proc_uuid_policy.h>
37
38#include <kern/locks.h>
39#include <uuid/uuid.h>
40
41#include <string.h>
42#include <libkern/OSAtomic.h>
43
44#define PROC_UUID_POLICY_DEBUG 0
45
46#if PROC_UUID_POLICY_DEBUG
47#define dprintf(...) printf(__VA_ARGS__)
48#else
49#define dprintf(...) do { } while(0)
50#endif
51
52static LCK_GRP_DECLARE(proc_uuid_policy_subsys_lck_grp,
53 "proc_uuid_policy_subsys_lock");
54static LCK_MTX_DECLARE(proc_uuid_policy_subsys_mutex,
55 &proc_uuid_policy_subsys_lck_grp);
56
57#define PROC_UUID_POLICY_SUBSYS_LOCK() lck_mtx_lock(&proc_uuid_policy_subsys_mutex)
58#define PROC_UUID_POLICY_SUBSYS_UNLOCK() lck_mtx_unlock(&proc_uuid_policy_subsys_mutex)
59
60#define PROC_UUID_POLICY_HASH_SIZE 64
61u_long proc_uuid_policy_hash_mask;
62
63/* Assume first byte of UUIDs are evenly distributed */
64#define UUIDHASH(uuid) (&proc_uuid_policy_hashtbl[uuid[0] & proc_uuid_policy_hash_mask])
65static LIST_HEAD(proc_uuid_policy_hashhead, proc_uuid_policy_entry) * proc_uuid_policy_hashtbl;
66
67/*
68 * On modification, invalidate cached lookups by bumping the generation count.
69 * Other calls will need to take the slowpath of taking
70 * the subsystem lock.
71 */
72static volatile int32_t proc_uuid_policy_table_gencount;
73#define BUMP_PROC_UUID_POLICY_GENERATION_COUNT() do { \
74 if (OSIncrementAtomic(&proc_uuid_policy_table_gencount) == (INT32_MAX - 1)) { \
75 proc_uuid_policy_table_gencount = 1; \
76 } \
77 } while (0)
78
79#define MAX_PROC_UUID_POLICY_COUNT 10240
80static volatile int32_t proc_uuid_policy_count;
81
82struct proc_uuid_policy_entry {
83 LIST_ENTRY(proc_uuid_policy_entry) entries;
84 uuid_t uuid; /* Mach-O executable UUID */
85 uint32_t flags; /* policy flag for that UUID */
86};
87
88static int
89proc_uuid_policy_insert(uuid_t uuid, uint32_t flags);
90
91static struct proc_uuid_policy_entry *
92proc_uuid_policy_remove_locked(uuid_t uuid, uint32_t flags, int *should_delete);
93
94static int
95proc_uuid_policy_remove(uuid_t uuid, uint32_t flags);
96
97static struct proc_uuid_policy_entry *
98proc_uuid_policy_lookup_locked(uuid_t uuid);
99
100static int
101proc_uuid_policy_clear(uint32_t flags);
102
103void
104proc_uuid_policy_init(void)
105{
106 proc_uuid_policy_hashtbl = hashinit(PROC_UUID_POLICY_HASH_SIZE, M_PROC_UUID_POLICY, hashmask: &proc_uuid_policy_hash_mask);
107 proc_uuid_policy_table_gencount = 1;
108 proc_uuid_policy_count = 0;
109}
110
111static int
112proc_uuid_policy_insert(uuid_t uuid, uint32_t flags)
113{
114 struct proc_uuid_policy_entry *entry, *foundentry = NULL;
115 int error;
116
117#if PROC_UUID_POLICY_DEBUG
118 uuid_string_t uuidstr;
119 uuid_unparse(uuid, uuidstr);
120#endif
121
122 if (uuid_is_null(uu: uuid)) {
123 return EINVAL;
124 }
125
126 entry = kalloc_type(struct proc_uuid_policy_entry, Z_WAITOK | Z_ZERO);
127
128 memcpy(dst: entry->uuid, src: uuid, n: sizeof(uuid_t));
129 entry->flags = flags;
130
131 PROC_UUID_POLICY_SUBSYS_LOCK();
132
133 foundentry = proc_uuid_policy_lookup_locked(uuid);
134 if (foundentry != NULL) {
135 /* The UUID is already in the list. Update the flags. */
136 foundentry->flags |= flags;
137 error = 0;
138 kfree_type(struct proc_uuid_policy_entry, entry);
139 entry = NULL;
140 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
141 } else {
142 /* Our target UUID is not in the list, insert it now */
143 if (proc_uuid_policy_count < MAX_PROC_UUID_POLICY_COUNT) {
144 LIST_INSERT_HEAD(UUIDHASH(uuid), entry, entries);
145 proc_uuid_policy_count++;
146 error = 0;
147 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
148 } else {
149 error = ENOMEM;
150 }
151 }
152
153 PROC_UUID_POLICY_SUBSYS_UNLOCK();
154
155 if (error) {
156 kfree_type(struct proc_uuid_policy_entry, entry);
157 dprintf("Failed to insert proc uuid policy (%s,0x%08x), table full\n", uuidstr, flags);
158 } else {
159 dprintf("Inserted proc uuid policy (%s,0x%08x)\n", uuidstr, flags);
160 }
161
162 return error;
163}
164
165static struct proc_uuid_policy_entry *
166proc_uuid_policy_remove_locked(uuid_t uuid, uint32_t flags, int *should_delete)
167{
168 struct proc_uuid_policy_entry *foundentry = NULL;
169 if (should_delete) {
170 *should_delete = 0;
171 }
172
173 foundentry = proc_uuid_policy_lookup_locked(uuid);
174 if (foundentry) {
175 if (foundentry->flags == flags) {
176 LIST_REMOVE(foundentry, entries);
177 proc_uuid_policy_count--;
178 if (should_delete) {
179 *should_delete = 1;
180 }
181 } else {
182 foundentry->flags &= ~flags;
183 }
184 }
185
186 return foundentry;
187}
188
189static int
190proc_uuid_policy_remove(uuid_t uuid, uint32_t flags)
191{
192 struct proc_uuid_policy_entry *delentry = NULL;
193 int error;
194 int should_delete = 0;
195
196#if PROC_UUID_POLICY_DEBUG
197 uuid_string_t uuidstr;
198 uuid_unparse(uuid, uuidstr);
199#endif
200
201 if (uuid_is_null(uu: uuid)) {
202 return EINVAL;
203 }
204
205 PROC_UUID_POLICY_SUBSYS_LOCK();
206
207 delentry = proc_uuid_policy_remove_locked(uuid, flags, should_delete: &should_delete);
208
209 if (delentry) {
210 error = 0;
211 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
212 } else {
213 error = ENOENT;
214 }
215
216 PROC_UUID_POLICY_SUBSYS_UNLOCK();
217
218 /* If we had found a pre-existing entry, deallocate its memory now */
219 if (delentry && should_delete) {
220 kfree_type(struct proc_uuid_policy_entry, delentry);
221 }
222
223 if (error) {
224 dprintf("Failed to remove proc uuid policy (%s), entry not present\n", uuidstr);
225 } else {
226 dprintf("Removed proc uuid policy (%s)\n", uuidstr);
227 }
228
229 return error;
230}
231
232static struct proc_uuid_policy_entry *
233proc_uuid_policy_lookup_locked(uuid_t uuid)
234{
235 struct proc_uuid_policy_entry *tmpentry, *searchentry, *foundentry = NULL;
236
237 LIST_FOREACH_SAFE(searchentry, UUIDHASH(uuid), entries, tmpentry) {
238 if (0 == memcmp(s1: searchentry->uuid, s2: uuid, n: sizeof(uuid_t))) {
239 foundentry = searchentry;
240 break;
241 }
242 }
243
244 return foundentry;
245}
246
247int
248proc_uuid_policy_lookup(uuid_t uuid, uint32_t *flags, int32_t *gencount)
249{
250 struct proc_uuid_policy_entry *foundentry = NULL;
251 int error;
252
253#if PROC_UUID_POLICY_DEBUG
254 uuid_string_t uuidstr;
255 uuid_unparse(uuid, uuidstr);
256#endif
257
258 if (uuid_is_null(uu: uuid) || !flags || !gencount) {
259 return EINVAL;
260 }
261
262 if (*gencount == proc_uuid_policy_table_gencount) {
263 /*
264 * Generation count hasn't changed, so old flags should be valid.
265 * We avoid taking the lock here by assuming any concurrent modifications
266 * to the table will invalidate the generation count.
267 */
268 return 0;
269 }
270
271 PROC_UUID_POLICY_SUBSYS_LOCK();
272
273 foundentry = proc_uuid_policy_lookup_locked(uuid);
274
275 if (foundentry) {
276 *flags = foundentry->flags;
277 *gencount = proc_uuid_policy_table_gencount;
278 error = 0;
279 } else {
280 error = ENOENT;
281 }
282
283 PROC_UUID_POLICY_SUBSYS_UNLOCK();
284
285 if (error == 0) {
286 dprintf("Looked up proc uuid policy (%s,0x%08x)\n", uuidstr, *flags);
287 }
288
289 return error;
290}
291
292static int
293proc_uuid_policy_clear(uint32_t flags)
294{
295 struct proc_uuid_policy_entry *tmpentry, *searchentry;
296 struct proc_uuid_policy_hashhead deletehead = LIST_HEAD_INITIALIZER(deletehead);
297 unsigned long hashslot;
298
299 /* If clear call includes no flags, infer 'No Cellular' flag */
300 if (flags == PROC_UUID_POLICY_FLAGS_NONE) {
301 flags = PROC_UUID_NO_CELLULAR;
302 }
303
304 PROC_UUID_POLICY_SUBSYS_LOCK();
305
306 if (proc_uuid_policy_count > 0) {
307 for (hashslot = 0; hashslot <= proc_uuid_policy_hash_mask; hashslot++) {
308 struct proc_uuid_policy_hashhead *headp = &proc_uuid_policy_hashtbl[hashslot];
309
310 LIST_FOREACH_SAFE(searchentry, headp, entries, tmpentry) {
311 if ((searchentry->flags & flags) == searchentry->flags) {
312 /* We are clearing all flags for this entry, move entry to our delete list */
313 LIST_REMOVE(searchentry, entries);
314 proc_uuid_policy_count--;
315 LIST_INSERT_HEAD(&deletehead, searchentry, entries);
316 } else {
317 searchentry->flags &= ~flags;
318 }
319 }
320 }
321
322 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
323 }
324
325 PROC_UUID_POLICY_SUBSYS_UNLOCK();
326
327 /* Memory deallocation happens after the hash lock is dropped */
328 LIST_FOREACH_SAFE(searchentry, &deletehead, entries, tmpentry) {
329 LIST_REMOVE(searchentry, entries);
330 kfree_type(struct proc_uuid_policy_entry, searchentry);
331 }
332
333 dprintf("Clearing proc uuid policy table\n");
334
335 return 0;
336}
337
338int
339proc_uuid_policy_kernel(uint32_t operation, uuid_t uuid, uint32_t flags)
340{
341 int error = 0;
342
343 switch (operation) {
344 case PROC_UUID_POLICY_OPERATION_CLEAR:
345 error = proc_uuid_policy_clear(flags);
346 break;
347
348 case PROC_UUID_POLICY_OPERATION_ADD:
349 error = proc_uuid_policy_insert(uuid, flags);
350 break;
351
352 case PROC_UUID_POLICY_OPERATION_REMOVE:
353 error = proc_uuid_policy_remove(uuid, flags);
354 break;
355
356 default:
357 error = EINVAL;
358 break;
359 }
360
361 return error;
362}
363
364int
365proc_uuid_policy(struct proc *p __unused, struct proc_uuid_policy_args *uap, int32_t *retval __unused)
366{
367 int error = 0;
368 uuid_t uuid;
369 memcpy(dst: uuid, src: UUID_NULL, n: sizeof(uuid_t));
370
371 /* Need privilege for policy changes */
372 error = priv_check_cred(cred: kauth_cred_get(), PRIV_PROC_UUID_POLICY, flags: 0);
373 if (error) {
374 dprintf("%s failed privilege check for proc_uuid_policy: %d\n", p->p_comm, error);
375 return error;
376 } else {
377 dprintf("%s succeeded privilege check for proc_uuid_policy\n", p->p_comm);
378 }
379
380 if (uap->uuid) {
381 if (uap->uuidlen != sizeof(uuid_t)) {
382 return ERANGE;
383 }
384
385 error = copyin(uap->uuid, uuid, sizeof(uuid_t));
386 if (error) {
387 return error;
388 }
389 }
390
391 return proc_uuid_policy_kernel(operation: uap->operation, uuid, flags: uap->flags);
392}
393