]> git.saurik.com Git - apple/libc.git/blob - libdarwin/variant.c
927ac2885b5d4e63017039fe5c566326968517ce
[apple/libc.git] / libdarwin / variant.c
1 /*
2 * Copyright (c) 2016 Apple Computer, 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/variant_private.h>
37
38 enum variant_property {
39 VP_ALL = 0,
40 VP_CONTENT,
41 VP_DIAGNOSTICS,
42 VP_UI,
43 VP_SECURITY,
44 VP_MAX
45 };
46
47 enum check_status {
48 S_UNKNOWN = 0,
49 S_NO = 2,
50 S_YES = 3
51 };
52
53 static bool
54 status2bool(enum check_status status) {
55 switch (status) {
56 case S_NO:
57 return false;
58 case S_YES:
59 return true;
60 default:
61 os_crash("os_variant had unexpected status");
62 }
63 }
64
65 #define VAR_FILE_LEGACY "/var/db/disableAppleInternal"
66
67 #if TARGET_OS_OSX
68 #define VAR_FILE_OVERRIDE "/var/db/os_variant_override"
69 #else
70 #define VAR_FILE_OVERRIDE "/usr/share/misc/os_variant_override"
71 #endif
72
73 #if !TARGET_OS_SIMULATOR
74 #define INTERNAL_CONTENT_PATH "/System/Library/CoreServices/AppleInternalVariant.plist"
75 #else
76 #define INTERNAL_CONTENT_PATH "/AppleInternal"
77 #endif
78
79 #define SYSTEM_VERSION_PLIST_PATH "/System/Library/CoreServices/SystemVersion.plist"
80 #define SYSTEM_VERSION_PLIST_KEY "ReleaseType"
81
82 #if TARGET_OS_IPHONE
83 #define INTERNAL_SETTINGS_PATH "/AppleInternal/Library/PreferenceBundles/Internal Settings.bundle"
84 #else
85 #define INTERNAL_DIAGS_PROFILE_PATH "/var/db/ConfigurationProfiles/Settings/com.apple.InternalDiagnostics.plist"
86 #endif
87
88 #if !TARGET_OS_SIMULATOR
89 #define CACHE_SYSCTL_NAME "kern.osvariant_status"
90
91 static void _restore_cached_check_status(uint64_t status);
92 static uint64_t _get_cached_check_status(void);
93
94 static char * _read_file(const char *path, size_t *size_out)
95 {
96 char *buf = NULL;
97
98 int fd = open(path, O_RDONLY);
99 if (fd == -1) return NULL;
100
101 struct stat sb;
102 int rc = fstat(fd, &sb);
103 if (rc != 0 || sb.st_size == 0) {
104 goto error;
105 }
106
107 size_t size_limit = (size_out && *size_out != 0) ? *size_out : 1024;
108 size_t size = (size_t)sb.st_size;
109 if (size_out) *size_out = (size_t)sb.st_size;
110 if (size > size_limit) {
111 goto error;
112 }
113
114 buf = malloc(size + 1);
115 if (!buf) {
116 goto error;
117 }
118
119 ssize_t bytes_read = read(fd, buf, size);
120 buf[size] = '\0';
121
122
123 if (bytes_read == (ssize_t)size) {
124 close(fd);
125 return buf;
126 }
127
128 error:
129 close(fd);
130 free(buf);
131 return NULL;
132 }
133
134 static xpc_object_t read_plist(const char *path)
135 {
136 size_t size = 16 * 1024;
137 uint8_t *buf = (uint8_t*)_read_file(path, &size);
138 if (!buf) return NULL;
139
140 xpc_object_t plist = xpc_create_from_plist(buf, size);
141 if (plist && xpc_get_type(plist) != XPC_TYPE_DICTIONARY) {
142 xpc_release(plist);
143 plist = NULL;
144 }
145
146 free(buf);
147
148 return plist;
149 }
150 #endif
151
152 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
153 static enum check_status internal_content = S_UNKNOWN;
154 #endif
155 #if !TARGET_OS_SIMULATOR
156 static enum check_status can_has_debugger = S_UNKNOWN;
157 #if TARGET_OS_IPHONE
158 static enum check_status internal_release_type = S_UNKNOWN;
159 #else // TARGET_OS_IPHONE
160 static enum check_status internal_diags_profile = S_UNKNOWN;
161 #endif // TARGET_OS_IPHONE
162 #endif // !TARGET_OS_SIMULATOR
163
164 static bool disabled_status[VP_MAX] = {};
165
166 static void _parse_disabled_status(char *test_string)
167 {
168 #if TARGET_OS_SIMULATOR
169 #pragma unused(test_string)
170 #else // TARGET_OS_SIMULATOR
171 char *override_str = NULL;
172
173 bzero(disabled_status, sizeof(disabled_status));
174
175 if (test_string != NULL) {
176 /* used for unit tests */
177 override_str = strdup(test_string);
178 } else {
179 if (access(VAR_FILE_LEGACY, F_OK) == 0) {
180 goto disable_all;
181 } else if (access(VAR_FILE_OVERRIDE, F_OK) != 0) {
182 return;
183 }
184
185 override_str = _read_file(VAR_FILE_OVERRIDE, NULL);
186 }
187
188 if (override_str == NULL) goto disable_all;
189
190 char *token, *string = override_str;
191 while ((token = strsep(&string, ",\n")) != NULL) {
192 if (strcmp(token, "content") == 0) {
193 disabled_status[VP_CONTENT] = true;
194 } else if (strcmp(token, "diagnostics") == 0) {
195 disabled_status[VP_DIAGNOSTICS] = true;
196 } else if (strcmp(token, "ui") == 0) {
197 disabled_status[VP_UI] = true;
198 } else if (strcmp(token, "security") == 0) {
199 disabled_status[VP_SECURITY] = true;
200 }
201 }
202
203 free(override_str);
204 return;
205
206 disable_all:
207 for (int i = 0; i < VP_MAX; i++) {
208 disabled_status[i] = true;
209 }
210 #endif //!TARGET_OS_SIMULATOR
211 }
212
213 #if !TARGET_OS_SIMULATOR
214 static bool _load_cached_status(void)
215 {
216 uint64_t status = 0;
217 size_t status_size = sizeof(status);
218 int ret = sysctlbyname(CACHE_SYSCTL_NAME, &status, &status_size, NULL, 0);
219 if (ret != 0) {
220 return false;
221 }
222
223 if (status) {
224 _restore_cached_check_status(status);
225 return true;
226 }
227
228 if (status == 0 && getpid() == 1) {
229 /*
230 * Looks like we are in launchd; try to set the status.
231 *
232 * We don't actually care if this works because we'll have warmed our state.
233 */
234 status = _get_cached_check_status();
235 sysctlbyname(CACHE_SYSCTL_NAME, NULL, 0, &status, status_size);
236 return true;
237 }
238
239 return false;
240 }
241 #endif
242
243 static void _initialize_status(void * __unused ctx)
244 {
245 #if !TARGET_OS_SIMULATOR
246 if (!_load_cached_status()) {
247 _parse_disabled_status(NULL);
248 }
249 #else
250 _parse_disabled_status(NULL);
251 #endif
252 }
253
254 static bool _check_disabled(enum variant_property variant_property)
255 {
256 static dispatch_once_t disabled_status_pred;
257 dispatch_once_f(&disabled_status_pred, NULL, _initialize_status);
258
259 return disabled_status[variant_property];
260 }
261
262 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
263 static bool _check_internal_content(void)
264 {
265 if (internal_content == S_UNKNOWN) {
266 #if !TARGET_OS_SIMULATOR
267 const char * path = INTERNAL_CONTENT_PATH;
268 #else
269 char *simulator_root = getenv("IPHONE_SIMULATOR_ROOT");
270 char *to_free = NULL, *path = NULL;
271 if (simulator_root) {
272 asprintf(&path, "%s/%s", simulator_root, INTERNAL_CONTENT_PATH);
273 if (path == NULL) {
274 return false;
275 }
276 to_free = path;
277 }
278 #endif
279 internal_content = (access(path, F_OK) == 0) ? S_YES : S_NO;
280 #if TARGET_OS_SIMULATOR
281 free(to_free);
282 #endif
283 }
284 return status2bool(internal_content);
285 }
286 #endif // !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
287
288 #if TARGET_OS_IPHONE
289
290 /*
291 * This set of criteria was taken from copyInternalBuild in MobileGestalt.c
292 */
293 static bool _check_internal_release_type(void)
294 {
295 #if TARGET_OS_SIMULATOR
296 return _check_internal_content();
297 #else // TARGET_OS_SIMULATOR
298 if (internal_release_type == S_UNKNOWN) {
299 xpc_object_t system_version_plist = read_plist(SYSTEM_VERSION_PLIST_PATH);
300 if (system_version_plist) {
301 const char *release_type =
302 xpc_dictionary_get_string(system_version_plist,
303 SYSTEM_VERSION_PLIST_KEY);
304
305 if (release_type == NULL) {
306 /*
307 * Confusingly, customer images are just completely missing this key.
308 */
309 internal_release_type = S_NO;
310 } else if (strcmp(release_type, "Internal") == 0 ||
311 strcmp(release_type, "Lite Internal") == 0 ||
312 strcmp(release_type, "NonUI") == 0) {
313 internal_release_type = S_YES;
314 } else {
315 internal_release_type = S_NO;
316 }
317
318 xpc_release(system_version_plist);
319 } else {
320 internal_release_type = (access(INTERNAL_SETTINGS_PATH, F_OK) == 0) ? S_YES : S_NO;
321 }
322 }
323
324 return status2bool(internal_release_type);
325 #endif // TARGET_OS_SIMULATOR
326 }
327
328 #else
329
330 static bool _check_internal_diags_profile() {
331 static enum check_status internal_diags_profile = S_UNKNOWN;
332
333 if (internal_diags_profile == S_UNKNOWN) {
334 xpc_object_t profile_settings = read_plist(INTERNAL_DIAGS_PROFILE_PATH);
335 if (profile_settings) {
336 internal_diags_profile = xpc_dictionary_get_bool(profile_settings, "AppleInternal") ? S_YES : S_NO;
337 xpc_release(profile_settings);
338 } else {
339 internal_diags_profile = S_NO;
340 }
341 }
342
343 return status2bool(internal_diags_profile);
344 }
345
346 #endif
347
348 static bool _check_can_has_debugger(void)
349 {
350 #if TARGET_OS_SIMULATOR
351 return _check_internal_content();
352 #else
353 if (can_has_debugger == S_UNKNOWN) {
354 #if TARGET_OS_IPHONE
355 can_has_debugger = *((uint32_t *)_COMM_PAGE_DEV_FIRM) ? S_YES : S_NO;
356 #else
357 /*
358 * The comm page bit does exist on macOS, but also requires kernel
359 * debugging in the CSR configuration. We don't need to be that strict
360 * here.
361 */
362 can_has_debugger = (csr_check(CSR_ALLOW_APPLE_INTERNAL) == 0) ? S_YES : S_NO;
363 #endif
364 }
365 return status2bool(can_has_debugger);
366 #endif // TARGET_OS_SIMULATOR
367 }
368
369 // For unit tests
370 #ifndef VARIANT_SKIP_EXPORTED
371
372 bool
373 os_variant_has_internal_content(const char * __unused subsystem)
374 {
375 if (_check_disabled(VP_CONTENT)) {
376 return false;
377 }
378
379 #if TARGET_OS_IPHONE
380 return _check_internal_release_type();
381 #else
382 return _check_internal_content();
383 #endif
384 }
385
386
387 bool
388 os_variant_has_internal_diagnostics(const char * __unused subsystem)
389 {
390 if (_check_disabled(VP_DIAGNOSTICS)) {
391 return false;
392 }
393
394 #if TARGET_OS_IPHONE
395 return _check_internal_release_type();
396 #else
397 return _check_internal_content() || _check_internal_diags_profile();
398 #endif
399 }
400
401 bool
402 os_variant_has_internal_ui(const char * __unused subsystem)
403 {
404 if (_check_disabled(VP_UI)) {
405 return false;
406 }
407
408 #if TARGET_OS_IPHONE
409 return _check_internal_release_type();
410 #else
411 return _check_internal_content();
412 #endif
413 }
414
415 bool
416 os_variant_allows_internal_security_policies(const char * __unused subsystem)
417 {
418 if (_check_disabled(VP_SECURITY)) {
419 return false;
420 }
421
422 return _check_can_has_debugger();
423 }
424
425 #endif // VARIANT_SKIP_EXPORTED
426
427 #define STATUS_INITIAL_BITS 0x70000000F0000000ULL
428 #define STATUS_BIT_WIDTH 2
429 #define STATUS_SET 0x2
430 #define STATUS_MASK 0x3
431
432 enum status_flags_positions {
433 SFP_INTERNAL_CONTENT = 0,
434 SFP_CAN_HAS_DEBUGGER = 1,
435 SFP_INTERNAL_RELEASE_TYPE = 2,
436 SFP_INTERNAL_DIAGS_PROFILE = 3
437 };
438
439 #if !TARGET_OS_SIMULATOR
440 static uint64_t _get_cached_check_status(void)
441 {
442 uint64_t res = STATUS_INITIAL_BITS;
443
444 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
445 _check_internal_content();
446 if (internal_content != S_UNKNOWN)
447 res |= internal_content << SFP_INTERNAL_CONTENT * STATUS_BIT_WIDTH;
448 #endif
449
450 _check_can_has_debugger();
451 if (can_has_debugger != S_UNKNOWN)
452 res |= can_has_debugger << SFP_CAN_HAS_DEBUGGER * STATUS_BIT_WIDTH;
453
454 #if TARGET_OS_IPHONE
455 _check_internal_release_type();
456 if (internal_release_type != S_UNKNOWN)
457 res |= internal_release_type << SFP_INTERNAL_RELEASE_TYPE * STATUS_BIT_WIDTH;
458 #else
459 _check_internal_diags_profile();
460 if (internal_diags_profile != S_UNKNOWN)
461 res |= internal_diags_profile << SFP_INTERNAL_DIAGS_PROFILE * STATUS_BIT_WIDTH;
462 #endif
463
464 _parse_disabled_status(NULL);
465 for (int i = 0; i < VP_MAX; i++) {
466 if (disabled_status[i]) {
467 res |= 0x1ULL << (i + 32);
468 }
469 }
470
471 return res;
472 }
473
474 static void _restore_cached_check_status(uint64_t status)
475 {
476 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
477 if ((status >> (SFP_INTERNAL_CONTENT * STATUS_BIT_WIDTH)) & STATUS_SET)
478 internal_content = (status >> (SFP_INTERNAL_CONTENT * STATUS_BIT_WIDTH)) & STATUS_MASK;
479 #endif
480 if ((status >> (SFP_CAN_HAS_DEBUGGER * STATUS_BIT_WIDTH)) & STATUS_SET)
481 can_has_debugger = (status >> (SFP_CAN_HAS_DEBUGGER * STATUS_BIT_WIDTH)) & STATUS_MASK;
482 #if TARGET_OS_IPHONE
483 if ((status >> (SFP_INTERNAL_RELEASE_TYPE * STATUS_BIT_WIDTH)) & STATUS_SET)
484 internal_release_type = (status >> (SFP_INTERNAL_RELEASE_TYPE * STATUS_BIT_WIDTH)) & STATUS_MASK;
485 #else
486 if ((status >> (SFP_INTERNAL_DIAGS_PROFILE * STATUS_BIT_WIDTH)) & STATUS_SET)
487 internal_diags_profile = (status >> (SFP_INTERNAL_DIAGS_PROFILE * STATUS_BIT_WIDTH)) & STATUS_MASK;
488 #endif
489
490 for (int i = 0; i < VP_MAX; i++) {
491 disabled_status[i] = (status >> (32 + i)) & 0x1;
492 }
493 }
494 #endif // !TARGET_OS_SIMULATOR