]> git.saurik.com Git - apple/libc.git/blob - libdarwin/variant.c
745a0ceb155319f5d875457f38e270f8a01d4bef
[apple/libc.git] / libdarwin / variant.c
1 /*
2 * Copyright (c) 2016 Apple Inc. All rights reserved.
3 *
4 * @APPLE_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. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <sys/stat.h>
27 #include <sys/sysctl.h>
28 #include <sys/types.h>
29 #include <dispatch/dispatch.h>
30 #include <xpc/xpc.h>
31 #include <xpc/private.h>
32 #include <System/sys/csr.h>
33 #include <System/machine/cpu_capabilities.h>
34
35 #include <os/assumes.h>
36 #include <os/stdlib.h>
37 #include <os/variant_private.h>
38
39 /*
40 * Lists all properties overridden by an empty file
41 */
42 #define ALL_OVERRIDES_STR "content,diagnostics,ui,security"
43
44 enum variant_property {
45 VP_CONTENT,
46 VP_DIAGNOSTICS,
47 VP_UI,
48 VP_SECURITY,
49 VP_MAX
50 };
51
52 enum check_status {
53 S_UNKNOWN = 0,
54 S_NO = 2,
55 S_YES = 3
56 };
57
58 static bool
59 status2bool(enum check_status status) {
60 switch (status) {
61 case S_NO:
62 return false;
63 case S_YES:
64 return true;
65 default:
66 os_crash("os_variant had unexpected status");
67 }
68 }
69
70 #define VAR_FILE_LEGACY "/var/db/disableAppleInternal"
71
72 #if TARGET_OS_OSX
73 #define VAR_FILE_OVERRIDE "/var/db/os_variant_override"
74 #else
75 #define VAR_FILE_OVERRIDE "/usr/share/misc/os_variant_override"
76 #endif
77
78 #if !TARGET_OS_SIMULATOR
79 #define INTERNAL_CONTENT_PATH "/System/Library/CoreServices/AppleInternalVariant.plist"
80 #else
81 #define INTERNAL_CONTENT_PATH "/AppleInternal"
82 #endif
83
84 #define SYSTEM_VERSION_PLIST_PATH "/System/Library/CoreServices/SystemVersion.plist"
85 #define SYSTEM_VERSION_PLIST_KEY "ReleaseType"
86
87 #if TARGET_OS_IPHONE
88 #define INTERNAL_SETTINGS_PATH "/AppleInternal/Library/PreferenceBundles/Internal Settings.bundle"
89 #else
90 #define INTERNAL_DIAGS_PROFILE_PATH "/var/db/ConfigurationProfiles/Settings/com.apple.InternalDiagnostics.plist"
91 #define FACTORY_CONTENT_PATH "/System/Library/CoreServices/AppleFactoryVariant.plist"
92 #endif
93
94 #if !TARGET_OS_SIMULATOR
95 #define CACHE_SYSCTL_NAME "kern.osvariant_status"
96
97 static void _restore_cached_check_status(uint64_t status);
98 static uint64_t _get_cached_check_status(void);
99
100 static char * _read_file(const char *path, size_t *size_out)
101 {
102 char *buf = NULL;
103
104 int fd = open(path, O_RDONLY);
105 if (fd == -1) return NULL;
106
107 struct stat sb;
108 int rc = fstat(fd, &sb);
109 if (rc != 0 || sb.st_size == 0) {
110 goto error;
111 }
112
113 size_t size_limit = (size_out && *size_out != 0) ? *size_out : 1024;
114 size_t size = (size_t)sb.st_size;
115 if (size_out) *size_out = (size_t)sb.st_size;
116 if (size > size_limit) {
117 goto error;
118 }
119
120 buf = malloc(size + 1);
121 if (!buf) {
122 goto error;
123 }
124
125 ssize_t bytes_read = read(fd, buf, size);
126 buf[size] = '\0';
127
128
129 if (bytes_read == (ssize_t)size) {
130 close(fd);
131 return buf;
132 }
133
134 error:
135 close(fd);
136 free(buf);
137 return NULL;
138 }
139
140 static xpc_object_t read_plist(const char *path)
141 {
142 size_t size = 16 * 1024;
143 uint8_t *buf = (uint8_t*)_read_file(path, &size);
144 if (!buf) return NULL;
145
146 xpc_object_t plist = xpc_create_from_plist(buf, size);
147 if (plist && xpc_get_type(plist) != XPC_TYPE_DICTIONARY) {
148 xpc_release(plist);
149 plist = NULL;
150 }
151
152 free(buf);
153
154 return plist;
155 }
156 #endif
157
158 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
159 static enum check_status internal_content = S_UNKNOWN;
160 #endif
161 #if !TARGET_OS_SIMULATOR
162 static enum check_status can_has_debugger = S_UNKNOWN;
163 #if TARGET_OS_IPHONE
164 static enum check_status internal_release_type = S_UNKNOWN;
165 static enum check_status factory_release_type = S_UNKNOWN;
166 #else // TARGET_OS_IPHONE
167 static enum check_status internal_diags_profile = S_UNKNOWN;
168 static enum check_status factory_content = S_UNKNOWN;
169 #endif // TARGET_OS_IPHONE
170 #endif // !TARGET_OS_SIMULATOR
171
172 static bool disabled_status[VP_MAX] = {};
173
174 static void _parse_disabled_status(char *test_string)
175 {
176 #if TARGET_OS_SIMULATOR
177 #pragma unused(test_string)
178 #else // TARGET_OS_SIMULATOR
179 char *override_str = NULL;
180
181 bzero(disabled_status, sizeof(disabled_status));
182
183 if (test_string != NULL) {
184 /* used for unit tests */
185 override_str = os_strdup(test_string);
186 } else {
187 if (access(VAR_FILE_LEGACY, F_OK) == 0) {
188 override_str = os_strdup(ALL_OVERRIDES_STR);
189 } else if (access(VAR_FILE_OVERRIDE, F_OK) != 0) {
190 return;
191 }
192
193 override_str = _read_file(VAR_FILE_OVERRIDE, NULL);
194 }
195
196 if (override_str == NULL) {
197 override_str = os_strdup(ALL_OVERRIDES_STR);
198 }
199
200 char *token, *string = override_str;
201 while ((token = strsep(&string, ",\n")) != NULL) {
202 if (strcmp(token, "content") == 0) {
203 disabled_status[VP_CONTENT] = true;
204 } else if (strcmp(token, "diagnostics") == 0) {
205 disabled_status[VP_DIAGNOSTICS] = true;
206 } else if (strcmp(token, "ui") == 0) {
207 disabled_status[VP_UI] = true;
208 } else if (strcmp(token, "security") == 0) {
209 disabled_status[VP_SECURITY] = true;
210 }
211 }
212
213 free(override_str);
214 return;
215 #endif //!TARGET_OS_SIMULATOR
216 }
217
218 #if !TARGET_OS_SIMULATOR
219 static bool _load_cached_status(void)
220 {
221 uint64_t status = 0;
222 size_t status_size = sizeof(status);
223 int ret = sysctlbyname(CACHE_SYSCTL_NAME, &status, &status_size, NULL, 0);
224 if (ret != 0) {
225 return false;
226 }
227
228 if (status) {
229 _restore_cached_check_status(status);
230 return true;
231 }
232
233 if (status == 0 && getpid() == 1) {
234 /*
235 * Looks like we are in launchd; try to set the status.
236 *
237 * We don't actually care if this works because we'll have warmed our state.
238 */
239 status = _get_cached_check_status();
240 sysctlbyname(CACHE_SYSCTL_NAME, NULL, 0, &status, status_size);
241 return true;
242 }
243
244 return false;
245 }
246 #endif
247
248 static void _initialize_status(void * __unused ctx)
249 {
250 #if !TARGET_OS_SIMULATOR
251 if (!_load_cached_status()) {
252 _parse_disabled_status(NULL);
253 }
254 #else
255 _parse_disabled_status(NULL);
256 #endif
257 }
258
259 static bool _check_disabled(enum variant_property variant_property)
260 {
261 static dispatch_once_t disabled_status_pred;
262 dispatch_once_f(&disabled_status_pred, NULL, _initialize_status);
263
264 return disabled_status[variant_property];
265 }
266
267 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
268 static bool _check_internal_content(void)
269 {
270 if (internal_content == S_UNKNOWN) {
271 #if !TARGET_OS_SIMULATOR
272 const char * path = INTERNAL_CONTENT_PATH;
273 #else
274 char *simulator_root = getenv("IPHONE_SIMULATOR_ROOT");
275 char *to_free = NULL, *path = NULL;
276 if (simulator_root) {
277 asprintf(&path, "%s/%s", simulator_root, INTERNAL_CONTENT_PATH);
278 if (path == NULL) {
279 return false;
280 }
281 to_free = path;
282 }
283 #endif
284 internal_content = (access(path, F_OK) == 0) ? S_YES : S_NO;
285 #if TARGET_OS_SIMULATOR
286 free(to_free);
287 #endif
288 }
289 return status2bool(internal_content);
290 }
291 #endif // !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
292
293 #if TARGET_OS_OSX
294 static bool _check_factory_content(void)
295 {
296 if (factory_content == S_UNKNOWN) {
297 const char * path = FACTORY_CONTENT_PATH;
298 factory_content = (access(path, F_OK) == 0) ? S_YES : S_NO;
299 }
300 return status2bool(factory_content);
301 }
302 #endif // TARGET_OS_OSX
303
304 #if TARGET_OS_IPHONE
305
306 #if !TARGET_OS_SIMULATOR
307 static bool _parse_system_version_plist(void)
308 {
309 xpc_object_t system_version_plist = read_plist(SYSTEM_VERSION_PLIST_PATH);
310 if (!system_version_plist) {
311 return false;
312 }
313
314 const char *release_type =
315 xpc_dictionary_get_string(system_version_plist,
316 SYSTEM_VERSION_PLIST_KEY);
317
318 if (release_type == NULL) {
319 /*
320 * Confusingly, customer images are just completely missing this key.
321 */
322 internal_release_type = S_NO;
323 factory_release_type = S_NO;
324 } else if (strcmp(release_type, "Internal") == 0 ||
325 strcmp(release_type, "Lite Internal") == 0) {
326 internal_release_type = S_YES;
327 factory_release_type = S_NO;
328 } else if (strcmp(release_type, "NonUI") == 0) {
329 internal_release_type = S_YES;
330 factory_release_type = S_YES;
331 } else {
332 internal_release_type = S_NO;
333 factory_release_type = S_NO;
334 }
335
336 xpc_release(system_version_plist);
337
338 return true;
339 }
340 #endif //!TARGET_OS_SIMULATOR
341
342 /*
343 * This set of criteria was taken from copyInternalBuild in MobileGestalt.c
344 */
345 static bool _check_internal_release_type(void)
346 {
347 #if TARGET_OS_SIMULATOR
348 return _check_internal_content();
349 #else // TARGET_OS_SIMULATOR
350 if (internal_release_type == S_UNKNOWN) {
351 if (!_parse_system_version_plist()) {
352 internal_release_type = (access(INTERNAL_SETTINGS_PATH, F_OK) == 0) ? S_YES : S_NO;
353 }
354 }
355
356 return status2bool(internal_release_type);
357 #endif // TARGET_OS_SIMULATOR
358 }
359
360 static bool _check_factory_release_type(void)
361 {
362 #if TARGET_OS_SIMULATOR
363 return false;
364 #else // TARGET_OS_SIMULATOR
365 if (factory_release_type == S_UNKNOWN) {
366 if (!_parse_system_version_plist()) {
367 factory_release_type = S_NO;
368 }
369 }
370
371 return status2bool(factory_release_type);
372 #endif // TARGET_OS_SIMULATOR
373 }
374
375 #else
376
377 static bool _check_internal_diags_profile(void)
378 {
379 if (internal_diags_profile == S_UNKNOWN) {
380 xpc_object_t profile_settings = read_plist(INTERNAL_DIAGS_PROFILE_PATH);
381 if (profile_settings) {
382 internal_diags_profile = xpc_dictionary_get_bool(profile_settings, "AppleInternal") ? S_YES : S_NO;
383 xpc_release(profile_settings);
384 } else {
385 internal_diags_profile = S_NO;
386 }
387 }
388
389 return status2bool(internal_diags_profile);
390 }
391
392 #endif
393
394 static bool _check_can_has_debugger(void)
395 {
396 #if TARGET_OS_SIMULATOR
397 return _check_internal_content();
398 #else
399 if (can_has_debugger == S_UNKNOWN) {
400 #if TARGET_OS_IPHONE
401 can_has_debugger = *((uint32_t *)_COMM_PAGE_DEV_FIRM) ? S_YES : S_NO;
402 #else
403 /*
404 * The comm page bit does exist on macOS, but also requires kernel
405 * debugging in the CSR configuration. We don't need to be that strict
406 * here.
407 */
408 can_has_debugger = (csr_check(CSR_ALLOW_APPLE_INTERNAL) == 0) ? S_YES : S_NO;
409 #endif
410 }
411 return status2bool(can_has_debugger);
412 #endif // TARGET_OS_SIMULATOR
413 }
414
415 // For unit tests
416 #ifndef VARIANT_SKIP_EXPORTED
417
418 bool
419 os_variant_has_internal_content(const char * __unused subsystem)
420 {
421 if (_check_disabled(VP_CONTENT)) {
422 return false;
423 }
424
425 #if TARGET_OS_IPHONE
426 return _check_internal_release_type();
427 #else
428 return _check_internal_content();
429 #endif
430 }
431
432
433 bool
434 os_variant_has_internal_diagnostics(const char * __unused subsystem)
435 {
436 if (_check_disabled(VP_DIAGNOSTICS)) {
437 return false;
438 }
439
440 #if TARGET_OS_IPHONE
441 return _check_internal_release_type();
442 #else
443 return _check_internal_content() || _check_internal_diags_profile();
444 #endif
445 }
446
447 bool
448 os_variant_has_internal_ui(const char * __unused subsystem)
449 {
450 if (_check_disabled(VP_UI)) {
451 return false;
452 }
453
454 #if TARGET_OS_IPHONE
455 return _check_internal_release_type();
456 #else
457 return _check_internal_content();
458 #endif
459 }
460
461 bool
462 os_variant_allows_internal_security_policies(const char * __unused subsystem)
463 {
464 if (_check_disabled(VP_SECURITY)) {
465 return false;
466 }
467
468 return _check_can_has_debugger();
469 }
470
471 bool
472 os_variant_has_factory_content(const char * __unused subsystem)
473 {
474 #if TARGET_OS_IPHONE
475 return _check_factory_release_type();
476 #else
477 return _check_factory_content();
478 #endif
479 }
480
481 #endif // VARIANT_SKIP_EXPORTED
482
483 #define STATUS_INITIAL_BITS 0x70000000F0000000ULL
484 #define STATUS_BIT_WIDTH 2
485 #define STATUS_SET 0x2
486 #define STATUS_MASK 0x3
487
488 enum status_flags_positions {
489 SFP_INTERNAL_CONTENT = 0,
490 SFP_CAN_HAS_DEBUGGER = 1,
491 SFP_INTERNAL_RELEASE_TYPE = 2,
492 SFP_INTERNAL_DIAGS_PROFILE = 3,
493 SFP_FACTORY_CONTENT = 4,
494 SFP_FACTORY_RELEASE_TYPE = 5,
495 };
496
497 #if !TARGET_OS_SIMULATOR
498 static uint64_t _get_cached_check_status(void)
499 {
500 uint64_t res = STATUS_INITIAL_BITS;
501
502 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
503 _check_internal_content();
504 if (internal_content != S_UNKNOWN)
505 res |= internal_content << SFP_INTERNAL_CONTENT * STATUS_BIT_WIDTH;
506 #endif
507
508 _check_can_has_debugger();
509 if (can_has_debugger != S_UNKNOWN)
510 res |= can_has_debugger << SFP_CAN_HAS_DEBUGGER * STATUS_BIT_WIDTH;
511
512 #if TARGET_OS_IPHONE
513 _check_internal_release_type();
514 if (internal_release_type != S_UNKNOWN)
515 res |= internal_release_type << SFP_INTERNAL_RELEASE_TYPE * STATUS_BIT_WIDTH;
516
517 _check_factory_release_type();
518 if (factory_release_type != S_UNKNOWN)
519 res |= factory_release_type << SFP_FACTORY_RELEASE_TYPE * STATUS_BIT_WIDTH;
520 #else
521 _check_internal_diags_profile();
522 if (internal_diags_profile != S_UNKNOWN)
523 res |= internal_diags_profile << SFP_INTERNAL_DIAGS_PROFILE * STATUS_BIT_WIDTH;
524
525 _check_factory_content();
526 if (factory_content != S_UNKNOWN)
527 res |= factory_content << SFP_FACTORY_CONTENT * STATUS_BIT_WIDTH;
528 #endif
529
530 _parse_disabled_status(NULL);
531 for (int i = 0; i < VP_MAX; i++) {
532 if (disabled_status[i]) {
533 res |= 0x1ULL << (i + 32);
534 }
535 }
536
537 return res;
538 }
539
540 static void _restore_cached_check_status(uint64_t status)
541 {
542 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
543 if ((status >> (SFP_INTERNAL_CONTENT * STATUS_BIT_WIDTH)) & STATUS_SET)
544 internal_content = (status >> (SFP_INTERNAL_CONTENT * STATUS_BIT_WIDTH)) & STATUS_MASK;
545 #endif
546
547 if ((status >> (SFP_CAN_HAS_DEBUGGER * STATUS_BIT_WIDTH)) & STATUS_SET)
548 can_has_debugger = (status >> (SFP_CAN_HAS_DEBUGGER * STATUS_BIT_WIDTH)) & STATUS_MASK;
549
550 #if TARGET_OS_IPHONE
551 if ((status >> (SFP_INTERNAL_RELEASE_TYPE * STATUS_BIT_WIDTH)) & STATUS_SET)
552 internal_release_type = (status >> (SFP_INTERNAL_RELEASE_TYPE * STATUS_BIT_WIDTH)) & STATUS_MASK;
553
554 if ((status >> (SFP_FACTORY_RELEASE_TYPE * STATUS_BIT_WIDTH)) & STATUS_SET)
555 factory_release_type = (status >> (SFP_FACTORY_RELEASE_TYPE * STATUS_BIT_WIDTH)) & STATUS_MASK;
556 #else
557 if ((status >> (SFP_INTERNAL_DIAGS_PROFILE * STATUS_BIT_WIDTH)) & STATUS_SET)
558 internal_diags_profile = (status >> (SFP_INTERNAL_DIAGS_PROFILE * STATUS_BIT_WIDTH)) & STATUS_MASK;
559
560 if ((status >> (SFP_FACTORY_CONTENT * STATUS_BIT_WIDTH)) & STATUS_SET)
561 factory_content = (status >> (SFP_FACTORY_CONTENT * STATUS_BIT_WIDTH)) & STATUS_MASK;
562 #endif
563
564 for (int i = 0; i < VP_MAX; i++) {
565 disabled_status[i] = (status >> (32 + i)) & 0x1;
566 }
567 }
568 #endif // !TARGET_OS_SIMULATOR