| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * rseq.c |
| * |
| * Copyright (C) 2016 Mathieu Desnoyers <mathieu.desnoyers@efficios.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; only |
| * version 2.1 of the License. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <sched.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <syscall.h> |
| #include <assert.h> |
| #include <signal.h> |
| #include <limits.h> |
| #include <dlfcn.h> |
| #include <stddef.h> |
| #include <sys/auxv.h> |
| #include <linux/auxvec.h> |
| |
| #include <linux/compiler.h> |
| |
| #include "../kselftest.h" |
| #include "rseq.h" |
| |
| /* |
| * Define weak versions to play nice with binaries that are statically linked |
| * against a libc that doesn't support registering its own rseq. |
| */ |
| __weak ptrdiff_t __rseq_offset; |
| __weak unsigned int __rseq_size; |
| __weak unsigned int __rseq_flags; |
| |
| static const ptrdiff_t *libc_rseq_offset_p = &__rseq_offset; |
| static const unsigned int *libc_rseq_size_p = &__rseq_size; |
| static const unsigned int *libc_rseq_flags_p = &__rseq_flags; |
| |
| /* Offset from the thread pointer to the rseq area. */ |
| ptrdiff_t rseq_offset; |
| |
| /* |
| * Size of the registered rseq area. 0 if the registration was |
| * unsuccessful. |
| */ |
| unsigned int rseq_size = -1U; |
| |
| /* Flags used during rseq registration. */ |
| unsigned int rseq_flags; |
| |
| /* |
| * rseq feature size supported by the kernel. 0 if the registration was |
| * unsuccessful. |
| */ |
| unsigned int rseq_feature_size = -1U; |
| |
| static int rseq_ownership; |
| static int rseq_reg_success; /* At least one rseq registration has succeded. */ |
| |
| /* Allocate a large area for the TLS. */ |
| #define RSEQ_THREAD_AREA_ALLOC_SIZE 1024 |
| |
| /* Original struct rseq feature size is 20 bytes. */ |
| #define ORIG_RSEQ_FEATURE_SIZE 20 |
| |
| /* Original struct rseq allocation size is 32 bytes. */ |
| #define ORIG_RSEQ_ALLOC_SIZE 32 |
| |
| static |
| __thread struct rseq_abi __rseq_abi __attribute__((tls_model("initial-exec"), aligned(RSEQ_THREAD_AREA_ALLOC_SIZE))) = { |
| .cpu_id = RSEQ_ABI_CPU_ID_UNINITIALIZED, |
| }; |
| |
| static int sys_rseq(struct rseq_abi *rseq_abi, uint32_t rseq_len, |
| int flags, uint32_t sig) |
| { |
| return syscall(__NR_rseq, rseq_abi, rseq_len, flags, sig); |
| } |
| |
| static int sys_getcpu(unsigned *cpu, unsigned *node) |
| { |
| return syscall(__NR_getcpu, cpu, node, NULL); |
| } |
| |
| int rseq_available(void) |
| { |
| int rc; |
| |
| rc = sys_rseq(NULL, 0, 0, 0); |
| if (rc != -1) |
| abort(); |
| switch (errno) { |
| case ENOSYS: |
| return 0; |
| case EINVAL: |
| return 1; |
| default: |
| abort(); |
| } |
| } |
| |
| int rseq_register_current_thread(void) |
| { |
| int rc; |
| |
| if (!rseq_ownership) { |
| /* Treat libc's ownership as a successful registration. */ |
| return 0; |
| } |
| rc = sys_rseq(&__rseq_abi, rseq_size, 0, RSEQ_SIG); |
| if (rc) { |
| if (RSEQ_READ_ONCE(rseq_reg_success)) { |
| /* Incoherent success/failure within process. */ |
| abort(); |
| } |
| return -1; |
| } |
| assert(rseq_current_cpu_raw() >= 0); |
| RSEQ_WRITE_ONCE(rseq_reg_success, 1); |
| return 0; |
| } |
| |
| int rseq_unregister_current_thread(void) |
| { |
| int rc; |
| |
| if (!rseq_ownership) { |
| /* Treat libc's ownership as a successful unregistration. */ |
| return 0; |
| } |
| rc = sys_rseq(&__rseq_abi, rseq_size, RSEQ_ABI_FLAG_UNREGISTER, RSEQ_SIG); |
| if (rc) |
| return -1; |
| return 0; |
| } |
| |
| static |
| unsigned int get_rseq_feature_size(void) |
| { |
| unsigned long auxv_rseq_feature_size, auxv_rseq_align; |
| |
| auxv_rseq_align = getauxval(AT_RSEQ_ALIGN); |
| assert(!auxv_rseq_align || auxv_rseq_align <= RSEQ_THREAD_AREA_ALLOC_SIZE); |
| |
| auxv_rseq_feature_size = getauxval(AT_RSEQ_FEATURE_SIZE); |
| assert(!auxv_rseq_feature_size || auxv_rseq_feature_size <= RSEQ_THREAD_AREA_ALLOC_SIZE); |
| if (auxv_rseq_feature_size) |
| return auxv_rseq_feature_size; |
| else |
| return ORIG_RSEQ_FEATURE_SIZE; |
| } |
| |
| static __attribute__((constructor)) |
| void rseq_init(void) |
| { |
| /* |
| * If the libc's registered rseq size isn't already valid, it may be |
| * because the binary is dynamically linked and not necessarily due to |
| * libc not having registered a restartable sequence. Try to find the |
| * symbols if that's the case. |
| */ |
| if (!*libc_rseq_size_p) { |
| libc_rseq_offset_p = dlsym(RTLD_NEXT, "__rseq_offset"); |
| libc_rseq_size_p = dlsym(RTLD_NEXT, "__rseq_size"); |
| libc_rseq_flags_p = dlsym(RTLD_NEXT, "__rseq_flags"); |
| } |
| if (libc_rseq_size_p && libc_rseq_offset_p && libc_rseq_flags_p && |
| *libc_rseq_size_p != 0) { |
| /* rseq registration owned by glibc */ |
| rseq_offset = *libc_rseq_offset_p; |
| rseq_size = *libc_rseq_size_p; |
| rseq_flags = *libc_rseq_flags_p; |
| rseq_feature_size = get_rseq_feature_size(); |
| if (rseq_feature_size > rseq_size) |
| rseq_feature_size = rseq_size; |
| return; |
| } |
| rseq_ownership = 1; |
| if (!rseq_available()) { |
| rseq_size = 0; |
| rseq_feature_size = 0; |
| return; |
| } |
| rseq_offset = (void *)&__rseq_abi - rseq_thread_pointer(); |
| rseq_flags = 0; |
| rseq_feature_size = get_rseq_feature_size(); |
| if (rseq_feature_size == ORIG_RSEQ_FEATURE_SIZE) |
| rseq_size = ORIG_RSEQ_ALLOC_SIZE; |
| else |
| rseq_size = RSEQ_THREAD_AREA_ALLOC_SIZE; |
| } |
| |
| static __attribute__((destructor)) |
| void rseq_exit(void) |
| { |
| if (!rseq_ownership) |
| return; |
| rseq_offset = 0; |
| rseq_size = -1U; |
| rseq_feature_size = -1U; |
| rseq_ownership = 0; |
| } |
| |
| int32_t rseq_fallback_current_cpu(void) |
| { |
| int32_t cpu; |
| |
| cpu = sched_getcpu(); |
| if (cpu < 0) { |
| perror("sched_getcpu()"); |
| abort(); |
| } |
| return cpu; |
| } |
| |
| int32_t rseq_fallback_current_node(void) |
| { |
| uint32_t cpu_id, node_id; |
| int ret; |
| |
| ret = sys_getcpu(&cpu_id, &node_id); |
| if (ret) { |
| perror("sys_getcpu()"); |
| return ret; |
| } |
| return (int32_t) node_id; |
| } |