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
get_macho_uuid(const char * cwd,const char * path,uuid_t uuid)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 T_META_CHECK_LEAKS(false), T_META_TAG_VM_NOT_PREFERRED)
117 {
118 T_SETUPBEGIN;
119
120 uuid_t kern_uuid;
121 uuid_string_t kern_uuid_str;
122 size_t kern_uuid_size = sizeof(kern_uuid_str);
123 int ret = sysctlbyname("kern.uuid", &kern_uuid_str, &kern_uuid_size, NULL,
124 0);
125 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "should get running kernel UUID");
126 T_LOG("%s: running kernel", kern_uuid_str);
127
128 ret = uuid_parse(kern_uuid_str, kern_uuid);
129 T_QUIET; T_ASSERT_EQ(ret, 0, "should parse kernel UUID into bytes");
130
131 #if TARGET_OS_OSX
132 const char *kernels_path = "/System/Library/Kernels/";
133 #else // TARGET_OS_OSX
134 const char *kernels_path = "/";
135 #endif // !TARGET_OS_OSX
136 T_LOG("searching for kernels at %s", kernels_path);
137
138 ret = chdir(kernels_path);
139 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "should change directory to %s",
140 kernels_path);
141
142 DIR *kernels_dir = opendir(kernels_path);
143 T_QUIET; T_ASSERT_NOTNULL(kernels_dir, "should open directory at %s",
144 kernels_path);
145
146 T_SETUPEND;
147
148 bool found = false;
149 struct dirent *entry = NULL;
150 while ((entry = readdir(kernels_dir)) != NULL) {
151 uuid_t bin_uuid;
152 bool ok = get_macho_uuid(kernels_path, entry->d_name, bin_uuid);
153 if (ok) {
154 uuid_string_t bin_uuid_str;
155 uuid_unparse(bin_uuid, bin_uuid_str);
156 T_LOG("%s: from %s%s", bin_uuid_str, kernels_path, entry->d_name);
157 if (uuid_compare(bin_uuid, kern_uuid) == 0) {
158 found = true;
159 T_PASS("UUID from %s%s matches kernel UUID", kernels_path,
160 entry->d_name);
161 }
162 }
163 }
164 if (!found) {
165 T_FAIL("failed to find kernel binary with UUID of the running kernel, "
166 "wrong kernel is booted");
167 }
168 (void)closedir(kernels_dir);
169 }
170