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 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