Loading...
--- /dev/null
+++ libmalloc/libmalloc-646.0.13/tests/printer_tests.m
@@ -0,0 +1,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)