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