xref: /xnu-8792.41.9/tests/kas_info.c (revision 5c2921b07a2480ab43ec66f5b9e41cb872bc554f)
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(void)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*
kernel_path(void)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
disk_kernel_segments(uint64_t ** segs_out,size_t * nsegs_out)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
is_fileset_kc(void)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 }
256