Add a regress test to test various malloc API and heap mismanagement
authorotto <otto@openbsd.org>
Mon, 8 May 2023 11:12:44 +0000 (11:12 +0000)
committerotto <otto@openbsd.org>
Mon, 8 May 2023 11:12:44 +0000 (11:12 +0000)
errors which should cause abort. A few are not enabled yet, they
will be once the corresponding diffs in malloc are committed.

regress/lib/libc/malloc/malloc_errs/Makefile [new file with mode: 0644]
regress/lib/libc/malloc/malloc_errs/malloc_errs.c [new file with mode: 0644]

diff --git a/regress/lib/libc/malloc/malloc_errs/Makefile b/regress/lib/libc/malloc/malloc_errs/Makefile
new file mode 100644 (file)
index 0000000..150f30e
--- /dev/null
@@ -0,0 +1,5 @@
+#      $OpenBSD: Makefile,v 1.1 2023/05/08 11:12:44 otto Exp $
+
+PROG=  malloc_errs
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libc/malloc/malloc_errs/malloc_errs.c b/regress/lib/libc/malloc/malloc_errs/malloc_errs.c
new file mode 100644 (file)
index 0000000..e2ac6dd
--- /dev/null
@@ -0,0 +1,286 @@
+/*     $OpenBSD: malloc_errs.c,v 1.1 2023/05/08 11:12:44 otto Exp $    */
+/*
+ * Copyright (c) 2023 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+
+/* Test erroneous use of API and heap that malloc should catch */
+
+void
+clearq(void *p)
+{
+       int i;
+       void *q;
+
+       /* Clear delayed free queue */
+       for (i = 0; i < 400; i++) {
+               q = malloc(100);
+               free(q);
+               if (p == q) {
+                       fprintf(stderr, "Re-use\n");
+                       abort();
+               }
+       }
+}
+
+/* test the test setup */
+void
+t0(void)
+{
+       abort();
+}
+
+/* double free >= page size */
+void
+t1(void)
+{
+       void *p = malloc(10000);
+       free(p);
+       free(p);
+}
+
+/* double free chunks are different, have a delayed free list */
+void
+t2(void)
+{
+       void *p, *q;
+       int i;
+
+       p = malloc(100);
+       free(p);
+       clearq(p);
+       free(p);
+}
+
+/* double free without clearing delayed free list, needs F */
+void
+t3(void)
+{
+       void *p = malloc(100);
+       free(p);
+       free(p);
+}
+
+/* free without prior allocation */
+void
+t4(void)
+{
+       free((void*)1);
+}
+
+/* realloc of bogus pointer */
+void
+t5(void)
+{
+       realloc((void*)1, 10);
+}
+
+/* write after free for chunk */
+void
+t6(void)
+{
+       char *p = malloc(32);
+       free(p);
+       p[0] = ~p[0];
+       clearq(NULL);
+}
+
+/* write after free large alloction */
+void
+t7(void)
+{
+       char *p, *q;
+       int i;
+
+       p = malloc(10000);
+       free(p);
+       p[0] = ~p[0];
+       /* force re-use from the cache */
+       for (i = 0; i < 100; i++) {
+               q = malloc(10000);
+               free(q);
+       }
+}
+
+/* write after free for chunk, no clearing of delayed free queue */
+void
+t8(void)
+{
+       char *p, *q;
+
+       p = malloc(32);
+       q = malloc(32);
+       free(p);
+       p[0] = ~p[0];
+       free(q);
+}
+
+/* canary check */
+void
+t9(void)
+{
+       char *p;
+
+       p = malloc(100);
+       p[100] = 0;
+       free(p);
+}
+
+/* t10 is the same as t9 with different flags */
+
+/* modified chunk pointer */
+void
+t11(void)
+{
+       char *p = malloc(100);
+       free(p + 1);
+}
+
+/* free chunk pointer */
+void
+t12(void)
+{
+       char *p = malloc(16);
+       free(p + 16);
+}
+
+/* freezero with wrong size */
+void
+t13(void)
+{
+       char *p = malloc(16);
+       freezero(p, 17);
+}
+
+/* freezero with wrong size 2 */
+void
+t14(void)
+{
+       char *p = malloc(15);
+       freezero(p, 16);
+}
+
+/* freezero with wrong size, pages */
+void
+t15(void)
+{
+       char *p = malloc(getpagesize());
+       freezero(p, getpagesize() + 1);
+}
+
+/* recallocarray with wrong size */
+void
+t16(void)
+{
+       abort(); /* not yet */
+       char *p = recallocarray(NULL, 0, 16, 1);
+       char *q = recallocarray(p, 2, 3, 16);
+}
+
+/* recallocarray with wrong size 2 */
+void
+t17(void)
+{
+       char *p = recallocarray(NULL, 0, 15, 1);
+       char *q = recallocarray(p, 2, 3, 15);
+}
+
+/* recallocarray with wrong size, pages */
+void
+t18(void)
+{
+       abort(); /* not yet */
+       char *p = recallocarray(NULL, 0, 1, getpagesize());
+       char *q = recallocarray(p, 2, 3, getpagesize());
+}
+
+struct test {
+       void (*test)(void);
+       const char *flags;
+};
+
+struct test tests[] = {
+       { t0, "" },
+       { t1, "" },
+       { t2, "" },
+       { t3, "F" },
+       { t4, "" },
+       { t5, "" },
+       { t6, "J" },
+       { t7, "JJ" },
+       { t8, "FJ" },
+       { t9, "C" },
+       { t9, "JC" }, /* t10 re-uses code from t9 */
+       { t11, "" },
+       { t12, "" },
+       { t13, "" },
+       { t14, "C" },
+       { t15, "" },
+       { t16, "" },
+       { t17, "C" },
+       { t18, "" },
+};
+
+int main(int argc, char *argv[])
+{
+
+       const struct rlimit lim = {0, 0};
+       int i, status;
+       pid_t pid;
+       char num[10];
+       char options[10];
+       extern char* malloc_options;
+
+       if (argc == 3) {
+               malloc_options = argv[2];
+               /* prevent coredumps */
+               setrlimit(RLIMIT_CORE, &lim);
+               i = atoi(argv[1]);
+               fprintf(stderr, "Test %d\n", i);
+               (*tests[i].test)();
+               return 0;
+       }
+
+       for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+               pid = fork();
+               switch (pid) {
+               case 0:
+                       snprintf(options, sizeof(options), "cfjgu%s", tests[i].flags);
+                       snprintf(num, sizeof(num), "%d", i);
+                       execl(argv[0], argv[0], num, options, NULL);
+                       err(1, "exec");
+               break;
+               case -1:
+                       err(1, "fork");
+               break;
+               default: 
+                       if (waitpid(pid, &status, 0) == -1)
+                               err(1, "wait");
+                       if (!WIFSIGNALED(status) || 
+                           WTERMSIG(status) != SIGABRT)
+                       errx(1, "Test %d did not abort", i);
+               break;
+               }
+       }
+       return 0;
+}
+