Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | // // printer_tests.m // libmalloc // // Tests for the xzone malloc json printer // #include <darwintest.h> #include <darwintest_utils.h> #include <../src/internal.h> #if CONFIG_XZONE_MALLOC && (MALLOC_TARGET_IOS_ONLY || TARGET_OS_OSX) #include <Foundation/Foundation.h> #include <Foundation/NSJSONSerialization.h> static char *print_buffer = NULL; static size_t print_buffer_capacity = 0; static size_t print_buffer_index = 0; static void reset_print_buffer(void) { T_ASSERT_NULL(print_buffer, "reset_print_buffer called multiple times"); print_buffer_index = 0; print_buffer_capacity = vm_page_size; print_buffer = malloc(print_buffer_capacity); T_ASSERT_NOTNULL(print_buffer, "Allocate print buffer"); } static void resize_print_buffer(void) { T_ASSERT_NOTNULL(print_buffer, "Must call reset_print_buffer first"); vm_address_t addr = 0; size_t new_capacity = print_buffer_capacity * 2; print_buffer = realloc(print_buffer, new_capacity); T_ASSERT_NOTNULL(print_buffer, "Realloc print buffer"); print_buffer_capacity = new_capacity; } static void append_to_buffer(const char *data, size_t len) { while (1) { if (len >= print_buffer_capacity - print_buffer_index) { resize_print_buffer(); } else { memcpy(&print_buffer[print_buffer_index], data, len); print_buffer_index += len; break; } } } static void free_print_buffer(void) { free(print_buffer); print_buffer = NULL; print_buffer_capacity = 0; print_buffer_index = 0; } // Calls "heap -p *procname" and returns an array of json objects, one each for // the xzm zones in the remote process. If the process doesn't use xzm, the // array will be empty. If the process doesn't exist, this function either calls // T_FAIL or T_SKIP, based on skip_if_not_found. If multiple processes with name // procname exist, this function will call T_FAIL. static NSArray * get_process_json(const char *procname, bool skip_if_not_found) { reset_print_buffer(); __block bool not_found = false; // Dump heap's stderr to our stdout, for help debugging test failures. Also // monitor for a magic string indicating that the requested process doesn't // exist, to handle skip_if_not_found dt_pipe_data_handler_t stderr_handler = ^bool(char *data, __unused size_t data_size, __unused dt_pipe_data_handler_context_t *context) { T_LOG("heap stderr: %s", data); const char *not_found_needle = "heap cannot find any existing process"; if (strstr(data, not_found_needle)) { not_found = true; T_LOG("Process does not exist"); return true; } return false; }; // returning true will stop executing handler. dt_pipe_data_handler_t stdout_handler = ^bool(char *data, __unused size_t data_size, __unused dt_pipe_data_handler_context_t *context) { T_LOG("heap output: %s", data); append_to_buffer(data, data_size); return false; }; const char *argv[] = { "/usr/bin/heap", "-p", procname, NULL }; pid_t pid = dt_launch_tool_pipe(argv, false, NULL, stdout_handler, stderr_handler, BUFFER_PATTERN_LINE, NULL); int exit_status; int signal; int timeout = 30; // 30 second timeout bool wait = dt_waitpid(pid, &exit_status, &signal, timeout); if (!wait && skip_if_not_found && not_found) { // Failed exit - should only occur when the tools couldn't find a // process by name T_SKIP("Skipping since %s doesn't exist", procname); } T_ASSERT_TRUE(wait, "heap exited successfully, status = %d, signal = %d", exit_status, signal); T_ASSERT_POSIX_ZERO(exit_status, "Exit status is success"); T_ASSERT_POSIX_ZERO(signal, "Exit signal is success"); // We don't have a great way to know that we've processed all of heap's // stderr/stdout, so use a fixed sleep to (hopefully) let those finish sleep(1); NSMutableArray *retval = [NSMutableArray arrayWithCapacity:1]; char *heap_output = &print_buffer[0]; size_t heap_len = print_buffer_index; while (heap_output) { const char *start_symbol = "Begin xzone malloc JSON:\n"; const char *end_symbol = "End xzone malloc JSON\n"; char *json_start = strnstr(heap_output, start_symbol, heap_len); if (!json_start) { // No more json to parse break; } json_start += strlen(start_symbol); char *json_end = strnstr(heap_output, end_symbol, heap_len); T_ASSERT_GE(json_end, json_start, "Incorrect end token"); NSData *json_data = [NSData dataWithBytes:json_start length:(json_end - json_start)]; NSError *e = nil; NSDictionary *json_dict = [NSJSONSerialization JSONObjectWithData:json_data options:0 error:&e]; T_ASSERT_NE(json_dict, nil, "Parsed json, error = %s", [[e localizedDescription] UTF8String]); [retval addObject:json_dict]; char *new_heap_output = json_end + strlen(end_symbol); heap_len -= new_heap_output - heap_output; heap_output = new_heap_output; } free_print_buffer(); return retval; } static void security_critical_process_checks(NSArray *json_array) { // A security critical process should have at least one xzone malloc zone, // and should have guard pages enabled T_ASSERT_GE(json_array.count, 1, "At least one zone is xzm"); NSDictionary *guard_config = json_array[0][@"guard_config"]; T_ASSERT_NE(guard_config, nil, "Guard config dictionary in output"); T_ASSERT_TRUE([guard_config[@"guards_enabled"] boolValue], "Guards enabled in launchd"); } T_DECL(xzone_enabled_launchd, "Check that xzone malloc is enabled in launchd", T_META_ENABLED(!TARGET_CPU_X86_64), T_META_TAG_VM_NOT_ELIGIBLE, T_META_ASROOT(true)) { security_critical_process_checks(get_process_json("1", false)); } T_DECL(xzone_enabled_logd, "Check that xzone malloc is enabled in logd", T_META_ENABLED(!TARGET_CPU_X86_64), T_META_TAG_VM_NOT_ELIGIBLE, T_META_ASROOT(true)) { security_critical_process_checks(get_process_json("logd", false)); } T_DECL(xzone_enabled_notifyd, "Check that xzone malloc is enabled in notifyd", T_META_ENABLED(!TARGET_CPU_X86_64), T_META_TAG_VM_NOT_ELIGIBLE, T_META_ASROOT(true)) { security_critical_process_checks(get_process_json("notifyd", false)); } #else // CONFIG_XZONE_MALLOC && (MALLOC_TARGET_IOS || TARGET_OS_OSX) T_DECL(skip_json_printer_tests, "Skip printer tests") { T_SKIP("Nothing to test without xzone malloc on ios/macos"); } #endif // CONFIG_XZONE_MALLOC && (MALLOC_TARGET_IOS_ONLY || TARGET_OS_OSX) |