[v17,15/15] selftests, arm64: add a selftest for passing tagged pointers to kernel
diff mbox series

Message ID e024234e652f23be4d76d63227de114e7def5dff.1560339705.git.andreyknvl@google.com
State New
Headers show
Series
  • arm64: untag user pointers passed to the kernel
Related show

Commit Message

Andrey Konovalov June 12, 2019, 11:43 a.m. UTC
This patch is a part of a series that extends arm64 kernel ABI to allow to
pass tagged user pointers (with the top byte set to something else other
than 0x00) as syscall arguments.

This patch adds a simple test, that calls the uname syscall with a
tagged user pointer as an argument. Without the kernel accepting tagged
user pointers the test fails with EFAULT.

Co-developed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Andrey Konovalov <andreyknvl@google.com>
---
 tools/testing/selftests/arm64/.gitignore      |  2 +
 tools/testing/selftests/arm64/Makefile        | 22 +++++++
 .../testing/selftests/arm64/run_tags_test.sh  | 12 ++++
 tools/testing/selftests/arm64/tags_lib.c      | 62 +++++++++++++++++++
 tools/testing/selftests/arm64/tags_test.c     | 18 ++++++
 5 files changed, 116 insertions(+)
 create mode 100644 tools/testing/selftests/arm64/.gitignore
 create mode 100644 tools/testing/selftests/arm64/Makefile
 create mode 100755 tools/testing/selftests/arm64/run_tags_test.sh
 create mode 100644 tools/testing/selftests/arm64/tags_lib.c
 create mode 100644 tools/testing/selftests/arm64/tags_test.c

Comments

Szabolcs Nagy June 12, 2019, 12:30 p.m. UTC | #1
On 12/06/2019 12:43, Andrey Konovalov wrote:
> --- /dev/null
> +++ b/tools/testing/selftests/arm64/tags_lib.c
> @@ -0,0 +1,62 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <stdlib.h>
> +#include <sys/prctl.h>
> +
> +#define TAG_SHIFT	(56)
> +#define TAG_MASK	(0xffUL << TAG_SHIFT)
> +
> +#define PR_SET_TAGGED_ADDR_CTRL	55
> +#define PR_GET_TAGGED_ADDR_CTRL	56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +
> +void *__libc_malloc(size_t size);
> +void __libc_free(void *ptr);
> +void *__libc_realloc(void *ptr, size_t size);
> +void *__libc_calloc(size_t nmemb, size_t size);

this does not work on at least musl.

the most robust solution would be to implement
the malloc apis with mmap/munmap/mremap, if that's
too cumbersome then use dlsym RTLD_NEXT (although
that has the slight wart that in glibc it may call
calloc so wrapping calloc that way is tricky).

in simple linux tests i'd just use static or
stack allocations or mmap.

if a generic preloadable lib solution is needed
then do it properly with pthread_once to avoid
races etc.

> +
> +static void *tag_ptr(void *ptr)
> +{
> +	static int tagged_addr_err = 1;
> +	unsigned long tag = 0;
> +
> +	/*
> +	 * Note that this code is racy. We only use it as a part of a single
> +	 * threaded test application. Beware of using in multithreaded ones.
> +	 */
> +	if (tagged_addr_err == 1)
> +		tagged_addr_err = prctl(PR_SET_TAGGED_ADDR_CTRL,
> +				PR_TAGGED_ADDR_ENABLE, 0, 0, 0);
> +
> +	if (!ptr)
> +		return ptr;
> +	if (!tagged_addr_err)
> +		tag = rand() & 0xff;
> +
> +	return (void *)((unsigned long)ptr | (tag << TAG_SHIFT));
> +}
> +
> +static void *untag_ptr(void *ptr)
> +{
> +	return (void *)((unsigned long)ptr & ~TAG_MASK);
> +}
> +
> +void *malloc(size_t size)
> +{
> +	return tag_ptr(__libc_malloc(size));
> +}
...
Catalin Marinas June 12, 2019, 3 p.m. UTC | #2
On Wed, Jun 12, 2019 at 01:30:36PM +0100, Szabolcs Nagy wrote:
> On 12/06/2019 12:43, Andrey Konovalov wrote:
> > --- /dev/null
> > +++ b/tools/testing/selftests/arm64/tags_lib.c
> > @@ -0,0 +1,62 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +#include <stdlib.h>
> > +#include <sys/prctl.h>
> > +
> > +#define TAG_SHIFT	(56)
> > +#define TAG_MASK	(0xffUL << TAG_SHIFT)
> > +
> > +#define PR_SET_TAGGED_ADDR_CTRL	55
> > +#define PR_GET_TAGGED_ADDR_CTRL	56
> > +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> > +
> > +void *__libc_malloc(size_t size);
> > +void __libc_free(void *ptr);
> > +void *__libc_realloc(void *ptr, size_t size);
> > +void *__libc_calloc(size_t nmemb, size_t size);
> 
> this does not work on at least musl.
> 
> the most robust solution would be to implement
> the malloc apis with mmap/munmap/mremap, if that's
> too cumbersome then use dlsym RTLD_NEXT (although
> that has the slight wart that in glibc it may call
> calloc so wrapping calloc that way is tricky).
> 
> in simple linux tests i'd just use static or
> stack allocations or mmap.
> 
> if a generic preloadable lib solution is needed
> then do it properly with pthread_once to avoid
> races etc.

Thanks for the feedback Szabolcs. I guess we can go back to the initial
simple test that Andrey had and drop the whole LD_PRELOAD hack (I'll
just use it for my internal testing).

BTW, when you get some time, please review Vincenzo's ABI documentation
patches from a user/libc perspective. Once agreed, they should become
part of this series.
Andrey Konovalov June 19, 2019, 2:42 p.m. UTC | #3
On Wed, Jun 12, 2019 at 5:01 PM Catalin Marinas <catalin.marinas@arm.com> wrote:
>
> On Wed, Jun 12, 2019 at 01:30:36PM +0100, Szabolcs Nagy wrote:
> > On 12/06/2019 12:43, Andrey Konovalov wrote:
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/arm64/tags_lib.c
> > > @@ -0,0 +1,62 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +
> > > +#include <stdlib.h>
> > > +#include <sys/prctl.h>
> > > +
> > > +#define TAG_SHIFT  (56)
> > > +#define TAG_MASK   (0xffUL << TAG_SHIFT)
> > > +
> > > +#define PR_SET_TAGGED_ADDR_CTRL    55
> > > +#define PR_GET_TAGGED_ADDR_CTRL    56
> > > +#define PR_TAGGED_ADDR_ENABLE      (1UL << 0)
> > > +
> > > +void *__libc_malloc(size_t size);
> > > +void __libc_free(void *ptr);
> > > +void *__libc_realloc(void *ptr, size_t size);
> > > +void *__libc_calloc(size_t nmemb, size_t size);
> >
> > this does not work on at least musl.
> >
> > the most robust solution would be to implement
> > the malloc apis with mmap/munmap/mremap, if that's
> > too cumbersome then use dlsym RTLD_NEXT (although
> > that has the slight wart that in glibc it may call
> > calloc so wrapping calloc that way is tricky).
> >
> > in simple linux tests i'd just use static or
> > stack allocations or mmap.
> >
> > if a generic preloadable lib solution is needed
> > then do it properly with pthread_once to avoid
> > races etc.
>
> Thanks for the feedback Szabolcs. I guess we can go back to the initial
> simple test that Andrey had and drop the whole LD_PRELOAD hack (I'll
> just use it for my internal testing).

OK, will do in v18.

>
> BTW, when you get some time, please review Vincenzo's ABI documentation
> patches from a user/libc perspective. Once agreed, they should become
> part of this series.
>
> --
> Catalin

Patch
diff mbox series

diff --git a/tools/testing/selftests/arm64/.gitignore b/tools/testing/selftests/arm64/.gitignore
new file mode 100644
index 000000000000..9b6a568de17f
--- /dev/null
+++ b/tools/testing/selftests/arm64/.gitignore
@@ -0,0 +1,2 @@ 
+tags_test
+tags_lib.so
diff --git a/tools/testing/selftests/arm64/Makefile b/tools/testing/selftests/arm64/Makefile
new file mode 100644
index 000000000000..9dee18727923
--- /dev/null
+++ b/tools/testing/selftests/arm64/Makefile
@@ -0,0 +1,22 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+include ../lib.mk
+
+# ARCH can be overridden by the user for cross compiling
+ARCH ?= $(shell uname -m 2>/dev/null || echo not)
+
+ifneq (,$(filter $(ARCH),aarch64 arm64))
+
+TEST_CUSTOM_PROGS := $(OUTPUT)/tags_test
+
+$(OUTPUT)/tags_test: tags_test.c $(OUTPUT)/tags_lib.so
+	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $<
+
+$(OUTPUT)/tags_lib.so: tags_lib.c
+	$(CC) -o $@ -shared $(CFLAGS) $(LDFLAGS) $^
+
+TEST_PROGS := run_tags_test.sh
+
+all: $(TEST_CUSTOM_PROGS)
+
+endif
diff --git a/tools/testing/selftests/arm64/run_tags_test.sh b/tools/testing/selftests/arm64/run_tags_test.sh
new file mode 100755
index 000000000000..2bbe0cd4220b
--- /dev/null
+++ b/tools/testing/selftests/arm64/run_tags_test.sh
@@ -0,0 +1,12 @@ 
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+echo "--------------------"
+echo "running tags test"
+echo "--------------------"
+LD_PRELOAD=./tags_lib.so ./tags_test
+if [ $? -ne 0 ]; then
+	echo "[FAIL]"
+else
+	echo "[PASS]"
+fi
diff --git a/tools/testing/selftests/arm64/tags_lib.c b/tools/testing/selftests/arm64/tags_lib.c
new file mode 100644
index 000000000000..55f64fc1aae6
--- /dev/null
+++ b/tools/testing/selftests/arm64/tags_lib.c
@@ -0,0 +1,62 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <stdlib.h>
+#include <sys/prctl.h>
+
+#define TAG_SHIFT	(56)
+#define TAG_MASK	(0xffUL << TAG_SHIFT)
+
+#define PR_SET_TAGGED_ADDR_CTRL	55
+#define PR_GET_TAGGED_ADDR_CTRL	56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+
+void *__libc_malloc(size_t size);
+void __libc_free(void *ptr);
+void *__libc_realloc(void *ptr, size_t size);
+void *__libc_calloc(size_t nmemb, size_t size);
+
+static void *tag_ptr(void *ptr)
+{
+	static int tagged_addr_err = 1;
+	unsigned long tag = 0;
+
+	/*
+	 * Note that this code is racy. We only use it as a part of a single
+	 * threaded test application. Beware of using in multithreaded ones.
+	 */
+	if (tagged_addr_err == 1)
+		tagged_addr_err = prctl(PR_SET_TAGGED_ADDR_CTRL,
+				PR_TAGGED_ADDR_ENABLE, 0, 0, 0);
+
+	if (!ptr)
+		return ptr;
+	if (!tagged_addr_err)
+		tag = rand() & 0xff;
+
+	return (void *)((unsigned long)ptr | (tag << TAG_SHIFT));
+}
+
+static void *untag_ptr(void *ptr)
+{
+	return (void *)((unsigned long)ptr & ~TAG_MASK);
+}
+
+void *malloc(size_t size)
+{
+	return tag_ptr(__libc_malloc(size));
+}
+
+void free(void *ptr)
+{
+	__libc_free(untag_ptr(ptr));
+}
+
+void *realloc(void *ptr, size_t size)
+{
+	return tag_ptr(__libc_realloc(untag_ptr(ptr), size));
+}
+
+void *calloc(size_t nmemb, size_t size)
+{
+	return tag_ptr(__libc_calloc(nmemb, size));
+}
diff --git a/tools/testing/selftests/arm64/tags_test.c b/tools/testing/selftests/arm64/tags_test.c
new file mode 100644
index 000000000000..263b302874ed
--- /dev/null
+++ b/tools/testing/selftests/arm64/tags_test.c
@@ -0,0 +1,18 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/utsname.h>
+
+int main(void)
+{
+	struct utsname *ptr;
+	int err;
+
+	ptr = (struct utsname *)malloc(sizeof(*ptr));
+	err = uname(ptr);
+	free(ptr);
+	return err;
+}