1 /* Copyright (c) 2020 Apple Computer, Inc. All rights reserved. */
2
3 #include <CoreSymbolication/CoreSymbolication.h>
4 #include <CoreSymbolication/CoreSymbolicationPrivate.h>
5 #include <darwintest.h>
6 #include <dispatch/dispatch.h>
7
8 #include <mach-o/loader.h>
9
10 #include <sys/kas_info.h>
11
12 #include <sys/mman.h>
13 #include <sys/stat.h>
14 #include <sys/sysctl.h>
15 #include <sys/types.h>
16
17 #include <fcntl.h>
18
19 #include <stdint.h>
20
21 T_GLOBAL_META(
22 T_META_NAMESPACE("xnu.kas_info"),
23 T_META_CHECK_LEAKS(false),
24 T_META_ASROOT(true));
25
26 static bool
slide_enabled(void)27 slide_enabled(void)
28 {
29 int slide_enabled, err;
30 size_t size = sizeof(slide_enabled);
31 err = sysctlbyname("kern.slide", &slide_enabled, &size, NULL, 0);
32 T_ASSERT_POSIX_SUCCESS(err, "sysctl(\"kern.slide\");");
33 return slide_enabled != 0;
34 }
35
36 static uint64_t
kernel_slide(int selector)37 kernel_slide(int selector)
38 {
39 uint64_t slide;
40 size_t size = sizeof(slide);
41 int err = kas_info(selector, &slide, &size);
42 if (err && errno == ENOTSUP) {
43 T_SKIP("Running on kernel without kas_info");
44 }
45
46 T_ASSERT_POSIX_SUCCESS(errno, "kas_info with selector %d", selector);
47 T_ASSERT_EQ(size, sizeof(slide), "returned size is valid");
48
49 return slide;
50 }
51
52 T_DECL(kernel_text_slide,
53 "ensures that kas_info can return the kernel text slide")
54 {
55 if (!slide_enabled()) {
56 T_SKIP("KASLR is not enabled");
57 __builtin_unreachable();
58 }
59
60 uint64_t slide = kernel_slide(KAS_INFO_KERNEL_TEXT_SLIDE_SELECTOR);
61
62 T_ASSERT_GT_ULLONG(slide, 0ULL, "kernel slide is non-zero");
63 }
64
65 static void
test_kas_info_invalid_args(int selector)66 test_kas_info_invalid_args(int selector)
67 {
68 uint64_t slide;
69 size_t size = 0;
70 int err;
71
72 err = kas_info(selector, &slide, NULL);
73 if (errno == ENOTSUP) {
74 T_SKIP("Running on kernel without kas_info %d", selector);
75 }
76 T_ASSERT_POSIX_FAILURE(err, EFAULT, "kas_info %d with NULL size", selector);
77
78 size = sizeof(uint64_t);
79 err = kas_info(selector, NULL, &size);
80 T_ASSERT_POSIX_FAILURE(err, EFAULT, "kas_info %d with NULL slide", selector);
81
82 size = sizeof(uint32_t);
83 err = kas_info(selector, &slide, &size);
84 T_ASSERT_POSIX_FAILURE(err, EINVAL, "kas_info %d with invalid size", selector);
85 }
86
87 T_DECL(kernel_text_slide_invalid,
88 "ensures that kas_info handles invalid input to KERNEL_TEXT_SLIDE_SELECTOR")
89 {
90 test_kas_info_invalid_args(KAS_INFO_KERNEL_TEXT_SLIDE_SELECTOR);
91 }
92
93 static bool
sptm_enabled(void)94 sptm_enabled(void)
95 {
96 int page_protection_type, err;
97 size_t size = sizeof(page_protection_type);
98 err = sysctlbyname("kern.page_protection_type", &page_protection_type, &size, NULL, 0);
99 T_ASSERT_POSIX_SUCCESS(err, "sysctl(\"kern.page_protection_type\");");
100 return page_protection_type == 2;
101 }
102
103 T_DECL(sptm_txm_text_slide,
104 "ensures that kas_info can return the SPTM/TXM text slides")
105 {
106 const uint64_t sptm_slide = kernel_slide(KAS_INFO_SPTM_TEXT_SLIDE_SELECTOR);
107 const uint64_t txm_slide = kernel_slide(KAS_INFO_TXM_TEXT_SLIDE_SELECTOR);
108
109 if (sptm_enabled()) {
110 T_ASSERT_GT_ULLONG(sptm_slide, 0ULL, "Ensure the SPTM slide is non-zero on an SPTM-enabled system");
111 T_ASSERT_GT_ULLONG(txm_slide, 0ULL, "Ensure the TXM slide is non-zero on an SPTM-enabled system");
112 } else {
113 /* When not running on an SPTM-enabled system, this selector should return zero. */
114 T_ASSERT_EQ(sptm_slide, 0ULL, "Ensure the SPTM slide is zero on a non-SPTM system");
115 T_ASSERT_EQ(txm_slide, 0ULL, "Ensure the TXM slide is zero on a non-SPTM system");
116 }
117 }
118
119 T_DECL(sptm_txm_text_slide_invalid,
120 "ensures that kas_info handles invalid input to [SPTM|TXM]_TEXT_SLIDE_SELECTOR")
121 {
122 test_kas_info_invalid_args(KAS_INFO_SPTM_TEXT_SLIDE_SELECTOR);
123 test_kas_info_invalid_args(KAS_INFO_TXM_TEXT_SLIDE_SELECTOR);
124 }
125
126 static char const*
kernel_path(void)127 kernel_path(void)
128 {
129 static CSSymbolicatorRef symbolicator;
130 static char const* path;
131 static dispatch_once_t once;
132 dispatch_once(&once, ^{
133 uint32_t flags = kCSSymbolicatorDefaultCreateFlags;
134 symbolicator = CSSymbolicatorCreateWithMachKernelFlagsAndNotification(flags, NULL);
135 T_QUIET; T_ASSERT_TRUE(!CSIsNull(symbolicator), "CSSymbolicatorCreateWithMachKernelFlagsAndNotification");
136 path = CSSymbolOwnerGetPath(CSSymbolicatorGetAOutSymbolOwner(symbolicator));
137 if (!path) {
138 path = CSSymbolOwnerGetPath(CSSymbolicatorGetSymbolOwner(symbolicator));
139 }
140 T_QUIET; T_ASSERT_NOTNULL(path, "CSSymbolOwnerGetPath/CSSymbolicatorGetSymbolOwner");
141 });
142 return path;
143 }
144
145 static void
disk_kernel_segments(uint64_t ** segs_out,size_t * nsegs_out)146 disk_kernel_segments(uint64_t **segs_out, size_t *nsegs_out)
147 {
148 char const* path = kernel_path();
149 int fd = open(path, O_RDONLY);
150 int err;
151 struct stat sb;
152 size_t nsegs = 0;
153 uint64_t *segs = NULL;
154 void *data;
155
156 T_LOG("Kernel file is %s", path);
157 T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, "open kernel file");
158
159 err = fstat(fd, &sb);
160 T_ASSERT_POSIX_SUCCESS(err, "fstat kernel file");
161
162 data = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
163 T_ASSERT_NE(data, MAP_FAILED, "mmap kernel file");
164
165 /*
166 * TODO: If we bring back FAT kernel binaries
167 * this will need to be fixed to handle them properly
168 */
169 uint32_t magic = *(uint32_t*)data;
170 struct load_command *cmd = NULL;
171
172 switch (magic) {
173 case MH_MAGIC: OS_FALLTHROUGH;
174 case MH_CIGAM: {
175 struct mach_header *mh = (struct mach_header *)data;
176 cmd = (struct load_command *)(&(mh[1]));
177 nsegs = mh->ncmds;
178 }
179 break;
180 case MH_MAGIC_64: OS_FALLTHROUGH;
181 case MH_CIGAM_64: {
182 struct mach_header_64 *mh = (struct mach_header_64 *)data;
183 cmd = (struct load_command *)(&(mh[1]));
184 nsegs = mh->ncmds;
185 }
186 break;
187 default:
188 T_FAIL("kernel file is not a Mach-O file, magic is %x", magic);
189 }
190
191 /* Adjust for the LC_UUID && LC_BUILD_VERSION commands in front of
192 * load commands for dSYMs
193 */
194 while (cmd->cmd != LC_SEGMENT && cmd->cmd != LC_SEGMENT_64) {
195 cmd = (struct load_command *) ((uintptr_t) cmd + cmd->cmdsize);
196 nsegs--;
197 }
198
199 segs = calloc(nsegs, sizeof(*segs));
200 T_ASSERT_NOTNULL(segs, "calloc disk segment array");
201
202 for (uint8_t i = 0; i < nsegs; i++) {
203 if (cmd->cmd == LC_SEGMENT) {
204 struct segment_command *sg = (struct segment_command *) cmd;
205 if (sg->vmsize > 0) {
206 segs[i] = sg->vmaddr;
207 }
208 } else if (cmd->cmd == LC_SEGMENT_64) {
209 struct segment_command_64 *sg = (struct segment_command_64 *) cmd;
210 if (sg->vmsize > 0) {
211 segs[i] = sg->vmaddr;
212 }
213 }
214 cmd = (struct load_command *) ((uintptr_t) cmd + cmd->cmdsize);
215 }
216
217 *segs_out = segs;
218 *nsegs_out = nsegs;
219
220 err = munmap(data, (size_t)sb.st_size);
221
222 err = close(fd);
223 T_ASSERT_POSIX_SUCCESS(err, "close kernel fd");
224 }
225
226 static bool
is_fileset_kc(void)227 is_fileset_kc(void)
228 {
229 char uuid[1024];
230 int err;
231 size_t size = sizeof(uuid);
232 err = sysctlbyname("kern.filesetuuid", uuid, &size, NULL, 0);
233 return err == 0;
234 }
235
236 #define KAS_INFO_KERNEL_SEGMENT_LOCATION_SELECTOR 1
237
238 T_DECL(kernel_segment_location,
239 "ensures that KAS_INFO_KERNEL_SEGMENT_LOCATION returns correct segment locations")
240 {
241 int err;
242
243 if (!slide_enabled()) {
244 T_SKIP("KASLR is not enabled");
245 __builtin_unreachable();
246 }
247
248 uint64_t *disk_segs;
249 size_t disk_nsegs;
250 disk_kernel_segments(&disk_segs, &disk_nsegs);
251
252 size_t size = 0;
253
254 err = kas_info(KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR, NULL, &size);
255 if (errno == ENOTSUP) {
256 T_SKIP("KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR not supported");
257 }
258 T_ASSERT_POSIX_SUCCESS(err, "kas_info KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR for size");
259
260 uint64_t mem_nsegs = size / sizeof(uint64_t);
261 uint64_t *mem_segs = calloc(mem_nsegs, sizeof(*disk_segs));
262
263 err = kas_info(KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR, mem_segs, &size);
264 if (errno == ENOTSUP) {
265 T_SKIP("KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR not supported");
266 }
267
268 T_ASSERT_POSIX_SUCCESS(err, "kas_info KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR for data");
269
270 T_LOG("Kernel has %zu segments on disk, %zu in memory:", disk_nsegs, mem_nsegs);
271 for (size_t i = 0; i < disk_nsegs; i++) {
272 T_LOG("%zu %llx %llx", i, disk_segs[i], mem_segs[i]);
273 }
274
275 /*
276 * If the kernel is not a fileset, verify that all
277 * the segments in memory are the segment on disk
278 * + the kaslr slide
279 */
280 if (!is_fileset_kc()) {
281 T_LOG("Kernelcache is not a fileset kernelcache");
282
283 uint64_t slide = kernel_slide(KAS_INFO_KERNEL_TEXT_SLIDE_SELECTOR);
284 for (size_t i = 0; i < disk_nsegs; i++) {
285 if (disk_segs[i] == 0 || mem_segs[i] == 0) {
286 continue;
287 }
288 T_ASSERT_EQ(disk_segs[i] + slide, mem_segs[i], "segment %zu is slid", i);
289 }
290 }
291
292 free(disk_segs);
293 free(mem_segs);
294 }
295