Add memory poisoning framework

While an area of memory is poisoned, reading or writing from it triggers a
sanitizer violation.

Implemented for ASan.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/programs/test/metatest.c b/programs/test/metatest.c
index b8dffa9..6ab240c 100644
--- a/programs/test/metatest.c
+++ b/programs/test/metatest.c
@@ -32,6 +32,7 @@
 #include <mbedtls/platform_util.h>
 #include "test/helpers.h"
 #include "test/macros.h"
+#include "test/memory.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -143,6 +144,45 @@
     /* Leak of a heap object */
 }
 
+/* name = "test_memory_poison_%(start)_%(offset)_%(count)"
+ * Poison a region starting at start from an 8-byte aligned origin,
+ * encompassing count bytes. Access the region at offset from the start.
+ */
+void test_memory_poison(const char *name)
+{
+    size_t start = 0, offset = 0, count = 0;
+    if (sscanf(name, "%*[^0-9]%zu%*[^0-9]%zu%*[^0-9]%zu",
+               &start, &offset, &count) != 3) {
+        mbedtls_fprintf(stderr, "%s: Bad name format: %s\n", __func__, name);
+        return;
+    }
+
+    union {
+        long long ll;
+        unsigned char buf[32];
+    } aligned;
+    memset(aligned.buf, 'a', sizeof(aligned.buf));
+
+    if (start > sizeof(aligned.buf)) {
+        mbedtls_fprintf(stderr, "%s: start=%zu > size=%zu", __func__,
+                        start, sizeof(aligned.buf));
+        return;
+    }
+    if (start + count > sizeof(aligned.buf)) {
+        mbedtls_fprintf(stderr, "%s: start+count=%zu > size=%zu", __func__,
+                        start + count, sizeof(aligned.buf));
+        return;
+    }
+    if (offset >= count) {
+        mbedtls_fprintf(stderr, "%s: offset=%zu >= count=%zu", __func__,
+                        offset, count);
+        return;
+    }
+
+    MBEDTLS_TEST_MEMORY_POISON(aligned.buf + start, count);
+    mbedtls_printf("%u\n", (unsigned) aligned.buf[start + offset]);
+}
+
 
 /****************************************************************/
 /* Threading */
@@ -291,6 +331,7 @@
     { "double_free", "asan", double_free },
     { "read_uninitialized_stack", "msan", read_uninitialized_stack },
     { "memory_leak", "asan", memory_leak },
+    { "test_memory_poison_0_0_8", "asan", test_memory_poison },
     { "mutex_lock_not_initialized", "pthread", mutex_lock_not_initialized },
     { "mutex_unlock_not_initialized", "pthread", mutex_unlock_not_initialized },
     { "mutex_free_not_initialized", "pthread", mutex_free_not_initialized },
diff --git a/tests/include/test/memory.h b/tests/include/test/memory.h
index ef05112..27baf7a 100644
--- a/tests/include/test/memory.h
+++ b/tests/include/test/memory.h
@@ -15,4 +15,79 @@
 #include "mbedtls/build_info.h"
 #include "mbedtls/platform.h"
 
+/** \def MBEDTLS_TEST_MEMORY_CAN_POISON
+ *
+ * This macro is defined if the tests are compiled with a method to mark
+ * memory as poisoned, which can be used to enforce some memory access
+ * policies.
+ *
+ * Currently, only Asan (Address Sanitizer) is supported.
+ */
+#if defined(__SANITIZE_ADDRESS__)
+#  define MBEDTLS_TEST_HAVE_ASAN
+#endif
+#if defined(__has_feature)
+#  if __has_feature(address_sanitizer)
+#    define MBEDTLS_TEST_HAVE_ASAN
+#  endif
+#endif
+#if defined(MBEDTLS_TEST_HAVE_ASAN)
+#  define MBEDTLS_TEST_MEMORY_CAN_POISON
+#endif
+
+/** \def MBEDTLS_TEST_MEMORY_POISON(buf, size)
+ *
+ * Poison a memory area so that any attempt to read or write from it will
+ * cause a runtime failure.
+ *
+ * The behavior is undefined if any part of the memory area is invalid.
+ *
+ * This is a no-op in builds without a poisoning method.
+ * See #MBEDTLS_TEST_MEMORY_CAN_POISON.
+ *
+ * \param buf   Pointer to the beginning of the memory area to poison.
+ * \param size  Size of the memory area in bytes.
+ */
+
+/** \def MBEDTLS_TEST_MEMORY_UNPOISON(buf, size)
+ *
+ * Undo the effect of #MBEDTLS_TEST_MEMORY_POISON.
+ *
+ * The behavior is undefined if any part of the memory area is invalid,
+ * or if the memory area contains a mixture of poisoned and unpoisoned parts.
+ *
+ * This is a no-op in builds without a poisoning method.
+ * See #MBEDTLS_TEST_MEMORY_CAN_POISON.
+ *
+ * \param buf   Pointer to the beginning of the memory area to unpoison.
+ * \param size  Size of the memory area in bytes.
+ */
+
+#if defined(MBEDTLS_TEST_MEMORY_CAN_POISON)
+
+/** Poison a memory area so that any attempt to read or write from it will
+ * cause a runtime failure.
+ *
+ * The behavior is undefined if any part of the memory area is invalid.
+ */
+void mbedtls_test_memory_poison(const unsigned char *ptr, size_t size);
+#define MBEDTLS_TEST_MEMORY_POISON(ptr, size)    \
+    mbedtls_test_memory_poison(ptr, size)
+
+/** Undo the effect of mbedtls_test_memory_poison().
+ *
+ * This is a no-op if the given area is entirely valid, unpoisoned memory.
+ *
+ * The behavior is undefined if any part of the memory area is invalid,
+ * or if the memory area contains a mixture of poisoned and unpoisoned parts.
+ */
+void mbedtls_test_memory_unpoison(const unsigned char *ptr, size_t size);
+#define MBEDTLS_TEST_MEMORY_UNPOISON(ptr, size)    \
+    mbedtls_test_memory_unpoison(ptr, size)
+
+#else /* MBEDTLS_TEST_MEMORY_CAN_POISON */
+#define MBEDTLS_TEST_MEMORY_POISON(ptr, size) ((void) 0)
+#define MBEDTLS_TEST_MEMORY_UNPOISON(ptr, size) ((void) 0)
+#endif /* MBEDTLS_TEST_MEMORY_CAN_POISON */
+
 #endif /* TEST_MEMORY_H */
diff --git a/tests/src/test_memory.c b/tests/src/test_memory.c
index cda91e5..6b1404b 100644
--- a/tests/src/test_memory.c
+++ b/tests/src/test_memory.c
@@ -13,3 +13,25 @@
 #include <test/macros.h>
 #include <test/memory.h>
 
+#if defined(MBEDTLS_TEST_HAVE_ASAN)
+#include <sanitizer/asan_interface.h>
+#include <stdint.h>
+#endif
+
+#if defined(MBEDTLS_TEST_HAVE_ASAN)
+void mbedtls_test_memory_poison(const unsigned char *ptr, size_t size)
+{
+    if (size == 0) {
+        return;
+    }
+    __asan_poison_memory_region(ptr, size);
+}
+
+void mbedtls_test_memory_unpoison(const unsigned char *ptr, size_t size)
+{
+    if (size == 0) {
+        return;
+    }
+    __asan_unpoison_memory_region(ptr, size);
+}
+#endif /* Asan */