]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
1 | // Copyright (c) 2020 Apple, Inc. All rights reserved. |
2 | ||
3 | #include <darwintest.h> | |
4 | #include <dirent.h> | |
5 | #include <fcntl.h> | |
6 | #include <libkern/OSByteOrder.h> | |
7 | #include <mach-o/loader.h> | |
8 | #include <stdbool.h> | |
9 | #include <sys/mman.h> | |
10 | #include <sys/stat.h> | |
11 | #include <sys/sysctl.h> | |
12 | #include <TargetConditionals.h> | |
13 | #include <unistd.h> | |
14 | #include <uuid/uuid.h> | |
15 | ||
16 | static bool | |
17 | get_macho_uuid(const char *cwd, const char *path, uuid_t uuid) | |
18 | { | |
19 | bool found = false; | |
20 | void *mapped = MAP_FAILED; | |
21 | size_t mapped_len = 0; | |
22 | ||
23 | T_SETUPBEGIN; | |
24 | ||
25 | // Skip irregular files (directories, devices, etc.). | |
26 | struct stat stbuf = {}; | |
27 | int ret = stat(path, &stbuf); | |
28 | if (ret < 0 && errno == ENOENT) { | |
29 | goto out; | |
30 | } | |
31 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "should stat %s%s", cwd, path); | |
32 | if ((stbuf.st_mode & S_IFREG) == 0) { | |
33 | goto out; | |
34 | } | |
35 | if (stbuf.st_size < (off_t)sizeof(struct mach_header)) { | |
36 | goto out; | |
37 | } | |
38 | ||
39 | int fd = open(path, O_RDONLY); | |
40 | if (fd < 0 && (errno == EPERM || errno == EACCES || errno == ENOENT)) { | |
41 | goto out; | |
42 | } | |
43 | T_QUIET; | |
44 | T_ASSERT_POSIX_SUCCESS(fd, "should open file at %s%s", cwd, path); | |
45 | ||
46 | mapped = mmap(NULL, (size_t)stbuf.st_size, PROT_READ, MAP_PRIVATE, | |
47 | fd, 0); | |
48 | T_QUIET; T_WITH_ERRNO; | |
49 | T_ASSERT_NE(mapped, MAP_FAILED, "should map Mach-O binary at %s%s", | |
50 | cwd, path); | |
51 | (void)close(fd); | |
52 | ||
53 | // Mach-O parsing boilerplate. | |
54 | uint32_t magic = *(uint32_t *)mapped; | |
55 | bool should_swap = false; | |
56 | bool b32 = false; | |
57 | // XXX This does not handle fat binaries. | |
58 | switch (magic) { | |
59 | case MH_CIGAM: | |
60 | should_swap = true; | |
61 | OS_FALLTHROUGH; | |
62 | case MH_MAGIC: | |
63 | b32 = true; | |
64 | break; | |
65 | case MH_CIGAM_64: | |
66 | should_swap = true; | |
67 | break; | |
68 | case MH_MAGIC_64: | |
69 | break; | |
70 | default: | |
71 | goto out; | |
72 | } | |
73 | const struct load_command *lcmd = NULL; | |
74 | unsigned int ncmds = 0; | |
75 | if (b32) { | |
76 | const struct mach_header *hdr = mapped; | |
77 | ncmds = hdr->ncmds; | |
78 | lcmd = (const void *)((const char *)mapped + sizeof(*hdr)); | |
79 | } else { | |
80 | const struct mach_header_64 *hdr = mapped; | |
81 | ncmds = hdr->ncmds; | |
82 | lcmd = (const void *)((const char *)mapped + sizeof(*hdr)); | |
83 | } | |
84 | ncmds = should_swap ? OSSwapInt32(ncmds) : ncmds; | |
85 | ||
86 | // Scan through load commands to find LC_UUID. | |
87 | for (unsigned int i = 0; i < ncmds; i++) { | |
88 | if ((should_swap ? OSSwapInt32(lcmd->cmd) : lcmd->cmd) == LC_UUID) { | |
89 | const struct uuid_command *uuid_cmd = (const void *)lcmd; | |
90 | uuid_copy(uuid, uuid_cmd->uuid); | |
91 | found = true; | |
92 | break; | |
93 | } | |
94 | ||
95 | uint32_t cmdsize = should_swap ? OSSwapInt32(lcmd->cmdsize) : | |
96 | lcmd->cmdsize; | |
97 | lcmd = (const void *)((const char *)lcmd + cmdsize); | |
98 | } | |
99 | ||
100 | if (!found) { | |
101 | T_LOG("could not find LC_UUID in Mach-O at %s%s", cwd, path); | |
102 | } | |
103 | ||
104 | out: | |
105 | T_SETUPEND; | |
106 | ||
107 | if (mapped != MAP_FAILED) { | |
108 | munmap(mapped, mapped_len); | |
109 | } | |
110 | return found; | |
111 | } | |
112 | ||
113 | T_DECL(correct_kernel_booted, | |
114 | "Make sure the kernel on disk matches the running kernel, by UUID.", | |
115 | T_META_RUN_CONCURRENTLY(true)) | |
116 | { | |
117 | T_SETUPBEGIN; | |
118 | ||
119 | uuid_t kern_uuid; | |
120 | uuid_string_t kern_uuid_str; | |
121 | size_t kern_uuid_size = sizeof(kern_uuid_str); | |
122 | int ret = sysctlbyname("kern.uuid", &kern_uuid_str, &kern_uuid_size, NULL, | |
123 | 0); | |
124 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "should get running kernel UUID"); | |
125 | T_LOG("%s: running kernel", kern_uuid_str); | |
126 | ||
127 | ret = uuid_parse(kern_uuid_str, kern_uuid); | |
128 | T_QUIET; T_ASSERT_EQ(ret, 0, "should parse kernel UUID into bytes"); | |
129 | ||
130 | #if TARGET_OS_OSX | |
131 | const char *kernels_path = "/System/Library/Kernels/"; | |
132 | #else // TARGET_OS_OSX | |
133 | const char *kernels_path = "/"; | |
134 | #endif // !TARGET_OS_OSX | |
135 | T_LOG("searching for kernels at %s", kernels_path); | |
136 | ||
137 | ret = chdir(kernels_path); | |
138 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "should change directory to %s", | |
139 | kernels_path); | |
140 | ||
141 | DIR *kernels_dir = opendir(kernels_path); | |
142 | T_QUIET; T_ASSERT_NOTNULL(kernels_dir, "should open directory at %s", | |
143 | kernels_path); | |
144 | ||
145 | T_SETUPEND; | |
146 | ||
147 | bool found = false; | |
148 | struct dirent *entry = NULL; | |
149 | while ((entry = readdir(kernels_dir)) != NULL) { | |
150 | uuid_t bin_uuid; | |
151 | bool ok = get_macho_uuid(kernels_path, entry->d_name, bin_uuid); | |
152 | if (ok) { | |
153 | uuid_string_t bin_uuid_str; | |
154 | uuid_unparse(bin_uuid, bin_uuid_str); | |
155 | T_LOG("%s: from %s%s", bin_uuid_str, kernels_path, entry->d_name); | |
156 | if (uuid_compare(bin_uuid, kern_uuid) == 0) { | |
157 | found = true; | |
158 | T_PASS("UUID from %s%s matches kernel UUID", kernels_path, | |
159 | entry->d_name); | |
160 | } | |
161 | } | |
162 | } | |
163 | if (!found) { | |
164 | T_FAIL("failed to find kernel binary with UUID of the running kernel, " | |
165 | "wrong kernel is booted"); | |
166 | } | |
167 | } |