scudo: add MTE tests to scudotest

Bug: 231155845
Test: run the tests
Change-Id: Ibebfa2f3d08b639f59a8c1ad9ca7f51d158368c0
diff --git a/lib/scudo/test/include/scudo_app.h b/lib/scudo/test/include/scudo_app.h
index 82a0a10..ee2013a 100644
--- a/lib/scudo/test/include/scudo_app.h
+++ b/lib/scudo/test/include/scudo_app.h
@@ -33,6 +33,13 @@
     SCUDO_REALLOC_TYPE_MISMATCH,
     SCUDO_ALLOC_LARGE,
     SCUDO_ALLOC_BENCHMARK,
+    SCUDO_UNTAGGED_MEMREF,
+    SCUDO_TAGGED_MEMREF,
+    SCUDO_MEMTAG_MISMATCHED_READ,
+    SCUDO_MEMTAG_MISMATCHED_WRITE,
+    SCUDO_MEMTAG_READ_AFTER_FREE,
+    SCUDO_MEMTAG_WRITE_AFTER_FREE,
+    SCUDO_TEST_FAIL,
     SCUDO_BAD_CMD
 };
 
diff --git a/lib/scudo/test/manifest.json b/lib/scudo/test/manifest.json
index 6050c44..c1fcd14 100644
--- a/lib/scudo/test/manifest.json
+++ b/lib/scudo/test/manifest.json
@@ -1,5 +1,6 @@
 {
     "uuid": "df298f5e-7194-4556-9aff-201822110f41",
-    "min_heap": 4096,
-    "min_stack": 4096
+    "min_heap": 16384,
+    "min_stack": 4096,
+    "mgmt_flags": {"non_critical_app": true}
 }
diff --git a/lib/scudo/test/scudo_test.c b/lib/scudo/test/scudo_test.c
index c8766a4..0c43cfe 100644
--- a/lib/scudo/test/scudo_test.c
+++ b/lib/scudo/test/scudo_test.c
@@ -16,27 +16,56 @@
 
 #include <lib/tipc/tipc.h>
 #include <lib/unittest/unittest.h>
+#include <stdlib.h>
+#include <sys/auxv.h>
+#include <trusty/memref.h>
 #include <trusty_unittest.h>
 #include <uapi/err.h>
+#include <uapi/mm.h>
 
 #include <scudo_app.h>
 #include <scudo_consts.h>
 
 #define TLOG_TAG "scudo_test"
 
+#ifndef HWCAP2_MTE
+#define HWCAP2_MTE (1 << 18)
+#endif
+
+#define PAGE_SIZE getauxval(AT_PAGESZ)
+
+int send_memref_msg(handle_t chan,
+                    const void* buf,
+                    size_t len,
+                    handle_t memref) {
+    struct iovec iov = {
+            .iov_base = (void*)buf,
+            .iov_len = len,
+    };
+    ipc_msg_t msg = {
+            .iov = &iov,
+            .num_iov = 1,
+            .handles = memref < 0 ? NULL : &memref,
+            .num_handles = memref < 0 ? 0 : 1,
+    };
+    return send_msg(chan, &msg);
+}
+
 /*
  * Sends command to app and then waits for a
  * reply or channel close. In the non-crashing case, the server
  * should echo back the original command and scudo_srv_rpc returns
  * NO_ERROR.
  */
-static int scudo_srv_rpc(handle_t chan, enum scudo_command cmd) {
+static int scudo_srv_rpc_memref(handle_t chan,
+                                enum scudo_command cmd,
+                                int memref) {
     int ret;
     struct scudo_msg msg = {
             .cmd = cmd,
     };
 
-    ret = tipc_send1(chan, &msg, sizeof(msg));
+    ret = send_memref_msg(chan, &msg, sizeof(msg), memref);
     ASSERT_GE(ret, 0);
     ASSERT_EQ(ret, sizeof(msg));
 
@@ -58,19 +87,28 @@
         return ret;
     }
     ASSERT_EQ(ret, sizeof(msg));
-    ASSERT_EQ(msg.cmd, cmd);
-
-    return NO_ERROR;
+    if (msg.cmd == cmd) {
+        return NO_ERROR;
+    }
+    return msg.cmd;
 
 test_abort:
     /* Use ERR_IO to indicate internal error with the test app */
     return ERR_IO;
 }
 
+static int scudo_srv_rpc(handle_t chan, enum scudo_command cmd) {
+    return scudo_srv_rpc_memref(chan, cmd, -1);
+}
+
 typedef struct scudo_info {
     handle_t chan;
 } scudo_info_t;
 
+static bool has_mte() {
+    return getauxval(AT_HWCAP2) & HWCAP2_MTE;
+}
+
 TEST_F_SETUP(scudo_info) {
     _state->chan = INVALID_IPC_HANDLE;
     ASSERT_EQ(tipc_connect(&_state->chan, SCUDO_TEST_SRV_PORT), 0);
@@ -138,6 +176,85 @@
     EXPECT_EQ(scudo_srv_rpc(_state->chan, SCUDO_ALLOC_LARGE), NO_ERROR);
 }
 
+TEST_F(scudo_info, mte_tagged_memref) {
+    if (!has_mte()) {
+        trusty_unittest_printf("[  SKIPPED ] MTE is not available\n");
+        return;
+    }
+    int ref = -1;
+    void* mem = memalign(PAGE_SIZE, PAGE_SIZE);
+    ASSERT_NE(mem, NULL);
+    memset(mem, 0x33, PAGE_SIZE);
+    ref = memref_create(
+            mem, PAGE_SIZE,
+            MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE | MMAP_FLAG_PROT_MTE);
+    ASSERT_GT(ref, 0);
+    printf("created memref %d for %p\n", ref, mem);
+    int rc = scudo_srv_rpc_memref(_state->chan, SCUDO_TAGGED_MEMREF, ref);
+    EXPECT_EQ(rc, NO_ERROR);
+    EXPECT_EQ(*((volatile char*)mem), 0x77);
+test_abort:;
+    close(ref);
+    free(mem);
+}
+
+TEST_F(scudo_info, mte_untagged_memref) {
+    if (!has_mte()) {
+        trusty_unittest_printf("[  SKIPPED ] MTE is not available\n");
+        return;
+    }
+    int ref = -1;
+    void* mem = memalign(PAGE_SIZE, PAGE_SIZE);
+    ASSERT_NE(mem, NULL);
+    memset(mem, 0x33, PAGE_SIZE);
+    ref = memref_create(mem, PAGE_SIZE,
+                        MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE);
+    ASSERT_GT(ref, 0);
+    printf("created memref %d for %p\n", ref, mem);
+    int rc = scudo_srv_rpc_memref(_state->chan, SCUDO_UNTAGGED_MEMREF, ref);
+    EXPECT_EQ(rc, NO_ERROR);
+    EXPECT_EQ(*((volatile char*)mem), 0x77);
+test_abort:;
+    close(ref);
+    free(mem);
+}
+
+TEST_F(scudo_info, mte_mismatched_tag_read) {
+    if (!has_mte()) {
+        trusty_unittest_printf("[  SKIPPED ] MTE is not available\n");
+        return;
+    }
+    EXPECT_EQ(scudo_srv_rpc(_state->chan, SCUDO_MEMTAG_MISMATCHED_READ),
+              ERR_CHANNEL_CLOSED);
+}
+
+TEST_F(scudo_info, mte_mismatched_tag_write) {
+    if (!has_mte()) {
+        trusty_unittest_printf("[  SKIPPED ] MTE is not available\n");
+        return;
+    }
+    EXPECT_EQ(scudo_srv_rpc(_state->chan, SCUDO_MEMTAG_MISMATCHED_WRITE),
+              ERR_CHANNEL_CLOSED);
+}
+
+TEST_F(scudo_info, mte_memtag_read_after_free) {
+    if (!has_mte()) {
+        trusty_unittest_printf("[  SKIPPED ] MTE is not available\n");
+        return;
+    }
+    EXPECT_EQ(scudo_srv_rpc(_state->chan, SCUDO_MEMTAG_READ_AFTER_FREE),
+              ERR_CHANNEL_CLOSED);
+}
+
+TEST_F(scudo_info, mte_memtag_write_after_free) {
+    if (!has_mte()) {
+        trusty_unittest_printf("[  SKIPPED ] MTE is not available\n");
+        return;
+    }
+    EXPECT_EQ(scudo_srv_rpc(_state->chan, SCUDO_MEMTAG_WRITE_AFTER_FREE),
+              ERR_CHANNEL_CLOSED);
+}
+
 TEST_F(scudo_info, alloc_benchmark) {
     EXPECT_EQ(scudo_srv_rpc(_state->chan, SCUDO_ALLOC_BENCHMARK), NO_ERROR);
 }
diff --git a/lib/scudo/test/srv/scudo_app.cpp b/lib/scudo/test/srv/scudo_app.cpp
index dc6edac..1fd1cdb 100644
--- a/lib/scudo/test/srv/scudo_app.cpp
+++ b/lib/scudo/test/srv/scudo_app.cpp
@@ -22,8 +22,11 @@
 #include <lk/err_ptr.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
+#include <trusty/memref.h>
 #include <trusty_log.h>
 #include <uapi/err.h>
+#include <uapi/mm.h>
 
 #include <scudo_app.h>
 #include <scudo_consts.h>
@@ -82,12 +85,56 @@
     TLOG("arr = %s\n", arr);
 }
 
+static void* retagged(void* taggedptr) {
+    uint64_t tagged = reinterpret_cast<uint64_t>(taggedptr);
+    uint64_t tag = tagged & 0x0f00000000000000;
+    uint64_t untagged = tagged & 0x00ffffffffffffff;
+    uint64_t newtag = (tag + 0x0100000000000000) & 0x0f00000000000000;
+    ;
+    return reinterpret_cast<void*>(newtag | untagged);
+}
+
+int recv_memref_msg(handle_t chan,
+                    size_t min_sz,
+                    void* buf,
+                    size_t buf_sz,
+                    int* memref) {
+    int rc;
+    ipc_msg_info_t msg_inf;
+
+    rc = get_msg(chan, &msg_inf);
+    if (rc)
+        return rc;
+
+    if (msg_inf.len < min_sz || msg_inf.len > buf_sz ||
+        msg_inf.num_handles > 1) {
+        /* unexpected msg size: buffer too small or too big */
+        rc = ERR_BAD_LEN;
+    } else {
+        struct iovec iov = {
+                .iov_base = buf,
+                .iov_len = buf_sz,
+        };
+        ipc_msg_t msg = {
+                .num_iov = 1,
+                .iov = &iov,
+                .num_handles = msg_inf.num_handles,
+                .handles = msg_inf.num_handles ? memref : NULL,
+        };
+        rc = read_msg(chan, msg_inf.id, 0, &msg);
+    }
+
+    put_msg(chan, msg_inf.id);
+    return rc;
+}
+
 static int scudo_on_message(const struct tipc_port* port,
                             handle_t chan,
                             void* ctx) {
     struct scudo_msg msg;
+    int memref = -1;
 
-    int ret = tipc_recv1(chan, sizeof(msg), &msg, sizeof(msg));
+    int ret = recv_memref_msg(chan, sizeof(msg), &msg, sizeof(msg), &memref);
     if (ret < 0 || ret != sizeof(msg)) {
         TLOGE("Failed to receive message (%d)\n", ret);
         return ret;
@@ -247,6 +294,99 @@
         break;
     }
 
+    case SCUDO_TAGGED_MEMREF: {
+        TLOGI("tagged memref (%d)\n", memref);
+        volatile char* mapped = (volatile char*)mmap(
+                0, 4096,
+                MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE | MMAP_FLAG_PROT_MTE,
+                0, memref, 0);
+        if (mapped != MAP_FAILED) {
+            TLOGI("Tagged memref should have failed\n");
+            msg.cmd = SCUDO_TEST_FAIL;
+            close(memref);
+            break;
+        }
+        mapped = (volatile char*)mmap(
+                0, 4096, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE, 0, memref,
+                0);
+        if (mapped == MAP_FAILED) {
+            TLOGI("Untagged mapping failed\n");
+            msg.cmd = SCUDO_TEST_FAIL;
+            close(memref);
+            break;
+        }
+        *mapped = 0x77;
+        close(memref);
+        break;
+    }
+
+    case SCUDO_UNTAGGED_MEMREF: {
+        TLOGI("untagged memref (%d)\n", memref);
+        volatile char* mapped = (volatile char*)mmap(
+                0, 4096, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE, 0, memref,
+                0);
+
+        if (!mapped || *mapped != 0x33) {
+            TLOGI("no map or bad data in memref %p: %0x\n", mapped,
+                  mapped ? *mapped : 0);
+            msg.cmd = SCUDO_TEST_FAIL;
+            close(memref);
+            break;
+        }
+        *mapped = 0x77;
+        close(memref);
+        break;
+    }
+
+    case SCUDO_MEMTAG_MISMATCHED_READ: {
+        void* mem = malloc(64);
+        char* arr = reinterpret_cast<char*>(mem);
+        *arr = 0x33;
+        volatile char* retagged_arr =
+                3 + reinterpret_cast<char*>(retagged(mem));
+        TLOGI("mismatched tag read %016lx %016lx\n", (uint64_t)arr,
+              (uint64_t)retagged_arr);
+        *arr = *retagged_arr;
+        TLOGI("should not be here\n");
+        free(mem);
+        break;
+    }
+
+    case SCUDO_MEMTAG_MISMATCHED_WRITE: {
+        void* mem = malloc(64);
+        char* arr = reinterpret_cast<char*>(mem);
+        *arr = 0x44;
+        volatile char* retagged_arr = reinterpret_cast<char*>(retagged(mem));
+        TLOGI("mismatched tag write %016lx %016lx\n", (uint64_t)arr,
+              (uint64_t)retagged_arr);
+        *retagged_arr = *arr;
+        TLOGI("should not be here\n");
+        free(mem);
+        break;
+    }
+
+    case SCUDO_MEMTAG_READ_AFTER_FREE: {
+        void* mem = malloc(64);
+        memset(mem, 64, 0xaa);
+        char* arr = reinterpret_cast<char*>(mem);
+        free(mem);
+        TLOGI("read after free %016lx\n", (uint64_t)arr);
+        touch(arr);  // this reads before writing
+        TLOGI("should not be here\n");
+        break;
+    }
+
+    case SCUDO_MEMTAG_WRITE_AFTER_FREE: {
+        void* mem = malloc(64);
+        memset(mem, 64, 0xbb);
+        char* arr = reinterpret_cast<char*>(mem);
+        free(mem);
+        TLOGI("write after free %016lx\n", (uint64_t)arr);
+        *arr = 1;
+        TLOGI("should not be here\n");
+        break;
+    }
+
     case SCUDO_ALLOC_BENCHMARK: {
         TLOGI("alloc benchmark\n");
         char* arr = reinterpret_cast<char*>(malloc(1500000));