1 #include <darwintest.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <sys/types.h>
5 #include <sys/event.h>
6 #include <sys/time.h>
7 #include <sys/sysctl.h>
8 #include <sys/resource.h>
9 #include <signal.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <TargetConditionals.h>
14 #include <unistd.h>
15 #include <dirent.h>
16 #include <sys/stat.h>
17 #include <sys/mman.h>
18 #include <mach/mach.h>
19 #include <mach/mach_vm.h>
20
21 #include <arm_acle.h>
22
23 #define BUFFLEN 2048
24 #define TIMEOUT 10 /* Timeout in seconds to wait for coredumps to appear */
25
26 #define VM_FLAGS_MTE 0x00002000
27
28 #define BUFFER_SZ_4MB (1024 * 1024 * 4)
29
30 T_GLOBAL_META(
31 T_META_NAMESPACE("xnu.arm"),
32 T_META_RADAR_COMPONENT_NAME("xnu"),
33 T_META_RADAR_COMPONENT_VERSION("crash tools"));
34
35
36 // Mach-O parsing utilities :
37
38 struct mach_header_64 {
39 uint32_t magic; /* mach magic number identifier */
40 uint32_t cputype; /* cpu specifier */
41 uint32_t cpusubtype; /* machine specifier */
42 uint32_t filetype; /* type of file */
43 uint32_t ncmds; /* number of load commands */
44 uint32_t sizeofcmds; /* the size of all the load commands */
45 uint32_t flags; /* flags */
46 uint32_t reserved; /* reserved */
47 };
48
49 struct load_command {
50 uint32_t cmd; /* type of load command */
51 uint32_t cmdsize; /* total size of command in bytes */
52 };
53
54 #define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be mapped */
55
56 struct segment_command_64 { /* for 64-bit architectures */
57 uint32_t cmd; /* LC_SEGMENT_64 */
58 uint32_t cmdsize; /* includes sizeof section_64 structs */
59 char segname[16]; /* segment name */
60 uint64_t vmaddr; /* memory address of this segment */
61 uint64_t vmsize; /* memory size of this segment */
62 uint64_t fileoff; /* file offset of this segment */
63 uint64_t filesize; /* amount to map from the file */
64 uint32_t maxprot; /* maximum VM protection */
65 uint32_t initprot; /* initial VM protection */
66 uint32_t nsects; /* number of sections in segment */
67 uint32_t flags; /* flags */
68 };
69
70 typedef struct mach_header_64 mach_header_t;
71 typedef struct segment_command_64 segment_command_t;
72 #define LC_SEGMENT_CMD LC_SEGMENT_64
73 typedef struct load_command load_command_t;
74
75 #define FOREACH_SEGMENT_COMMAND(_header, _segment) \
76 for (const segment_command_t *seg_indx = NULL, \
77 *_segment = (const segment_command_t *)((uintptr_t)(_header + 1)); \
78 seg_indx < (const segment_command_t *)(NULL) + (_header)->ncmds; \
79 ++seg_indx, _segment = (const segment_command_t *)((uintptr_t)_segment + _segment->cmdsize))
80
81 const segment_command_t * _Nullable
macho_get_next_segment(const mach_header_t * _Nonnull mh,const segment_command_t * _Nullable seg)82 macho_get_next_segment(const mach_header_t * _Nonnull mh, const segment_command_t * _Nullable seg)
83 {
84 FOREACH_SEGMENT_COMMAND(mh, nextseg) {
85 if (nextseg->cmd != LC_SEGMENT_CMD) {
86 continue;
87 }
88 if (seg == NULL) {
89 return nextseg;
90 }
91 if (seg == nextseg) {
92 seg = NULL;
93 }
94 }
95 return NULL;
96 }
97
98 static const char corefile_ctl[] = "kern.corefile";
99 static const char coredump_ctl[] = "kern.coredump";
100 /* The directory where coredumps will be */
101 static const char dump_dir[] = "/cores";
102 /* The coredump location when we set kern.coredump ctl to something valid */
103 static const char valid_dump_fmt[] = "/cores/test-core.%d";
104 static const char ls_path[] = "/bin/ls";
105
106 /* A valid coredump location to test. */
107 static char valid_dump_loc[] = "/cores/test-core.%P";
108
109 static const struct rlimit lim_infty = {
110 RLIM_INFINITY,
111 RLIM_INFINITY
112 };
113
114 static volatile int stop_looking = 0;
115
116 static const struct timespec timeout = {
117 TIMEOUT,
118 0
119 };
120
121 static void
sigalrm_handler(int sig)122 sigalrm_handler(int sig)
123 {
124 (void)sig;
125 stop_looking = 1;
126 return;
127 }
128
129 static void
list_coredump_files()130 list_coredump_files()
131 {
132 int ret;
133 char buf[BUFFLEN] = { 0 };
134
135 T_LOG("Contents of %s:", dump_dir);
136 snprintf(buf, BUFFLEN, "%s %s", ls_path, dump_dir);
137 ret = system(buf);
138 T_ASSERT_POSIX_SUCCESS(ret, "Listing contents of cores directory");
139 return;
140 }
141
142 static int
fork_and_wait_for_segfault()143 fork_and_wait_for_segfault()
144 {
145 int pid, ret;
146 int stat;
147 pid = fork();
148 if (pid == 0) {
149 unsigned int *ptr = (unsigned int *)0x30; /* Cause a segfault so that we get a coredump */
150 *ptr = 0xdeadd00d;
151 exit(0);
152 }
153 T_ASSERT_TRUE(pid != -1, "Checking fork success in parent");
154
155 ret = wait(&stat);
156 T_ASSERT_TRUE(ret != -1, "Waited for child to segfault and dump core");
157 T_ASSERT_FALSE(WIFEXITED(stat), "Seems that child process did not fail to execute");
158 return pid;
159 }
160
161 static int
setup_coredump_kevent(struct kevent * kev,int dir)162 setup_coredump_kevent(struct kevent *kev, int dir)
163 {
164 int ret;
165 int kqfd;
166
167 EV_SET(kev, dir, EVFILT_VNODE, EV_ADD, NOTE_WRITE, 0, NULL);
168 kqfd = kqueue();
169 T_ASSERT_POSIX_SUCCESS(kqfd, "kqueue: get kqueue for coredump monitoring");
170
171 ret = kevent(kqfd, kev, 1, NULL, 0, NULL);
172 T_ASSERT_POSIX_SUCCESS(ret, "kevent: setup directory monitoring for coredump");
173 return kqfd;
174 }
175
176 static bool
check_coredump_contains_vm_addr(const char * path,vm_address_t vm_addr,size_t vm_size)177 check_coredump_contains_vm_addr(const char *path, vm_address_t vm_addr, size_t vm_size)
178 {
179 int err;
180 struct stat filestat;
181 int fd = open(path, O_RDONLY);
182 T_ASSERT_GE(fd, 0, "Failed to open file %s\n", path);
183 err = fstat(fd, &filestat);
184 T_ASSERT_POSIX_SUCCESS(err, "Failed to open the corefile to check vm region");
185
186 T_WITH_ERRNO;
187 const mach_header_t *macho = (const mach_header_t *) mmap(NULL, filestat.st_size, PROT_READ, MAP_SHARED, fd, 0);
188 T_ASSERT_NE(macho, MAP_FAILED, "Failed to mmap corefile\n");
189
190 const segment_command_t * seg = NULL;
191
192 while (vm_size > 0 && NULL != (seg = macho_get_next_segment(macho, seg))) {
193 vm_address_t curr_end = seg->vmaddr + seg->vmsize;
194 /* if vm_addr is included in the segment : */
195 if ((vm_addr >= seg->vmaddr) && (vm_addr < curr_end)) {
196 size_t seg_shift = vm_addr - seg->vmaddr;
197 T_ASSERT_GE(
198 (unsigned long long)seg->filesize,
199 (unsigned long long)sizeof(unsigned long long) + seg_shift,
200 "We expect corefile to contain an unsigned long long at least");
201 unsigned long long *ptr = (unsigned long long*)((uintptr_t)seg->fileoff + (uintptr_t)macho + seg_shift);
202 T_ASSERT_EQ(*ptr, (unsigned long long)0xbadc0ffee, "Corefile missing secret value");
203 size_t curr_seg_tail = curr_end - vm_addr;
204 size_t sub_size = MIN(curr_seg_tail, vm_size);
205 vm_addr += sub_size;
206 vm_size -= sub_size;
207 }
208 }
209 return vm_size == 0;
210 }
211
212 static void
look_for_coredump_content(const char * format,int pid,int kqfd,struct kevent * kev,vm_address_t vm_addr,size_t vm_size)213 look_for_coredump_content(const char *format, int pid, int kqfd, struct kevent *kev, vm_address_t vm_addr, size_t vm_size)
214 {
215 int ret = 0;
216 int i = 0;
217 char buf[BUFFLEN];
218 memset(buf, 0, BUFFLEN);
219 snprintf(buf, BUFFLEN, format, pid);
220 /*
221 * Something else might touch this directory. If we get notified and don't see
222 * anything, try a few more times before failing.
223 */
224 alarm(TIMEOUT);
225 while (!stop_looking) {
226 /* Wait for kevent to tell us the coredump folder was modified */
227 ret = kevent(kqfd, NULL, 0, kev, 1, &timeout);
228 T_ASSERT_POSIX_SUCCESS(ret, "kevent: Waiting for coredump to appear");
229 ret = -1;
230 int fd = open(buf, O_RDONLY);
231 if (fd > 0) {
232 // found the file, stop looking
233 ret = 0;
234 close(fd);
235 break;
236 }
237
238 T_LOG("Couldn't find coredump file (try #%d).", i + 1);
239 i++;
240 }
241 alarm(0);
242
243 if (ret == -1) {
244 /* Couldn't find the coredump -- list contents of /cores */
245 list_coredump_files();
246 } else if (ret == 0) {
247 bool vm_reg_contained = check_coredump_contains_vm_addr(buf, vm_addr, vm_size);
248 T_ASSERT_EQ(vm_reg_contained, true, "Corefile %s doesn't have requested memory region", buf);
249 ret = remove(buf);
250 }
251
252 T_ASSERT_POSIX_SUCCESS(ret, "Removing coredump file (should be at %s)", buf);
253 }
254
255 static void
sysctl_enable_coredumps(void)256 sysctl_enable_coredumps(void)
257 {
258 int ret;
259 int enable_core_dump = 1;
260 size_t oldlen = BUFFLEN;
261 char buf[BUFFLEN];
262 memset(buf, 0, BUFFLEN);
263
264 ret = sysctlbyname(coredump_ctl, buf, &oldlen, &enable_core_dump, sizeof(int));
265 T_ASSERT_POSIX_SUCCESS(ret, "sysctl: enable core dumps");
266
267 ret = setrlimit(RLIMIT_CORE, &lim_infty);
268 T_ASSERT_POSIX_SUCCESS(ret, "setrlimit: remove limit on maximum coredump size");
269 }
270
271 T_DECL(
272 proc_core_name_mte,
273 "Tests behavior of core dump when process has MTE hard mode enabled and MTE mapping with active tags",
274 T_META_ASROOT(true),
275 T_META_IGNORECRASHES("proc_core_name_mte.*"),
276 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE", 1),
277 #if TARGET_OS_OSX
278 T_META_ENABLED(true)
279 #else
280 T_META_ENABLED(false)
281 #endif
282 )
283 {
284 DIR *dirp;
285 int ret, pid, dir;
286 char buf[BUFFLEN];
287 memset(buf, 0, BUFFLEN);
288 size_t oldlen = BUFFLEN;
289 struct kevent kev;
290 sig_t sig;
291 int kqfd;
292
293 sig = signal(SIGALRM, sigalrm_handler);
294 T_WITH_ERRNO; T_EXPECT_NE(sig, SIG_ERR, "signal: set sigalrm handler");
295
296 dirp = opendir(dump_dir);
297 T_ASSERT_NOTNULL(dirp, "opendir: opening coredump directory");
298 dir = dirfd(dirp);
299 T_ASSERT_POSIX_SUCCESS(dir, "dirfd: getting file descriptor for coredump directory");
300 kqfd = setup_coredump_kevent(&kev, dir);
301
302 sysctl_enable_coredumps();
303 vm_address_t vm_addr;
304 kern_return_t kret = vm_allocate(mach_task_self(), &vm_addr, BUFFER_SZ_4MB,
305 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
306
307 T_ASSERT_EQ(kret, 0, "vm_allocate failed to allocate MTE buffer");
308 *(unsigned long long *)vm_addr = 0xbadc0ffee;
309
310 uint8_t *tag_addr = __arm_mte_create_random_tag((void*)vm_addr, 0xffff);
311 uint8_t *tag_addr_next = __arm_mte_increment_tag((void*)vm_addr + 16, 1);
312 __arm_mte_set_tag(tag_addr);
313 __arm_mte_set_tag(tag_addr_next);
314
315 printf("New tagged addresses %p : %p\n", tag_addr, tag_addr_next);
316
317 ret = sysctlbyname(corefile_ctl, buf, &oldlen, valid_dump_loc, strlen(valid_dump_loc));
318 T_ASSERT_POSIX_SUCCESS(ret, "sysctl: set valid core dump location, old value was %s", buf);
319 memset(buf, 0, BUFFLEN);
320
321 pid = fork_and_wait_for_segfault();
322 look_for_coredump_content(valid_dump_fmt, pid, kqfd, &kev, vm_addr, BUFFER_SZ_4MB);
323
324 vm_deallocate(mach_task_self(), vm_addr, BUFFER_SZ_4MB);
325 closedir(dirp);
326 close(kqfd);
327 T_PASS("proc_core_name_mte PASSED");
328 }
329