]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
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 | |
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 | |
37 | kernel_slide(void) | |
38 | { | |
39 | uint64_t slide; | |
40 | size_t size = sizeof(slide); | |
41 | int err = kas_info(KAS_INFO_KERNEL_TEXT_SLIDE_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 KAS_INFO_KERNEL_TEXT_SLIDE_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(); | |
61 | ||
62 | T_ASSERT_GT_ULLONG(slide, 0ULL, "kernel slide is non-zero"); | |
63 | } | |
64 | ||
65 | T_DECL(kernel_text_slide_invalid, | |
66 | "ensures that kas_info handles invalid input to KERNEL_TEXT_SLIDE_SELECTOR") | |
67 | { | |
68 | uint64_t slide; | |
69 | size_t size = 0; | |
70 | int err; | |
71 | ||
72 | err = kas_info(KAS_INFO_KERNEL_TEXT_SLIDE_SELECTOR, &slide, NULL); | |
73 | if (errno == ENOTSUP) { | |
74 | T_SKIP("Running on kernel without kas_info"); | |
75 | } | |
76 | T_ASSERT_POSIX_FAILURE(err, EFAULT, "kas_info with NULL size"); | |
77 | ||
78 | size = sizeof(uint64_t); | |
79 | err = kas_info(KAS_INFO_KERNEL_TEXT_SLIDE_SELECTOR, NULL, &size); | |
80 | T_ASSERT_POSIX_FAILURE(err, EFAULT, "kas_info with NULL slide"); | |
81 | ||
82 | size = sizeof(uint32_t); | |
83 | err = kas_info(KAS_INFO_KERNEL_TEXT_SLIDE_SELECTOR, &slide, &size); | |
84 | T_ASSERT_POSIX_FAILURE(err, EINVAL, "kas_info with invalid size"); | |
85 | } | |
86 | ||
87 | static char const* | |
88 | kernel_path(void) | |
89 | { | |
90 | static CSSymbolicatorRef symbolicator; | |
91 | static char const* path; | |
92 | static dispatch_once_t once; | |
93 | dispatch_once(&once, ^{ | |
94 | uint32_t flags = kCSSymbolicatorDefaultCreateFlags; | |
95 | symbolicator = CSSymbolicatorCreateWithMachKernelFlagsAndNotification(flags, NULL); | |
96 | T_QUIET; T_ASSERT_TRUE(!CSIsNull(symbolicator), "CSSymbolicatorCreateWithMachKernelFlagsAndNotification"); | |
97 | path = CSSymbolOwnerGetPath(CSSymbolicatorGetAOutSymbolOwner(symbolicator)); | |
98 | if (!path) { | |
99 | path = CSSymbolOwnerGetPath(CSSymbolicatorGetSymbolOwner(symbolicator)); | |
100 | } | |
101 | T_QUIET; T_ASSERT_NOTNULL(path, "CSSymbolOwnerGetPath/CSSymbolicatorGetSymbolOwner"); | |
102 | }); | |
103 | return path; | |
104 | } | |
105 | ||
106 | static void | |
107 | disk_kernel_segments(uint64_t **segs_out, size_t *nsegs_out) | |
108 | { | |
109 | char const* path = kernel_path(); | |
110 | int fd = open(path, O_RDONLY); | |
111 | int err; | |
112 | struct stat sb; | |
113 | size_t nsegs = 0; | |
114 | uint64_t *segs = NULL; | |
115 | void *data; | |
116 | ||
117 | T_LOG("Kernel file is %s", path); | |
118 | T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, "open kernel file"); | |
119 | ||
120 | err = fstat(fd, &sb); | |
121 | T_ASSERT_POSIX_SUCCESS(err, "fstat kernel file"); | |
122 | ||
123 | data = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_SHARED, fd, 0); | |
124 | T_ASSERT_NE(data, MAP_FAILED, "mmap kernel file"); | |
125 | ||
126 | /* | |
127 | * TODO: If we bring back FAT kernel binaries | |
128 | * this will need to be fixed to handle them properly | |
129 | */ | |
130 | uint32_t magic = *(uint32_t*)data; | |
131 | struct load_command *cmd = NULL; | |
132 | ||
133 | switch (magic) { | |
134 | case MH_MAGIC: OS_FALLTHROUGH; | |
135 | case MH_CIGAM: { | |
136 | struct mach_header *mh = (struct mach_header *)data; | |
137 | cmd = (struct load_command *)(&(mh[1])); | |
138 | nsegs = mh->ncmds; | |
139 | } | |
140 | break; | |
141 | case MH_MAGIC_64: OS_FALLTHROUGH; | |
142 | case MH_CIGAM_64: { | |
143 | struct mach_header_64 *mh = (struct mach_header_64 *)data; | |
144 | cmd = (struct load_command *)(&(mh[1])); | |
145 | nsegs = mh->ncmds; | |
146 | } | |
147 | break; | |
148 | default: | |
149 | T_FAIL("kernel file is not a Mach-O file, magic is %x", magic); | |
150 | } | |
151 | ||
152 | /* Adjust for the LC_UUID && LC_BUILD_VERSION commands in front of | |
153 | * load commands for dSYMs | |
154 | */ | |
155 | while (cmd->cmd != LC_SEGMENT && cmd->cmd != LC_SEGMENT_64) { | |
156 | cmd = (struct load_command *) ((uintptr_t) cmd + cmd->cmdsize); | |
157 | nsegs--; | |
158 | } | |
159 | ||
160 | segs = calloc(nsegs, sizeof(*segs)); | |
161 | T_ASSERT_NOTNULL(segs, "calloc disk segment array"); | |
162 | ||
163 | for (uint8_t i = 0; i < nsegs; i++) { | |
164 | if (cmd->cmd == LC_SEGMENT) { | |
165 | struct segment_command *sg = (struct segment_command *) cmd; | |
166 | if (sg->vmsize > 0) { | |
167 | segs[i] = sg->vmaddr; | |
168 | } | |
169 | } else if (cmd->cmd == LC_SEGMENT_64) { | |
170 | struct segment_command_64 *sg = (struct segment_command_64 *) cmd; | |
171 | if (sg->vmsize > 0) { | |
172 | segs[i] = sg->vmaddr; | |
173 | } | |
174 | } | |
175 | cmd = (struct load_command *) ((uintptr_t) cmd + cmd->cmdsize); | |
176 | } | |
177 | ||
178 | *segs_out = segs; | |
179 | *nsegs_out = nsegs; | |
180 | ||
181 | err = munmap(data, (size_t)sb.st_size); | |
182 | ||
183 | err = close(fd); | |
184 | T_ASSERT_POSIX_SUCCESS(err, "close kernel fd"); | |
185 | } | |
186 | ||
187 | static bool | |
188 | is_fileset_kc(void) | |
189 | { | |
190 | char uuid[1024]; | |
191 | int err; | |
192 | size_t size = sizeof(uuid); | |
193 | err = sysctlbyname("kern.filesetuuid", uuid, &size, NULL, 0); | |
194 | return err == 0; | |
195 | } | |
196 | ||
197 | #define KAS_INFO_KERNEL_SEGMENT_LOCATION_SELECTOR 1 | |
198 | ||
199 | T_DECL(kernel_segment_location, | |
200 | "ensures that KAS_INFO_KERNEL_SEGMENT_LOCATION returns correct segment locations") | |
201 | { | |
202 | int err; | |
203 | ||
204 | if (!slide_enabled()) { | |
205 | T_SKIP("KASLR is not enabled"); | |
206 | __builtin_unreachable(); | |
207 | } | |
208 | ||
209 | uint64_t *disk_segs; | |
210 | size_t disk_nsegs; | |
211 | disk_kernel_segments(&disk_segs, &disk_nsegs); | |
212 | ||
213 | size_t size = 0; | |
214 | ||
215 | err = kas_info(KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR, NULL, &size); | |
216 | if (errno == ENOTSUP) { | |
217 | T_SKIP("KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR not supported"); | |
218 | } | |
219 | T_ASSERT_POSIX_SUCCESS(err, "kas_info KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR for size"); | |
220 | ||
221 | uint64_t mem_nsegs = size / sizeof(uint64_t); | |
222 | uint64_t *mem_segs = calloc(mem_nsegs, sizeof(*disk_segs)); | |
223 | ||
224 | err = kas_info(KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR, mem_segs, &size); | |
225 | if (errno == ENOTSUP) { | |
226 | T_SKIP("KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR not supported"); | |
227 | } | |
228 | ||
229 | T_ASSERT_POSIX_SUCCESS(err, "kas_info KAS_INFO_KERNEL_SEGMENT_VMADDR_SELECTOR for data"); | |
230 | ||
231 | T_LOG("Kernel has %zu segments on disk, %zu in memory:", disk_nsegs, mem_nsegs); | |
232 | for (size_t i = 0; i < disk_nsegs; i++) { | |
233 | T_LOG("%zu %llx %llx", i, disk_segs[i], mem_segs[i]); | |
234 | } | |
235 | ||
236 | /* | |
237 | * If the kernel is not a fileset, verify that all | |
238 | * the segments in memory are the segment on disk | |
239 | * + the kaslr slide | |
240 | */ | |
241 | if (!is_fileset_kc()) { | |
242 | T_LOG("Kernelcache is not a fileset kernelcache"); | |
243 | ||
244 | uint64_t slide = kernel_slide(); | |
245 | for (size_t i = 0; i < disk_nsegs; i++) { | |
246 | if (disk_segs[i] == 0 || mem_segs[i] == 0) { | |
247 | continue; | |
248 | } | |
249 | T_ASSERT_EQ(disk_segs[i] + slide, mem_segs[i], "segment %zu is slid", i); | |
250 | } | |
251 | } | |
252 | ||
253 | free(disk_segs); | |
254 | free(mem_segs); | |
255 | } |