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_attr_t *proc_uuid_policy_subsys_lck_grp_attr;
53static lck_grp_t *proc_uuid_policy_subsys_lck_grp;
54static lck_attr_t *proc_uuid_policy_subsys_lck_attr;
55static lck_mtx_t proc_uuid_policy_subsys_mutex;
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_subsys_lck_grp_attr = lck_grp_attr_alloc_init();
107 proc_uuid_policy_subsys_lck_grp = lck_grp_alloc_init("proc_uuid_policy_subsys_lock", proc_uuid_policy_subsys_lck_grp_attr);
108 proc_uuid_policy_subsys_lck_attr = lck_attr_alloc_init();
109 lck_mtx_init(&proc_uuid_policy_subsys_mutex, proc_uuid_policy_subsys_lck_grp, proc_uuid_policy_subsys_lck_attr);
110
111 proc_uuid_policy_hashtbl = hashinit(PROC_UUID_POLICY_HASH_SIZE, M_PROC_UUID_POLICY, &proc_uuid_policy_hash_mask);
112 proc_uuid_policy_table_gencount = 1;
113 proc_uuid_policy_count = 0;
114}
115
116static int
117proc_uuid_policy_insert(uuid_t uuid, uint32_t flags)
118{
119 struct proc_uuid_policy_entry *entry, *foundentry = NULL;
120 int error;
121
122#if PROC_UUID_POLICY_DEBUG
123 uuid_string_t uuidstr;
124 uuid_unparse(uuid, uuidstr);
125#endif
126
127 if (uuid_is_null(uuid))
128 return EINVAL;
129
130 MALLOC(entry, struct proc_uuid_policy_entry *, sizeof(*entry), M_PROC_UUID_POLICY, M_WAITOK|M_ZERO);
131
132 memcpy(entry->uuid, uuid, sizeof(uuid_t));
133 entry->flags = flags;
134
135 PROC_UUID_POLICY_SUBSYS_LOCK();
136
137 foundentry = proc_uuid_policy_lookup_locked(uuid);
138 if (foundentry != NULL) {
139 /* The UUID is already in the list. Update the flags. */
140 foundentry->flags |= flags;
141 error = 0;
142 FREE(entry, M_PROC_UUID_POLICY);
143 entry = NULL;
144 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
145 } else {
146 /* Our target UUID is not in the list, insert it now */
147 if (proc_uuid_policy_count < MAX_PROC_UUID_POLICY_COUNT) {
148 LIST_INSERT_HEAD(UUIDHASH(uuid), entry, entries);
149 proc_uuid_policy_count++;
150 error = 0;
151 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
152 } else {
153 error = ENOMEM;
154 }
155 }
156
157 PROC_UUID_POLICY_SUBSYS_UNLOCK();
158
159 if (error) {
160 FREE(entry, M_PROC_UUID_POLICY);
161 dprintf("Failed to insert proc uuid policy (%s,0x%08x), table full\n", uuidstr, flags);
162 } else {
163 dprintf("Inserted proc uuid policy (%s,0x%08x)\n", uuidstr, flags);
164 }
165
166 return error;
167}
168
169static struct proc_uuid_policy_entry *
170proc_uuid_policy_remove_locked(uuid_t uuid, uint32_t flags, int *should_delete)
171{
172 struct proc_uuid_policy_entry *foundentry = NULL;
173 if (should_delete) {
174 *should_delete = 0;
175 }
176
177 foundentry = proc_uuid_policy_lookup_locked(uuid);
178 if (foundentry) {
179 if (foundentry->flags == flags) {
180 LIST_REMOVE(foundentry, entries);
181 proc_uuid_policy_count--;
182 if (should_delete) {
183 *should_delete = 1;
184 }
185 } else {
186 foundentry->flags &= ~flags;
187 }
188 }
189
190 return foundentry;
191}
192
193static int
194proc_uuid_policy_remove(uuid_t uuid, uint32_t flags)
195{
196 struct proc_uuid_policy_entry *delentry = NULL;
197 int error;
198 int should_delete = 0;
199
200#if PROC_UUID_POLICY_DEBUG
201 uuid_string_t uuidstr;
202 uuid_unparse(uuid, uuidstr);
203#endif
204
205 if (uuid_is_null(uuid))
206 return EINVAL;
207
208 PROC_UUID_POLICY_SUBSYS_LOCK();
209
210 delentry = proc_uuid_policy_remove_locked(uuid, flags, &should_delete);
211
212 if (delentry) {
213 error = 0;
214 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
215 } else {
216 error = ENOENT;
217 }
218
219 PROC_UUID_POLICY_SUBSYS_UNLOCK();
220
221 /* If we had found a pre-existing entry, deallocate its memory now */
222 if (delentry && should_delete) {
223 FREE(delentry, M_PROC_UUID_POLICY);
224 }
225
226 if (error) {
227 dprintf("Failed to remove proc uuid policy (%s), entry not present\n", uuidstr);
228 } else {
229 dprintf("Removed proc uuid policy (%s)\n", uuidstr);
230 }
231
232 return error;
233}
234
235static struct proc_uuid_policy_entry *
236proc_uuid_policy_lookup_locked(uuid_t uuid)
237{
238 struct proc_uuid_policy_entry *tmpentry, *searchentry, *foundentry = NULL;
239
240 LIST_FOREACH_SAFE(searchentry, UUIDHASH(uuid), entries, tmpentry) {
241 if (0 == memcmp(searchentry->uuid, uuid, sizeof(uuid_t))) {
242 foundentry = searchentry;
243 break;
244 }
245 }
246
247 return foundentry;
248}
249
250int
251proc_uuid_policy_lookup(uuid_t uuid, uint32_t *flags, int32_t *gencount)
252{
253 struct proc_uuid_policy_entry *foundentry = NULL;
254 int error;
255
256#if PROC_UUID_POLICY_DEBUG
257 uuid_string_t uuidstr;
258 uuid_unparse(uuid, uuidstr);
259#endif
260
261 if (uuid_is_null(uuid) || !flags || !gencount)
262 return EINVAL;
263
264 if (*gencount == proc_uuid_policy_table_gencount) {
265 /*
266 * Generation count hasn't changed, so old flags should be valid.
267 * We avoid taking the lock here by assuming any concurrent modifications
268 * to the table will invalidate the generation count.
269 */
270 return 0;
271 }
272
273 PROC_UUID_POLICY_SUBSYS_LOCK();
274
275 foundentry = proc_uuid_policy_lookup_locked(uuid);
276
277 if (foundentry) {
278 *flags = foundentry->flags;
279 *gencount = proc_uuid_policy_table_gencount;
280 error = 0;
281 } else {
282 error = ENOENT;
283 }
284
285 PROC_UUID_POLICY_SUBSYS_UNLOCK();
286
287 if (error == 0) {
288 dprintf("Looked up proc uuid policy (%s,0x%08x)\n", uuidstr, *flags);
289 }
290
291 return error;
292}
293
294static int
295proc_uuid_policy_clear(uint32_t flags)
296{
297 struct proc_uuid_policy_entry *tmpentry, *searchentry;
298 struct proc_uuid_policy_hashhead deletehead = LIST_HEAD_INITIALIZER(deletehead);
299 unsigned long hashslot;
300
301 /* If clear call includes no flags, infer 'No Cellular' flag */
302 if (flags == PROC_UUID_POLICY_FLAGS_NONE) {
303 flags = PROC_UUID_NO_CELLULAR;
304 }
305
306 PROC_UUID_POLICY_SUBSYS_LOCK();
307
308 if (proc_uuid_policy_count > 0) {
309
310 for (hashslot=0; hashslot <= proc_uuid_policy_hash_mask; hashslot++) {
311 struct proc_uuid_policy_hashhead *headp = &proc_uuid_policy_hashtbl[hashslot];
312
313 LIST_FOREACH_SAFE(searchentry, headp, entries, tmpentry) {
314 if ((searchentry->flags & flags) == searchentry->flags) {
315 /* We are clearing all flags for this entry, move entry to our delete list */
316 LIST_REMOVE(searchentry, entries);
317 proc_uuid_policy_count--;
318 LIST_INSERT_HEAD(&deletehead, searchentry, entries);
319 } else {
320 searchentry->flags &= ~flags;
321 }
322 }
323 }
324
325 BUMP_PROC_UUID_POLICY_GENERATION_COUNT();
326 }
327
328 PROC_UUID_POLICY_SUBSYS_UNLOCK();
329
330 /* Memory deallocation happens after the hash lock is dropped */
331 LIST_FOREACH_SAFE(searchentry, &deletehead, entries, tmpentry) {
332 LIST_REMOVE(searchentry, entries);
333 FREE(searchentry, M_PROC_UUID_POLICY);
334 }
335
336 dprintf("Clearing proc uuid policy table\n");
337
338 return 0;
339}
340
341int proc_uuid_policy_kernel(uint32_t operation, uuid_t uuid, uint32_t flags)
342{
343 int error = 0;
344
345 switch (operation) {
346 case PROC_UUID_POLICY_OPERATION_CLEAR:
347 error = proc_uuid_policy_clear(flags);
348 break;
349
350 case PROC_UUID_POLICY_OPERATION_ADD:
351 error = proc_uuid_policy_insert(uuid, flags);
352 break;
353
354 case PROC_UUID_POLICY_OPERATION_REMOVE:
355 error = proc_uuid_policy_remove(uuid, flags);
356 break;
357
358 default:
359 error = EINVAL;
360 break;
361 }
362
363 return error;
364}
365
366int proc_uuid_policy(struct proc *p __unused, struct proc_uuid_policy_args *uap, int32_t *retval __unused)
367{
368 int error = 0;
369 uuid_t uuid;
370 memcpy(uuid, UUID_NULL, sizeof(uuid_t));
371
372 /* Need privilege for policy changes */
373 error = priv_check_cred(kauth_cred_get(), PRIV_PROC_UUID_POLICY, 0);
374 if (error) {
375 dprintf("%s failed privilege check for proc_uuid_policy: %d\n", p->p_comm, error);
376 return (error);
377 } else {
378 dprintf("%s succeeded privilege check for proc_uuid_policy\n", p->p_comm);
379 }
380
381 if (uap->uuid) {
382 if (uap->uuidlen != sizeof(uuid_t))
383 return ERANGE;
384
385 error = copyin(uap->uuid, uuid, sizeof(uuid_t));
386 if (error)
387 return error;
388 }
389
390 return proc_uuid_policy_kernel(uap->operation, uuid, uap->flags);
391}
392