diff options
Diffstat (limited to 'tools/testing/selftests/vDSO')
| -rw-r--r-- | tools/testing/selftests/vDSO/.gitignore | 9 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/Makefile | 44 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/parse_vdso.c | 299 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/parse_vdso.h | 30 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_call.h | 69 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_config.h | 92 | ||||
| l--------- | tools/testing/selftests/vDSO/vdso_standalone_test_x86.c | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_test_abi.c | 299 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_test_chacha.c | 133 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_test_correctness.c | 509 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_test_getcpu.c | 53 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_test_getrandom.c | 322 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vdso_test_gettimeofday.c | 56 | ||||
| -rw-r--r-- | tools/testing/selftests/vDSO/vgetrandom-chacha.S | 20 |
14 files changed, 1936 insertions, 0 deletions
diff --git a/tools/testing/selftests/vDSO/.gitignore b/tools/testing/selftests/vDSO/.gitignore new file mode 100644 index 000000000..ba322a353 --- /dev/null +++ b/tools/testing/selftests/vDSO/.gitignore @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +vdso_test +vdso_test_abi +vdso_test_correctness +vdso_test_gettimeofday +vdso_test_getcpu +vdso_standalone_test_x86 +vdso_test_getrandom +vdso_test_chacha diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile new file mode 100644 index 000000000..a61047bdc --- /dev/null +++ b/tools/testing/selftests/vDSO/Makefile @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../scripts/Makefile.arch + +TEST_GEN_PROGS := vdso_test_gettimeofday +TEST_GEN_PROGS += vdso_test_getcpu +TEST_GEN_PROGS += vdso_test_abi +ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64)) +TEST_GEN_PROGS += vdso_standalone_test_x86 +endif +TEST_GEN_PROGS += vdso_test_correctness +TEST_GEN_PROGS += vdso_test_getrandom +TEST_GEN_PROGS += vdso_test_chacha + +CFLAGS := -std=gnu99 -O2 -Wall -Wstrict-prototypes + +ifeq ($(CONFIG_X86_32),y) +LDLIBS += -lgcc_s +endif + +include ../lib.mk + +CFLAGS_NOLIBC := -nostdlib -nostdinc -ffreestanding -fno-asynchronous-unwind-tables \ + -fno-stack-protector -include $(top_srcdir)/tools/include/nolibc/nolibc.h \ + -I$(top_srcdir)/tools/include/nolibc/ $(KHDR_INCLUDES) + +$(OUTPUT)/vdso_test_gettimeofday: parse_vdso.c vdso_test_gettimeofday.c +$(OUTPUT)/vdso_test_getcpu: parse_vdso.c vdso_test_getcpu.c +$(OUTPUT)/vdso_test_abi: parse_vdso.c vdso_test_abi.c +$(OUTPUT)/vdso_test_correctness: parse_vdso.c vdso_test_correctness.c + +$(OUTPUT)/vdso_standalone_test_x86: vdso_standalone_test_x86.c parse_vdso.c | headers +$(OUTPUT)/vdso_standalone_test_x86: CFLAGS:=$(CFLAGS_NOLIBC) $(CFLAGS) + +$(OUTPUT)/vdso_test_getrandom: parse_vdso.c +$(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ + $(KHDR_INCLUDES) \ + -isystem $(top_srcdir)/include/uapi + +$(OUTPUT)/vdso_test_chacha: vgetrandom-chacha.S +$(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ + -idirafter $(top_srcdir)/tools/include/generated \ + -idirafter $(top_srcdir)/arch/$(SRCARCH)/include \ + -idirafter $(top_srcdir)/include \ + -Wa,--noexecstack diff --git a/tools/testing/selftests/vDSO/parse_vdso.c b/tools/testing/selftests/vDSO/parse_vdso.c new file mode 100644 index 000000000..c6ff4413e --- /dev/null +++ b/tools/testing/selftests/vDSO/parse_vdso.c @@ -0,0 +1,299 @@ +/* + * parse_vdso.c: Linux reference vDSO parser + * Written by Andrew Lutomirski, 2011-2014. + * + * This code is meant to be linked in to various programs that run on Linux. + * As such, it is available with as few restrictions as possible. This file + * is licensed under the Creative Commons Zero License, version 1.0, + * available at http://creativecommons.org/publicdomain/zero/1.0/legalcode + * + * The vDSO is a regular ELF DSO that the kernel maps into user space when + * it starts a program. It works equally well in statically and dynamically + * linked binaries. + * + * This code is tested on x86. In principle it should work on any + * architecture that has a vDSO. + */ + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <limits.h> +#include <elf.h> + +#include "parse_vdso.h" + +/* And here's the code. */ +#ifndef ELF_BITS +# if __SIZEOF_LONG__ >= 8 +# define ELF_BITS 64 +# else +# define ELF_BITS 32 +# endif +#endif + +#define ELF_BITS_XFORM2(bits, x) Elf##bits##_##x +#define ELF_BITS_XFORM(bits, x) ELF_BITS_XFORM2(bits, x) +#define ELF(x) ELF_BITS_XFORM(ELF_BITS, x) + +#ifdef __s390x__ +#define ELF_HASH_ENTRY ELF(Xword) +#else +#define ELF_HASH_ENTRY ELF(Word) +#endif + +static struct vdso_info +{ + bool valid; + + /* Load information */ + uintptr_t load_addr; + uintptr_t load_offset; /* load_addr - recorded vaddr */ + + /* Symbol table */ + ELF(Sym) *symtab; + const char *symstrings; + ELF(Word) *gnu_hash, *gnu_bucket; + ELF_HASH_ENTRY *bucket, *chain; + ELF_HASH_ENTRY nbucket, nchain; + + /* Version table */ + ELF(Versym) *versym; + ELF(Verdef) *verdef; +} vdso_info; + +/* + * Straight from the ELF specification...and then tweaked slightly, in order to + * avoid a few clang warnings. + */ +static unsigned long elf_hash(const char *name) +{ + unsigned long h = 0, g; + const unsigned char *uch_name = (const unsigned char *)name; + + while (*uch_name) + { + h = (h << 4) + *uch_name++; + g = h & 0xf0000000; + if (g) + h ^= g >> 24; + h &= ~g; + } + return h; +} + +static uint32_t gnu_hash(const char *name) +{ + const unsigned char *s = (void *)name; + uint32_t h = 5381; + + for (; *s; s++) + h += h * 32 + *s; + return h; +} + +void vdso_init_from_sysinfo_ehdr(uintptr_t base) +{ + size_t i; + bool found_vaddr = false; + + vdso_info.valid = false; + + vdso_info.load_addr = base; + + ELF(Ehdr) *hdr = (ELF(Ehdr)*)base; + if (hdr->e_ident[EI_CLASS] != + (ELF_BITS == 32 ? ELFCLASS32 : ELFCLASS64)) { + return; /* Wrong ELF class -- check ELF_BITS */ + } + + ELF(Phdr) *pt = (ELF(Phdr)*)(vdso_info.load_addr + hdr->e_phoff); + ELF(Dyn) *dyn = 0; + + /* + * We need two things from the segment table: the load offset + * and the dynamic table. + */ + for (i = 0; i < hdr->e_phnum; i++) + { + if (pt[i].p_type == PT_LOAD && !found_vaddr) { + found_vaddr = true; + vdso_info.load_offset = base + + (uintptr_t)pt[i].p_offset + - (uintptr_t)pt[i].p_vaddr; + } else if (pt[i].p_type == PT_DYNAMIC) { + dyn = (ELF(Dyn)*)(base + pt[i].p_offset); + } + } + + if (!found_vaddr || !dyn) + return; /* Failed */ + + /* + * Fish out the useful bits of the dynamic table. + */ + ELF_HASH_ENTRY *hash = 0; + vdso_info.symstrings = 0; + vdso_info.gnu_hash = 0; + vdso_info.symtab = 0; + vdso_info.versym = 0; + vdso_info.verdef = 0; + for (i = 0; dyn[i].d_tag != DT_NULL; i++) { + switch (dyn[i].d_tag) { + case DT_STRTAB: + vdso_info.symstrings = (const char *) + ((uintptr_t)dyn[i].d_un.d_ptr + + vdso_info.load_offset); + break; + case DT_SYMTAB: + vdso_info.symtab = (ELF(Sym) *) + ((uintptr_t)dyn[i].d_un.d_ptr + + vdso_info.load_offset); + break; + case DT_HASH: + hash = (ELF_HASH_ENTRY *) + ((uintptr_t)dyn[i].d_un.d_ptr + + vdso_info.load_offset); + break; + case DT_GNU_HASH: + vdso_info.gnu_hash = + (ELF(Word) *)((uintptr_t)dyn[i].d_un.d_ptr + + vdso_info.load_offset); + break; + case DT_VERSYM: + vdso_info.versym = (ELF(Versym) *) + ((uintptr_t)dyn[i].d_un.d_ptr + + vdso_info.load_offset); + break; + case DT_VERDEF: + vdso_info.verdef = (ELF(Verdef) *) + ((uintptr_t)dyn[i].d_un.d_ptr + + vdso_info.load_offset); + break; + } + } + if (!vdso_info.symstrings || !vdso_info.symtab || + (!hash && !vdso_info.gnu_hash)) + return; /* Failed */ + + if (!vdso_info.verdef) + vdso_info.versym = 0; + + /* Parse the hash table header. */ + if (vdso_info.gnu_hash) { + vdso_info.nbucket = vdso_info.gnu_hash[0]; + /* The bucket array is located after the header (4 uint32) and the bloom + * filter (size_t array of gnu_hash[2] elements). + */ + vdso_info.gnu_bucket = vdso_info.gnu_hash + 4 + + sizeof(size_t) / 4 * vdso_info.gnu_hash[2]; + } else { + vdso_info.nbucket = hash[0]; + vdso_info.nchain = hash[1]; + vdso_info.bucket = &hash[2]; + vdso_info.chain = &hash[vdso_info.nbucket + 2]; + } + + /* That's all we need. */ + vdso_info.valid = true; +} + +static bool vdso_match_version(ELF(Versym) ver, + const char *name, ELF(Word) hash) +{ + /* + * This is a helper function to check if the version indexed by + * ver matches name (which hashes to hash). + * + * The version definition table is a mess, and I don't know how + * to do this in better than linear time without allocating memory + * to build an index. I also don't know why the table has + * variable size entries in the first place. + * + * For added fun, I can't find a comprehensible specification of how + * to parse all the weird flags in the table. + * + * So I just parse the whole table every time. + */ + + /* First step: find the version definition */ + ver &= 0x7fff; /* Apparently bit 15 means "hidden" */ + ELF(Verdef) *def = vdso_info.verdef; + while(true) { + if ((def->vd_flags & VER_FLG_BASE) == 0 + && (def->vd_ndx & 0x7fff) == ver) + break; + + if (def->vd_next == 0) + return false; /* No definition. */ + + def = (ELF(Verdef) *)((char *)def + def->vd_next); + } + + /* Now figure out whether it matches. */ + ELF(Verdaux) *aux = (ELF(Verdaux)*)((char *)def + def->vd_aux); + return def->vd_hash == hash + && !strcmp(name, vdso_info.symstrings + aux->vda_name); +} + +static bool check_sym(ELF(Sym) *sym, ELF(Word) i, const char *name, + const char *version, unsigned long ver_hash) +{ + /* Check for a defined global or weak function w/ right name. */ + if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) + return false; + if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL && + ELF64_ST_BIND(sym->st_info) != STB_WEAK) + return false; + if (strcmp(name, vdso_info.symstrings + sym->st_name)) + return false; + + /* Check symbol version. */ + if (vdso_info.versym && + !vdso_match_version(vdso_info.versym[i], version, ver_hash)) + return false; + + return true; +} + +void *vdso_sym(const char *version, const char *name) +{ + unsigned long ver_hash; + if (!vdso_info.valid) + return 0; + + ver_hash = elf_hash(version); + ELF(Word) i; + + if (vdso_info.gnu_hash) { + uint32_t h1 = gnu_hash(name), h2, *hashval; + + i = vdso_info.gnu_bucket[h1 % vdso_info.nbucket]; + if (i == 0) + return 0; + h1 |= 1; + hashval = vdso_info.gnu_bucket + vdso_info.nbucket + + (i - vdso_info.gnu_hash[1]); + for (;; i++) { + ELF(Sym) *sym = &vdso_info.symtab[i]; + h2 = *hashval++; + if (h1 == (h2 | 1) && + check_sym(sym, i, name, version, ver_hash)) + return (void *)(vdso_info.load_offset + + sym->st_value); + if (h2 & 1) + break; + } + } else { + i = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket]; + for (; i; i = vdso_info.chain[i]) { + ELF(Sym) *sym = &vdso_info.symtab[i]; + if (sym->st_shndx != SHN_UNDEF && + check_sym(sym, i, name, version, ver_hash)) + return (void *)(vdso_info.load_offset + + sym->st_value); + } + } + + return 0; +} diff --git a/tools/testing/selftests/vDSO/parse_vdso.h b/tools/testing/selftests/vDSO/parse_vdso.h new file mode 100644 index 000000000..09d068ed1 --- /dev/null +++ b/tools/testing/selftests/vDSO/parse_vdso.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef PARSE_VDSO_H +#define PARSE_VDSO_H + +#include <stdint.h> + +/* + * To use this vDSO parser, first call one of the vdso_init_* functions. + * If you've already parsed auxv, then pass the value of AT_SYSINFO_EHDR + * to vdso_init_from_sysinfo_ehdr. Otherwise pass auxv to vdso_init_from_auxv. + * Then call vdso_sym for each symbol you want. For example, to look up + * gettimeofday on x86_64, use: + * + * <some pointer> = vdso_sym("LINUX_2.6", "gettimeofday"); + * or + * <some pointer> = vdso_sym("LINUX_2.6", "__vdso_gettimeofday"); + * + * vdso_sym will return 0 if the symbol doesn't exist or if the init function + * failed or was not called. vdso_sym is a little slow, so its return value + * should be cached. + * + * vdso_sym is threadsafe; the init functions are not. + * + * These are the prototypes: + */ +void *vdso_sym(const char *version, const char *name); +void vdso_init_from_sysinfo_ehdr(uintptr_t base); + +#endif diff --git a/tools/testing/selftests/vDSO/vdso_call.h b/tools/testing/selftests/vDSO/vdso_call.h new file mode 100644 index 000000000..e7205584c --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_call.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Macro to call vDSO functions + * + * Copyright (C) 2024 Christophe Leroy <christophe.leroy@csgroup.eu>, CS GROUP France + */ +#ifndef __VDSO_CALL_H__ +#define __VDSO_CALL_H__ + +#ifdef __powerpc__ + +#define LOADARGS_1(fn, __arg1) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ +} while (0) + +#define LOADARGS_2(fn, __arg1, __arg2) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ + _r4 = (long)__arg2; \ +} while (0) + +#define LOADARGS_3(fn, __arg1, __arg2, __arg3) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ + _r4 = (long)__arg2; \ + _r5 = (long)__arg3; \ +} while (0) + +#define LOADARGS_5(fn, __arg1, __arg2, __arg3, __arg4, __arg5) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ + _r4 = (long)__arg2; \ + _r5 = (long)__arg3; \ + _r6 = (long)__arg4; \ + _r7 = (long)__arg5; \ +} while (0) + +#define VDSO_CALL(fn, nr, args...) ({ \ + register void *_r0 asm ("r0"); \ + register long _r3 asm ("r3"); \ + register long _r4 asm ("r4"); \ + register long _r5 asm ("r5"); \ + register long _r6 asm ("r6"); \ + register long _r7 asm ("r7"); \ + register long _r8 asm ("r8"); \ + \ + LOADARGS_##nr(fn, args); \ + \ + asm volatile( \ + " mtctr %0\n" \ + " bctrl\n" \ + " bns+ 1f\n" \ + " neg 3, 3\n" \ + "1:" \ + : "+r" (_r0), "+r" (_r3), "+r" (_r4), "+r" (_r5), \ + "+r" (_r6), "+r" (_r7), "+r" (_r8) \ + : \ + : "r9", "r10", "r11", "r12", "cr0", "cr1", "cr5", \ + "cr6", "cr7", "xer", "lr", "ctr", "memory" \ + ); \ + _r3; \ +}) + +#else +#define VDSO_CALL(fn, nr, args...) fn(args) +#endif + +#endif diff --git a/tools/testing/selftests/vDSO/vdso_config.h b/tools/testing/selftests/vDSO/vdso_config.h new file mode 100644 index 000000000..5da223731 --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_config.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vdso_config.h: Configuration options for vDSO tests. + * Copyright (c) 2019 Arm Ltd. + */ +#ifndef __VDSO_CONFIG_H__ +#define __VDSO_CONFIG_H__ + +/* + * Each architecture exports its vDSO implementation with different names + * and a different version from the others, so we need to handle it as a + * special case. + */ +#if defined(__arm__) +#define VDSO_VERSION 0 +#define VDSO_NAMES 1 +#define VDSO_32BIT 1 +#elif defined(__aarch64__) +#define VDSO_VERSION 3 +#define VDSO_NAMES 0 +#elif defined(__powerpc64__) +#define VDSO_VERSION 1 +#define VDSO_NAMES 0 +#elif defined(__powerpc__) +#define VDSO_VERSION 1 +#define VDSO_NAMES 0 +#define VDSO_32BIT 1 +#elif defined (__s390x__) +#define VDSO_VERSION 2 +#define VDSO_NAMES 0 +#elif defined(__mips__) +#define VDSO_VERSION 0 +#define VDSO_NAMES 1 +#define VDSO_32BIT 1 +#elif defined(__sparc__) +#define VDSO_VERSION 0 +#define VDSO_NAMES 1 +#define VDSO_32BIT 1 +#elif defined(__i386__) +#define VDSO_VERSION 0 +#define VDSO_NAMES 1 +#define VDSO_32BIT 1 +#elif defined(__x86_64__) +#define VDSO_VERSION 0 +#define VDSO_NAMES 1 +#elif defined(__riscv__) || defined(__riscv) +#define VDSO_VERSION 5 +#define VDSO_NAMES 1 +#if __riscv_xlen == 32 +#define VDSO_32BIT 1 +#endif +#elif defined(__loongarch__) +#define VDSO_VERSION 6 +#define VDSO_NAMES 1 +#endif + +__attribute__((unused)) +static const char *versions[7] = { + "LINUX_2.6", + "LINUX_2.6.15", + "LINUX_2.6.29", + "LINUX_2.6.39", + "LINUX_4", + "LINUX_4.15", + "LINUX_5.10" +}; + +__attribute__((unused)) +static const char *names[2][8] = { + { + "__kernel_gettimeofday", + "__kernel_clock_gettime", + "__kernel_time", + "__kernel_clock_getres", + "__kernel_getcpu", + "__kernel_clock_gettime64", + "__kernel_getrandom", + "__kernel_clock_getres_time64", + }, + { + "__vdso_gettimeofday", + "__vdso_clock_gettime", + "__vdso_time", + "__vdso_clock_getres", + "__vdso_getcpu", + "__vdso_clock_gettime64", + "__vdso_getrandom", + "__vdso_clock_getres_time64", + }, +}; + +#endif /* __VDSO_CONFIG_H__ */ diff --git a/tools/testing/selftests/vDSO/vdso_standalone_test_x86.c b/tools/testing/selftests/vDSO/vdso_standalone_test_x86.c new file mode 120000 index 000000000..4d3d96f1e --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_standalone_test_x86.c @@ -0,0 +1 @@ +vdso_test_gettimeofday.c
\ No newline at end of file diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c new file mode 100644 index 000000000..b162a4ba9 --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * vdso_full_test.c: Sample code to test all the timers. + * Copyright (c) 2019 Arm Ltd. + * + * Compile with: + * gcc -std=gnu99 vdso_full_test.c parse_vdso.c + * + */ + +#include <stdint.h> +#include <elf.h> +#include <stdio.h> +#include <time.h> +#include <sys/auxv.h> +#include <sys/time.h> +#define _GNU_SOURCE +#include <unistd.h> +#include <sys/syscall.h> + +#include "kselftest.h" +#include "vdso_config.h" +#include "vdso_call.h" +#include "parse_vdso.h" + +static const char *version; +static const char **name; + +/* The same as struct __kernel_timespec */ +struct vdso_timespec64 { + uint64_t tv_sec; + uint64_t tv_nsec; +}; + +typedef long (*vdso_gettimeofday_t)(struct timeval *tv, struct timezone *tz); +typedef long (*vdso_clock_gettime_t)(clockid_t clk_id, struct timespec *ts); +typedef long (*vdso_clock_gettime64_t)(clockid_t clk_id, struct vdso_timespec64 *ts); +typedef long (*vdso_clock_getres_t)(clockid_t clk_id, struct timespec *ts); +typedef long (*vdso_clock_getres_time64_t)(clockid_t clk_id, struct vdso_timespec64 *ts); +typedef time_t (*vdso_time_t)(time_t *t); + +static const char * const vdso_clock_name[] = { + [CLOCK_REALTIME] = "CLOCK_REALTIME", + [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", + [CLOCK_PROCESS_CPUTIME_ID] = "CLOCK_PROCESS_CPUTIME_ID", + [CLOCK_THREAD_CPUTIME_ID] = "CLOCK_THREAD_CPUTIME_ID", + [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", + [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", + [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", + [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", + [CLOCK_REALTIME_ALARM] = "CLOCK_REALTIME_ALARM", + [CLOCK_BOOTTIME_ALARM] = "CLOCK_BOOTTIME_ALARM", + [10 /* CLOCK_SGI_CYCLE */] = "CLOCK_SGI_CYCLE", + [CLOCK_TAI] = "CLOCK_TAI", +}; + +static void vdso_test_gettimeofday(void) +{ + /* Find gettimeofday. */ + vdso_gettimeofday_t vdso_gettimeofday = + (vdso_gettimeofday_t)vdso_sym(version, name[0]); + + if (!vdso_gettimeofday) { + ksft_print_msg("Couldn't find %s\n", name[0]); + ksft_test_result_skip("%s\n", name[0]); + return; + } + + struct timeval tv; + long ret = VDSO_CALL(vdso_gettimeofday, 2, &tv, 0); + + if (ret == 0) { + ksft_print_msg("The time is %lld.%06lld\n", + (long long)tv.tv_sec, (long long)tv.tv_usec); + ksft_test_result_pass("%s\n", name[0]); + } else { + ksft_test_result_fail("%s\n", name[0]); + } +} + +static void vdso_test_clock_gettime64(clockid_t clk_id) +{ + /* Find clock_gettime64. */ + vdso_clock_gettime64_t vdso_clock_gettime64 = + (vdso_clock_gettime64_t)vdso_sym(version, name[5]); + + if (!vdso_clock_gettime64) { + ksft_print_msg("Couldn't find %s\n", name[5]); + ksft_test_result_skip("%s %s\n", name[5], + vdso_clock_name[clk_id]); + return; + } + + struct vdso_timespec64 ts; + long ret = VDSO_CALL(vdso_clock_gettime64, 2, clk_id, &ts); + + if (ret == 0) { + ksft_print_msg("The time is %lld.%06lld\n", + (long long)ts.tv_sec, (long long)ts.tv_nsec); + ksft_test_result_pass("%s %s\n", name[5], + vdso_clock_name[clk_id]); + } else { + ksft_test_result_fail("%s %s\n", name[5], + vdso_clock_name[clk_id]); + } +} + +static void vdso_test_clock_gettime(clockid_t clk_id) +{ + /* Find clock_gettime. */ + vdso_clock_gettime_t vdso_clock_gettime = + (vdso_clock_gettime_t)vdso_sym(version, name[1]); + + if (!vdso_clock_gettime) { + ksft_print_msg("Couldn't find %s\n", name[1]); + ksft_test_result_skip("%s %s\n", name[1], + vdso_clock_name[clk_id]); + return; + } + + struct timespec ts; + long ret = VDSO_CALL(vdso_clock_gettime, 2, clk_id, &ts); + + if (ret == 0) { + ksft_print_msg("The time is %lld.%06lld\n", + (long long)ts.tv_sec, (long long)ts.tv_nsec); + ksft_test_result_pass("%s %s\n", name[1], + vdso_clock_name[clk_id]); + } else { + ksft_test_result_fail("%s %s\n", name[1], + vdso_clock_name[clk_id]); + } +} + +static void vdso_test_time(void) +{ + /* Find time. */ + vdso_time_t vdso_time = + (vdso_time_t)vdso_sym(version, name[2]); + + if (!vdso_time) { + ksft_print_msg("Couldn't find %s\n", name[2]); + ksft_test_result_skip("%s\n", name[2]); + return; + } + + long ret = VDSO_CALL(vdso_time, 1, NULL); + + if (ret > 0) { + ksft_print_msg("The time in hours since January 1, 1970 is %lld\n", + (long long)(ret / 3600)); + ksft_test_result_pass("%s\n", name[2]); + } else { + ksft_test_result_fail("%s\n", name[2]); + } +} + +static void vdso_test_clock_getres(clockid_t clk_id) +{ + int clock_getres_fail = 0; + + /* Find clock_getres. */ + vdso_clock_getres_t vdso_clock_getres = + (vdso_clock_getres_t)vdso_sym(version, name[3]); + + if (!vdso_clock_getres) { + ksft_print_msg("Couldn't find %s\n", name[3]); + ksft_test_result_skip("%s %s\n", name[3], + vdso_clock_name[clk_id]); + return; + } + + struct timespec ts, sys_ts; + long ret = VDSO_CALL(vdso_clock_getres, 2, clk_id, &ts); + + if (ret == 0) { + ksft_print_msg("The vdso resolution is %lld %lld\n", + (long long)ts.tv_sec, (long long)ts.tv_nsec); + } else { + clock_getres_fail++; + } + + ret = syscall(__NR_clock_getres, clk_id, &sys_ts); + + ksft_print_msg("The syscall resolution is %lld %lld\n", + (long long)sys_ts.tv_sec, (long long)sys_ts.tv_nsec); + + if ((sys_ts.tv_sec != ts.tv_sec) || (sys_ts.tv_nsec != ts.tv_nsec)) + clock_getres_fail++; + + if (clock_getres_fail > 0) { + ksft_test_result_fail("%s %s\n", name[3], + vdso_clock_name[clk_id]); + } else { + ksft_test_result_pass("%s %s\n", name[3], + vdso_clock_name[clk_id]); + } +} + +#ifdef __NR_clock_getres_time64 +static void vdso_test_clock_getres_time64(clockid_t clk_id) +{ + int clock_getres_fail = 0; + + /* Find clock_getres. */ + vdso_clock_getres_time64_t vdso_clock_getres_time64 = + (vdso_clock_getres_time64_t)vdso_sym(version, name[7]); + + if (!vdso_clock_getres_time64) { + ksft_print_msg("Couldn't find %s\n", name[7]); + ksft_test_result_skip("%s %s\n", name[7], + vdso_clock_name[clk_id]); + return; + } + + struct vdso_timespec64 ts, sys_ts; + long ret = VDSO_CALL(vdso_clock_getres_time64, 2, clk_id, &ts); + + if (ret == 0) { + ksft_print_msg("The vdso resolution is %lld %lld\n", + (long long)ts.tv_sec, (long long)ts.tv_nsec); + } else { + clock_getres_fail++; + } + + ret = syscall(__NR_clock_getres_time64, clk_id, &sys_ts); + + ksft_print_msg("The syscall resolution is %lld %lld\n", + (long long)sys_ts.tv_sec, (long long)sys_ts.tv_nsec); + + if ((sys_ts.tv_sec != ts.tv_sec) || (sys_ts.tv_nsec != ts.tv_nsec)) + clock_getres_fail++; + + if (clock_getres_fail > 0) { + ksft_test_result_fail("%s %s\n", name[7], + vdso_clock_name[clk_id]); + } else { + ksft_test_result_pass("%s %s\n", name[7], + vdso_clock_name[clk_id]); + } +} +#else /* !__NR_clock_getres_time64 */ +static void vdso_test_clock_getres_time64(clockid_t clk_id) +{ + ksft_test_result_skip("%s %s\n", name[7], vdso_clock_name[clk_id]); +} +#endif /* __NR_clock_getres_time64 */ + +/* + * This function calls vdso_test_clock_gettime and vdso_test_clock_getres + * with different values for clock_id. + */ +static inline void vdso_test_clock(clockid_t clock_id) +{ + ksft_print_msg("clock_id: %s\n", vdso_clock_name[clock_id]); + + vdso_test_clock_gettime(clock_id); + vdso_test_clock_gettime64(clock_id); + + vdso_test_clock_getres(clock_id); + vdso_test_clock_getres_time64(clock_id); +} + +#define VDSO_TEST_PLAN 38 + +int main(int argc, char **argv) +{ + unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + + ksft_print_header(); + + if (!sysinfo_ehdr) + ksft_exit_skip("AT_SYSINFO_EHDR is not present!\n"); + + ksft_set_plan(VDSO_TEST_PLAN); + + version = versions[VDSO_VERSION]; + name = (const char **)&names[VDSO_NAMES]; + + ksft_print_msg("[vDSO kselftest] VDSO_VERSION: %s\n", version); + + vdso_init_from_sysinfo_ehdr(getauxval(AT_SYSINFO_EHDR)); + + vdso_test_gettimeofday(); + + vdso_test_clock(CLOCK_REALTIME); + vdso_test_clock(CLOCK_BOOTTIME); + vdso_test_clock(CLOCK_TAI); + vdso_test_clock(CLOCK_REALTIME_COARSE); + vdso_test_clock(CLOCK_MONOTONIC); + vdso_test_clock(CLOCK_MONOTONIC_RAW); + vdso_test_clock(CLOCK_MONOTONIC_COARSE); + vdso_test_clock(CLOCK_PROCESS_CPUTIME_ID); + vdso_test_clock(CLOCK_THREAD_CPUTIME_ID); + + vdso_test_time(); + + ksft_finished(); +} diff --git a/tools/testing/selftests/vDSO/vdso_test_chacha.c b/tools/testing/selftests/vDSO/vdso_test_chacha.c new file mode 100644 index 000000000..9a5c9c05e --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_test_chacha.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022-2024 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include <linux/compiler.h> +#include <tools/le_byteshift.h> +#include <sys/random.h> +#include <sys/auxv.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include "kselftest.h" + +#if defined(__aarch64__) +static bool cpu_has_capabilities(void) +{ + return getauxval(AT_HWCAP) & HWCAP_ASIMD; +} +#elif defined(__s390x__) +static bool cpu_has_capabilities(void) +{ + return getauxval(AT_HWCAP) & HWCAP_S390_VXRS; +} +#else +static bool cpu_has_capabilities(void) +{ + return true; +} +#endif + +static uint32_t rol32(uint32_t word, unsigned int shift) +{ + return (word << (shift & 31)) | (word >> ((-shift) & 31)); +} + +static void reference_chacha20_blocks(uint8_t *dst_bytes, const uint32_t *key, uint32_t *counter, size_t nblocks) +{ + uint32_t s[16] = { + 0x61707865U, 0x3320646eU, 0x79622d32U, 0x6b206574U, + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7], + counter[0], counter[1], 0, 0 + }; + + while (nblocks--) { + uint32_t x[16]; + memcpy(x, s, sizeof(x)); + for (unsigned int r = 0; r < 20; r += 2) { + #define QR(a, b, c, d) ( \ + x[a] += x[b], \ + x[d] = rol32(x[d] ^ x[a], 16), \ + x[c] += x[d], \ + x[b] = rol32(x[b] ^ x[c], 12), \ + x[a] += x[b], \ + x[d] = rol32(x[d] ^ x[a], 8), \ + x[c] += x[d], \ + x[b] = rol32(x[b] ^ x[c], 7)) + + QR(0, 4, 8, 12); + QR(1, 5, 9, 13); + QR(2, 6, 10, 14); + QR(3, 7, 11, 15); + QR(0, 5, 10, 15); + QR(1, 6, 11, 12); + QR(2, 7, 8, 13); + QR(3, 4, 9, 14); + } + for (unsigned int i = 0; i < 16; ++i, dst_bytes += sizeof(uint32_t)) + put_unaligned_le32(x[i] + s[i], dst_bytes); + if (!++s[12]) + ++s[13]; + } + counter[0] = s[12]; + counter[1] = s[13]; +} + +void __weak __arch_chacha20_blocks_nostack(uint8_t *dst_bytes, const uint32_t *key, uint32_t *counter, size_t nblocks) +{ + ksft_test_result_skip("Not implemented on architecture\n"); + ksft_finished(); +} + +int main(int argc, char *argv[]) +{ + enum { TRIALS = 1000, BLOCKS = 128, BLOCK_SIZE = 64 }; + uint32_t key[8], counter1[2], counter2[2]; + uint8_t output1[BLOCK_SIZE * BLOCKS], output2[BLOCK_SIZE * BLOCKS]; + + ksft_print_header(); + if (!cpu_has_capabilities()) + ksft_exit_skip("Required CPU capabilities missing\n"); + ksft_set_plan(1); + + for (unsigned int trial = 0; trial < TRIALS; ++trial) { + if (getrandom(key, sizeof(key), 0) != sizeof(key)) + ksft_exit_skip("getrandom() failed unexpectedly\n"); + memset(counter1, 0, sizeof(counter1)); + reference_chacha20_blocks(output1, key, counter1, BLOCKS); + for (unsigned int split = 0; split < BLOCKS; ++split) { + memset(output2, 'X', sizeof(output2)); + memset(counter2, 0, sizeof(counter2)); + if (split) + __arch_chacha20_blocks_nostack(output2, key, counter2, split); + __arch_chacha20_blocks_nostack(output2 + split * BLOCK_SIZE, key, counter2, BLOCKS - split); + if (memcmp(output1, output2, sizeof(output1))) + ksft_exit_fail_msg("Main loop outputs do not match on trial %u, split %u\n", trial, split); + if (memcmp(counter1, counter2, sizeof(counter1))) + ksft_exit_fail_msg("Main loop counters do not match on trial %u, split %u\n", trial, split); + } + } + memset(counter1, 0, sizeof(counter1)); + counter1[0] = (uint32_t)-BLOCKS + 2; + memset(counter2, 0, sizeof(counter2)); + counter2[0] = (uint32_t)-BLOCKS + 2; + + reference_chacha20_blocks(output1, key, counter1, BLOCKS); + __arch_chacha20_blocks_nostack(output2, key, counter2, BLOCKS); + if (memcmp(output1, output2, sizeof(output1))) + ksft_exit_fail_msg("Block limit outputs do not match after first round\n"); + if (memcmp(counter1, counter2, sizeof(counter1))) + ksft_exit_fail_msg("Block limit counters do not match after first round\n"); + + reference_chacha20_blocks(output1, key, counter1, BLOCKS); + __arch_chacha20_blocks_nostack(output2, key, counter2, BLOCKS); + if (memcmp(output1, output2, sizeof(output1))) + ksft_exit_fail_msg("Block limit outputs do not match after second round\n"); + if (memcmp(counter1, counter2, sizeof(counter1))) + ksft_exit_fail_msg("Block limit counters do not match after second round\n"); + + ksft_test_result_pass("chacha: PASS\n"); + ksft_exit_pass(); + return 0; +} diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c new file mode 100644 index 000000000..5c5a07dd1 --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ldt_gdt.c - Test cases for LDT and GDT access + * Copyright (c) 2011-2015 Andrew Lutomirski + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <sys/time.h> +#include <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/auxv.h> +#include <sys/syscall.h> +#include <string.h> +#include <errno.h> +#include <sched.h> +#include <stdbool.h> +#include <limits.h> + +#include "parse_vdso.h" +#include "vdso_config.h" +#include "vdso_call.h" +#include "kselftest.h" + +static const char *version; +static const char **name; + +#ifndef __NR_clock_gettime64 +#define __NR_clock_gettime64 403 +#endif + +#ifndef __kernel_timespec +struct __kernel_timespec { + long long tv_sec; + long long tv_nsec; +}; +#endif + +/* max length of lines in /proc/self/maps - anything longer is skipped here */ +#define MAPS_LINE_LEN 128 + +int nerrs = 0; + +typedef int (*vgettime_t)(clockid_t, struct timespec *); + +vgettime_t vdso_clock_gettime; + +typedef int (*vgettime64_t)(clockid_t, struct __kernel_timespec *); + +vgettime64_t vdso_clock_gettime64; + +typedef long (*vgtod_t)(struct timeval *tv, struct timezone *tz); + +vgtod_t vdso_gettimeofday; + +typedef time_t (*vtime_t)(__kernel_time_t *tloc); + +vtime_t vdso_time; + +typedef long (*getcpu_t)(unsigned *, unsigned *, void *); + +getcpu_t vgetcpu; +getcpu_t vdso_getcpu; + +static void *vsyscall_getcpu(void) +{ +#ifdef __x86_64__ + FILE *maps; + char line[MAPS_LINE_LEN]; + bool found = false; + + maps = fopen("/proc/self/maps", "r"); + if (!maps) /* might still be present, but ignore it here, as we test vDSO not vsyscall */ + return NULL; + + while (fgets(line, MAPS_LINE_LEN, maps)) { + char r, x; + void *start, *end; + char name[MAPS_LINE_LEN]; + + /* sscanf() is safe here as strlen(name) >= strlen(line) */ + if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s", + &start, &end, &r, &x, name) != 5) + continue; + + if (strcmp(name, "[vsyscall]")) + continue; + + /* assume entries are OK, as we test vDSO here not vsyscall */ + found = true; + break; + } + + fclose(maps); + + if (!found) { + printf("Warning: failed to find vsyscall getcpu\n"); + return NULL; + } + return (void *) (0xffffffffff600800); +#else + return NULL; +#endif +} + + +static void fill_function_pointers(void) +{ + unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + + if (!sysinfo_ehdr) { + printf("[WARN]\tfailed to find vDSO\n"); + return; + } + + vdso_init_from_sysinfo_ehdr(sysinfo_ehdr); + + vdso_getcpu = (getcpu_t)vdso_sym(version, name[4]); + if (!vdso_getcpu) + printf("Warning: failed to find getcpu in vDSO\n"); + + vgetcpu = (getcpu_t) vsyscall_getcpu(); + + vdso_clock_gettime = (vgettime_t)vdso_sym(version, name[1]); + if (!vdso_clock_gettime) + printf("Warning: failed to find clock_gettime in vDSO\n"); + +#if defined(VDSO_32BIT) + vdso_clock_gettime64 = (vgettime64_t)vdso_sym(version, name[5]); + if (!vdso_clock_gettime64) + printf("Warning: failed to find clock_gettime64 in vDSO\n"); +#endif + + vdso_gettimeofday = (vgtod_t)vdso_sym(version, name[0]); + if (!vdso_gettimeofday) + printf("Warning: failed to find gettimeofday in vDSO\n"); + + vdso_time = (vtime_t)vdso_sym(version, name[2]); + if (!vdso_time) + printf("Warning: failed to find time in vDSO\n"); + +} + +static long sys_getcpu(unsigned * cpu, unsigned * node, + void* cache) +{ + return syscall(__NR_getcpu, cpu, node, cache); +} + +static inline int sys_clock_gettime(clockid_t id, struct timespec *ts) +{ + return syscall(__NR_clock_gettime, id, ts); +} + +static inline int sys_clock_gettime64(clockid_t id, struct __kernel_timespec *ts) +{ + return syscall(__NR_clock_gettime64, id, ts); +} + +static inline int sys_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + return syscall(__NR_gettimeofday, tv, tz); +} + +static inline __kernel_old_time_t sys_time(__kernel_old_time_t *tloc) +{ +#ifdef __NR_time + return syscall(__NR_time, tloc); +#else + errno = ENOSYS; + return -1; +#endif +} + +static void test_getcpu(void) +{ + printf("[RUN]\tTesting getcpu...\n"); + + for (int cpu = 0; ; cpu++) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) + return; + + unsigned cpu_sys, cpu_vdso, cpu_vsys, + node_sys, node_vdso, node_vsys; + long ret_sys, ret_vdso = 1, ret_vsys = 1; + unsigned node; + + ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0); + if (vdso_getcpu) + ret_vdso = VDSO_CALL(vdso_getcpu, 3, &cpu_vdso, &node_vdso, 0); + if (vgetcpu) + ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0); + + if (!ret_sys) + node = node_sys; + else if (!ret_vdso) + node = node_vdso; + else if (!ret_vsys) + node = node_vsys; + + bool ok = true; + if (!ret_sys && (cpu_sys != cpu || node_sys != node)) + ok = false; + if (!ret_vdso && (cpu_vdso != cpu || node_vdso != node)) + ok = false; + if (!ret_vsys && (cpu_vsys != cpu || node_vsys != node)) + ok = false; + + printf("[%s]\tCPU %u:", ok ? "OK" : "FAIL", cpu); + if (!ret_sys) + printf(" syscall: cpu %u, node %u", cpu_sys, node_sys); + if (!ret_vdso) + printf(" vdso: cpu %u, node %u", cpu_vdso, node_vdso); + if (!ret_vsys) + printf(" vsyscall: cpu %u, node %u", cpu_vsys, + node_vsys); + printf("\n"); + + if (!ok) + nerrs++; + } +} + +static bool ts_leq(const struct timespec *a, const struct timespec *b) +{ + if (a->tv_sec != b->tv_sec) + return a->tv_sec < b->tv_sec; + else + return a->tv_nsec <= b->tv_nsec; +} + +static bool ts64_leq(const struct __kernel_timespec *a, + const struct __kernel_timespec *b) +{ + if (a->tv_sec != b->tv_sec) + return a->tv_sec < b->tv_sec; + else + return a->tv_nsec <= b->tv_nsec; +} + +static bool tv_leq(const struct timeval *a, const struct timeval *b) +{ + if (a->tv_sec != b->tv_sec) + return a->tv_sec < b->tv_sec; + else + return a->tv_usec <= b->tv_usec; +} + +static char const * const clocknames[] = { + [0] = "CLOCK_REALTIME", + [1] = "CLOCK_MONOTONIC", + [2] = "CLOCK_PROCESS_CPUTIME_ID", + [3] = "CLOCK_THREAD_CPUTIME_ID", + [4] = "CLOCK_MONOTONIC_RAW", + [5] = "CLOCK_REALTIME_COARSE", + [6] = "CLOCK_MONOTONIC_COARSE", + [7] = "CLOCK_BOOTTIME", + [8] = "CLOCK_REALTIME_ALARM", + [9] = "CLOCK_BOOTTIME_ALARM", + [10] = "CLOCK_SGI_CYCLE", + [11] = "CLOCK_TAI", +}; + +static void test_one_clock_gettime(int clock, const char *name) +{ + struct timespec start, vdso, end; + int vdso_ret, end_ret; + + printf("[RUN]\tTesting clock_gettime for clock %s (%d)...\n", name, clock); + + if (sys_clock_gettime(clock, &start) < 0) { + if (errno == EINVAL) { + vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso); + if (vdso_ret == -EINVAL) { + printf("[OK]\tNo such clock.\n"); + } else { + printf("[FAIL]\tNo such clock, but __vdso_clock_gettime returned %d\n", vdso_ret); + nerrs++; + } + } else { + printf("[WARN]\t clock_gettime(%d) syscall returned error %d\n", clock, errno); + } + return; + } + + vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso); + end_ret = sys_clock_gettime(clock, &end); + + if (vdso_ret != 0 || end_ret != 0) { + printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n", + vdso_ret, errno); + nerrs++; + return; + } + + printf("\t%llu.%09ld %llu.%09ld %llu.%09ld\n", + (unsigned long long)start.tv_sec, start.tv_nsec, + (unsigned long long)vdso.tv_sec, vdso.tv_nsec, + (unsigned long long)end.tv_sec, end.tv_nsec); + + if (!ts_leq(&start, &vdso) || !ts_leq(&vdso, &end)) { + printf("[FAIL]\tTimes are out of sequence\n"); + nerrs++; + return; + } + + printf("[OK]\tTest Passed.\n"); +} + +static void test_clock_gettime(void) +{ + if (!vdso_clock_gettime) { + printf("[SKIP]\tNo vDSO, so skipping clock_gettime() tests\n"); + return; + } + + for (int clock = 0; clock < ARRAY_SIZE(clocknames); clock++) + test_one_clock_gettime(clock, clocknames[clock]); + + /* Also test some invalid clock ids */ + test_one_clock_gettime(-1, "invalid"); + test_one_clock_gettime(INT_MIN, "invalid"); + test_one_clock_gettime(INT_MAX, "invalid"); +} + +static void test_one_clock_gettime64(int clock, const char *name) +{ + struct __kernel_timespec start, vdso, end; + int vdso_ret, end_ret; + + printf("[RUN]\tTesting clock_gettime64 for clock %s (%d)...\n", name, clock); + + if (sys_clock_gettime64(clock, &start) < 0) { + if (errno == EINVAL) { + vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso); + if (vdso_ret == -EINVAL) { + printf("[OK]\tNo such clock.\n"); + } else { + printf("[FAIL]\tNo such clock, but __vdso_clock_gettime64 returned %d\n", vdso_ret); + nerrs++; + } + } else { + printf("[WARN]\t clock_gettime64(%d) syscall returned error %d\n", clock, errno); + } + return; + } + + vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso); + end_ret = sys_clock_gettime64(clock, &end); + + if (vdso_ret != 0 || end_ret != 0) { + printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n", + vdso_ret, errno); + nerrs++; + return; + } + + printf("\t%llu.%09lld %llu.%09lld %llu.%09lld\n", + (unsigned long long)start.tv_sec, start.tv_nsec, + (unsigned long long)vdso.tv_sec, vdso.tv_nsec, + (unsigned long long)end.tv_sec, end.tv_nsec); + + if (!ts64_leq(&start, &vdso) || !ts64_leq(&vdso, &end)) { + printf("[FAIL]\tTimes are out of sequence\n"); + nerrs++; + return; + } + + printf("[OK]\tTest Passed.\n"); +} + +static void test_clock_gettime64(void) +{ + if (!vdso_clock_gettime64) { + printf("[SKIP]\tNo vDSO, so skipping clock_gettime64() tests\n"); + return; + } + + for (int clock = 0; clock < ARRAY_SIZE(clocknames); clock++) + test_one_clock_gettime64(clock, clocknames[clock]); + + /* Also test some invalid clock ids */ + test_one_clock_gettime64(-1, "invalid"); + test_one_clock_gettime64(INT_MIN, "invalid"); + test_one_clock_gettime64(INT_MAX, "invalid"); +} + +static void test_gettimeofday(void) +{ + struct timeval start, vdso, end; + struct timezone sys_tz, vdso_tz; + int vdso_ret, end_ret; + + if (!vdso_gettimeofday) + return; + + printf("[RUN]\tTesting gettimeofday...\n"); + + if (sys_gettimeofday(&start, &sys_tz) < 0) { + printf("[FAIL]\tsys_gettimeofday failed (%d)\n", errno); + nerrs++; + return; + } + + vdso_ret = VDSO_CALL(vdso_gettimeofday, 2, &vdso, &vdso_tz); + end_ret = sys_gettimeofday(&end, NULL); + + if (vdso_ret != 0 || end_ret != 0) { + printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n", + vdso_ret, errno); + nerrs++; + return; + } + + printf("\t%llu.%06lld %llu.%06lld %llu.%06lld\n", + (unsigned long long)start.tv_sec, (long long)start.tv_usec, + (unsigned long long)vdso.tv_sec, (long long)vdso.tv_usec, + (unsigned long long)end.tv_sec, (long long)end.tv_usec); + + if (!tv_leq(&start, &vdso) || !tv_leq(&vdso, &end)) { + printf("[FAIL]\tTimes are out of sequence\n"); + nerrs++; + } + + if (sys_tz.tz_minuteswest == vdso_tz.tz_minuteswest && + sys_tz.tz_dsttime == vdso_tz.tz_dsttime) { + printf("[OK]\ttimezones match: minuteswest=%d, dsttime=%d\n", + sys_tz.tz_minuteswest, sys_tz.tz_dsttime); + } else { + printf("[FAIL]\ttimezones do not match\n"); + nerrs++; + } + + /* And make sure that passing NULL for tz doesn't crash. */ + VDSO_CALL(vdso_gettimeofday, 2, &vdso, NULL); +} + +static void test_time(void) +{ + __kernel_old_time_t start, end, vdso_ret, vdso_param; + + if (!vdso_time) + return; + + printf("[RUN]\tTesting time...\n"); + + if (sys_time(&start) < 0) { + if (errno == -ENOSYS) { + printf("[SKIP]\tNo time() support\n"); + } else { + printf("[FAIL]\tsys_time failed (%d)\n", errno); + nerrs++; + } + return; + } + + vdso_ret = VDSO_CALL(vdso_time, 1, &vdso_param); + end = sys_time(NULL); + + if (vdso_ret < 0 || end < 0) { + printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n", + (int)vdso_ret, errno); + nerrs++; + return; + } + + printf("\t%lld %lld %lld\n", + (long long)start, + (long long)vdso_ret, + (long long)end); + + if (vdso_ret != vdso_param) { + printf("[FAIL]\tinconsistent return values: %lld %lld\n", + (long long)vdso_ret, (long long)vdso_param); + nerrs++; + return; + } + + if (!(start <= vdso_ret) || !(vdso_ret <= end)) { + printf("[FAIL]\tTimes are out of sequence\n"); + nerrs++; + } +} + +int main(int argc, char **argv) +{ + version = versions[VDSO_VERSION]; + name = (const char **)&names[VDSO_NAMES]; + + fill_function_pointers(); + + test_clock_gettime(); + test_clock_gettime64(); + test_gettimeofday(); + test_time(); + + /* + * Test getcpu() last so that, if something goes wrong setting affinity, + * we still run the other tests. + */ + test_getcpu(); + + return nerrs ? 1 : 0; +} diff --git a/tools/testing/selftests/vDSO/vdso_test_getcpu.c b/tools/testing/selftests/vDSO/vdso_test_getcpu.c new file mode 100644 index 000000000..3fe49cbda --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_test_getcpu.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vdso_test_getcpu.c: Sample code to test parse_vdso.c and vDSO getcpu() + * + * Copyright (c) 2020 Arm Ltd + */ + +#include <stdint.h> +#include <elf.h> +#include <stdio.h> +#include <sys/auxv.h> +#include <sys/time.h> + +#include "kselftest.h" +#include "parse_vdso.h" +#include "vdso_config.h" +#include "vdso_call.h" + +typedef long (*getcpu_t)(unsigned int *, unsigned int *, void *); + +int main(int argc, char **argv) +{ + const char *version = versions[VDSO_VERSION]; + const char **name = (const char **)&names[VDSO_NAMES]; + unsigned long sysinfo_ehdr; + unsigned int cpu, node; + getcpu_t get_cpu; + long ret; + + sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + if (!sysinfo_ehdr) { + printf("AT_SYSINFO_EHDR is not present!\n"); + return KSFT_SKIP; + } + + vdso_init_from_sysinfo_ehdr(getauxval(AT_SYSINFO_EHDR)); + + get_cpu = (getcpu_t)vdso_sym(version, name[4]); + if (!get_cpu) { + printf("Could not find %s\n", name[4]); + return KSFT_SKIP; + } + + ret = VDSO_CALL(get_cpu, 3, &cpu, &node, 0); + if (ret == 0) { + printf("Running on CPU %u node %u\n", cpu, node); + } else { + printf("%s failed\n", name[4]); + return KSFT_FAIL; + } + + return 0; +} diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c new file mode 100644 index 000000000..ef402001e --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022-2024 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include <assert.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sched.h> +#include <signal.h> +#include <sys/auxv.h> +#include <sys/mman.h> +#include <sys/random.h> +#include <sys/syscall.h> +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <linux/random.h> +#include <linux/ptrace.h> + +#include "kselftest.h" +#include "parse_vdso.h" +#include "vdso_config.h" +#include "vdso_call.h" + +#ifndef timespecsub +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + +#define ksft_assert(condition) \ + do { if (!(condition)) ksft_exit_fail_msg("Assertion failed: %s\n", #condition); } while (0) + +static struct { + pthread_mutex_t lock; + void **states; + size_t len, cap; + ssize_t(*fn)(void *, size_t, unsigned long, void *, size_t); + struct vgetrandom_opaque_params params; +} vgrnd = { + .lock = PTHREAD_MUTEX_INITIALIZER +}; + +static void *vgetrandom_get_state(void) +{ + void *state = NULL; + + pthread_mutex_lock(&vgrnd.lock); + if (!vgrnd.len) { + size_t page_size = getpagesize(); + size_t new_cap; + size_t alloc_size, num = sysconf(_SC_NPROCESSORS_ONLN); /* Just a decent heuristic. */ + size_t state_size_aligned, cache_line_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE) ?: 1; + void *new_block, *new_states; + + state_size_aligned = (vgrnd.params.size_of_opaque_state + cache_line_size - 1) & (~(cache_line_size - 1)); + alloc_size = (num * state_size_aligned + page_size - 1) & (~(page_size - 1)); + num = (page_size / state_size_aligned) * (alloc_size / page_size); + new_block = mmap(0, alloc_size, vgrnd.params.mmap_prot, vgrnd.params.mmap_flags, -1, 0); + if (new_block == MAP_FAILED) + goto out; + + new_cap = vgrnd.cap + num; + new_states = reallocarray(vgrnd.states, new_cap, sizeof(*vgrnd.states)); + if (!new_states) + goto unmap; + vgrnd.cap = new_cap; + vgrnd.states = new_states; + + for (size_t i = 0; i < num; ++i) { + if (((uintptr_t)new_block & (page_size - 1)) + vgrnd.params.size_of_opaque_state > page_size) + new_block = (void *)(((uintptr_t)new_block + page_size - 1) & (~(page_size - 1))); + vgrnd.states[i] = new_block; + new_block += state_size_aligned; + } + vgrnd.len = num; + goto success; + + unmap: + munmap(new_block, alloc_size); + goto out; + } +success: + state = vgrnd.states[--vgrnd.len]; + +out: + pthread_mutex_unlock(&vgrnd.lock); + return state; +} + +__attribute__((unused)) /* Example for libc implementors */ +static void vgetrandom_put_state(void *state) +{ + if (!state) + return; + pthread_mutex_lock(&vgrnd.lock); + vgrnd.states[vgrnd.len++] = state; + pthread_mutex_unlock(&vgrnd.lock); +} + +static void vgetrandom_init(void) +{ + const char *version = versions[VDSO_VERSION]; + const char *name = names[VDSO_NAMES][6]; + unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + ssize_t ret; + + if (!sysinfo_ehdr) + ksft_exit_skip("AT_SYSINFO_EHDR is not present\n"); + vdso_init_from_sysinfo_ehdr(sysinfo_ehdr); + vgrnd.fn = (__typeof__(vgrnd.fn))vdso_sym(version, name); + if (!vgrnd.fn) + ksft_exit_skip("%s@%s symbol is missing from vDSO\n", name, version); + ret = VDSO_CALL(vgrnd.fn, 5, NULL, 0, 0, &vgrnd.params, ~0UL); + if (ret == -ENOSYS) + ksft_exit_skip("CPU does not have runtime support\n"); + else if (ret) + ksft_exit_fail_msg("Failed to fetch vgetrandom params: %zd\n", ret); +} + +static ssize_t vgetrandom(void *buf, size_t len, unsigned long flags) +{ + static __thread void *state; + + if (!state) { + state = vgetrandom_get_state(); + ksft_assert(state); + } + return VDSO_CALL(vgrnd.fn, 5, buf, len, flags, state, vgrnd.params.size_of_opaque_state); +} + +enum { TRIALS = 25000000, THREADS = 256 }; + +static void *test_vdso_getrandom(void *ctx) +{ + for (size_t i = 0; i < TRIALS; ++i) { + unsigned int val; + ssize_t ret = vgetrandom(&val, sizeof(val), 0); + ksft_assert(ret == sizeof(val)); + } + return NULL; +} + +static void *test_libc_getrandom(void *ctx) +{ + for (size_t i = 0; i < TRIALS; ++i) { + unsigned int val; + ssize_t ret = getrandom(&val, sizeof(val), 0); + ksft_assert(ret == sizeof(val)); + } + return NULL; +} + +static void *test_syscall_getrandom(void *ctx) +{ + for (size_t i = 0; i < TRIALS; ++i) { + unsigned int val; + ssize_t ret = syscall(__NR_getrandom, &val, sizeof(val), 0); + ksft_assert(ret == sizeof(val)); + } + return NULL; +} + +static void bench_single(void) +{ + struct timespec start, end, diff; + + clock_gettime(CLOCK_MONOTONIC, &start); + test_vdso_getrandom(NULL); + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &diff); + printf(" vdso: %u times in %lu.%09lu seconds\n", TRIALS, diff.tv_sec, diff.tv_nsec); + + clock_gettime(CLOCK_MONOTONIC, &start); + test_libc_getrandom(NULL); + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &diff); + printf(" libc: %u times in %lu.%09lu seconds\n", TRIALS, diff.tv_sec, diff.tv_nsec); + + clock_gettime(CLOCK_MONOTONIC, &start); + test_syscall_getrandom(NULL); + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &diff); + printf("syscall: %u times in %lu.%09lu seconds\n", TRIALS, diff.tv_sec, diff.tv_nsec); +} + +static void bench_multi(void) +{ + struct timespec start, end, diff; + pthread_t threads[THREADS]; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (size_t i = 0; i < THREADS; ++i) + ksft_assert(pthread_create(&threads[i], NULL, test_vdso_getrandom, NULL) == 0); + for (size_t i = 0; i < THREADS; ++i) + pthread_join(threads[i], NULL); + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &diff); + printf(" vdso: %u x %u times in %lu.%09lu seconds\n", TRIALS, THREADS, diff.tv_sec, diff.tv_nsec); + + clock_gettime(CLOCK_MONOTONIC, &start); + for (size_t i = 0; i < THREADS; ++i) + ksft_assert(pthread_create(&threads[i], NULL, test_libc_getrandom, NULL) == 0); + for (size_t i = 0; i < THREADS; ++i) + pthread_join(threads[i], NULL); + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &diff); + printf(" libc: %u x %u times in %lu.%09lu seconds\n", TRIALS, THREADS, diff.tv_sec, diff.tv_nsec); + + clock_gettime(CLOCK_MONOTONIC, &start); + for (size_t i = 0; i < THREADS; ++i) + ksft_assert(pthread_create(&threads[i], NULL, test_syscall_getrandom, NULL) == 0); + for (size_t i = 0; i < THREADS; ++i) + pthread_join(threads[i], NULL); + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &diff); + printf(" syscall: %u x %u times in %lu.%09lu seconds\n", TRIALS, THREADS, diff.tv_sec, diff.tv_nsec); +} + +static void fill(void) +{ + uint8_t weird_size[323929]; + for (;;) + vgetrandom(weird_size, sizeof(weird_size), 0); +} + +static void kselftest(void) +{ + uint8_t weird_size[1263]; + pid_t child; + + ksft_print_header(); + vgetrandom_init(); + ksft_set_plan(2); + + for (size_t i = 0; i < 1000; ++i) { + ssize_t ret = vgetrandom(weird_size, sizeof(weird_size), 0); + ksft_assert(ret == sizeof(weird_size)); + } + + ksft_test_result_pass("getrandom: PASS\n"); + + unshare(CLONE_NEWUSER); + ksft_assert(unshare(CLONE_NEWTIME) == 0); + child = fork(); + ksft_assert(child >= 0); + if (!child) { + vgetrandom_init(); + child = getpid(); + ksft_assert(ptrace(PTRACE_TRACEME, 0, NULL, NULL) == 0); + ksft_assert(kill(child, SIGSTOP) == 0); + ksft_assert(vgetrandom(weird_size, sizeof(weird_size), 0) == sizeof(weird_size)); + _exit(0); + } + for (;;) { + struct ptrace_syscall_info info = { 0 }; + int status; + ksft_assert(waitpid(child, &status, 0) >= 0); + if (WIFEXITED(status)) { + ksft_assert(WEXITSTATUS(status) == 0); + break; + } + ksft_assert(WIFSTOPPED(status)); + if (WSTOPSIG(status) == SIGSTOP) + ksft_assert(ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD) == 0); + else if (WSTOPSIG(status) == (SIGTRAP | 0x80)) { + ksft_assert(ptrace(PTRACE_GET_SYSCALL_INFO, child, sizeof(info), &info) > 0); + if (info.op == PTRACE_SYSCALL_INFO_ENTRY && info.entry.nr == __NR_getrandom && + info.entry.args[0] == (uintptr_t)weird_size && info.entry.args[1] == sizeof(weird_size)) + ksft_exit_fail_msg("vgetrandom passed buffer to syscall getrandom unexpectedly\n"); + } + ksft_assert(ptrace(PTRACE_SYSCALL, child, 0, 0) == 0); + } + + ksft_test_result_pass("getrandom timens: PASS\n"); + + ksft_exit_pass(); +} + +static void usage(const char *argv0) +{ + fprintf(stderr, "Usage: %s [bench-single|bench-multi|fill]\n", argv0); +} + +int main(int argc, char *argv[]) +{ + if (argc == 1) { + kselftest(); + return 0; + } + + if (argc != 2) { + usage(argv[0]); + return 1; + } + + vgetrandom_init(); + + if (!strcmp(argv[1], "bench-single")) + bench_single(); + else if (!strcmp(argv[1], "bench-multi")) + bench_multi(); + else if (!strcmp(argv[1], "fill")) + fill(); + else { + usage(argv[0]); + return 1; + } + return 0; +} diff --git a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c new file mode 100644 index 000000000..990b29e0e --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vdso_test_gettimeofday.c: Sample code to test parse_vdso.c and + * vDSO gettimeofday() + * Copyright (c) 2014 Andy Lutomirski + * + * Compile with: + * gcc -std=gnu99 vdso_test_gettimeofday.c parse_vdso_gettimeofday.c + * + * Tested on x86, 32-bit and 64-bit. It may work on other architectures, too. + */ + +#include <stdio.h> +#include <sys/auxv.h> +#include <sys/time.h> + +#include "kselftest.h" +#include "parse_vdso.h" +#include "vdso_config.h" +#include "vdso_call.h" + +int main(int argc, char **argv) +{ + const char *version = versions[VDSO_VERSION]; + const char **name = (const char **)&names[VDSO_NAMES]; + + unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + if (!sysinfo_ehdr) { + printf("AT_SYSINFO_EHDR is not present!\n"); + return KSFT_SKIP; + } + + vdso_init_from_sysinfo_ehdr(getauxval(AT_SYSINFO_EHDR)); + + /* Find gettimeofday. */ + typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz); + gtod_t gtod = (gtod_t)vdso_sym(version, name[0]); + + if (!gtod) { + printf("Could not find %s\n", name[0]); + return KSFT_SKIP; + } + + struct timeval tv; + long ret = VDSO_CALL(gtod, 2, &tv, 0); + + if (ret == 0) { + printf("The time is %lld.%06lld\n", + (long long)tv.tv_sec, (long long)tv.tv_usec); + } else { + printf("%s failed\n", name[0]); + return KSFT_FAIL; + } + + return 0; +} diff --git a/tools/testing/selftests/vDSO/vgetrandom-chacha.S b/tools/testing/selftests/vDSO/vgetrandom-chacha.S new file mode 100644 index 000000000..16f985b08 --- /dev/null +++ b/tools/testing/selftests/vDSO/vgetrandom-chacha.S @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#define __ASSEMBLY__ + +#if defined(__aarch64__) +#include "../../../../arch/arm64/kernel/vdso/vgetrandom-chacha.S" +#elif defined(__loongarch__) +#include "../../../../arch/loongarch/vdso/vgetrandom-chacha.S" +#elif defined(__powerpc__) || defined(__powerpc64__) +#include "../../../../arch/powerpc/kernel/vdso/vgetrandom-chacha.S" +#elif defined(__riscv) && __riscv_xlen == 64 +#include "../../../../arch/riscv/kernel/vdso/vgetrandom-chacha.S" +#elif defined(__s390x__) +#include "../../../../arch/s390/kernel/vdso/vgetrandom-chacha.S" +#elif defined(__x86_64__) +#include "../../../../arch/x86/entry/vdso/vdso64/vgetrandom-chacha.S" +#endif |
