#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"
extern __weak ptrdiff_t __rseq_offset;
extern __weak unsigned int __rseq_size;
extern __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;
ptrdiff_t rseq_offset;
unsigned int rseq_size = -1U;
unsigned int rseq_flags;
static int rseq_ownership;
#define RSEQ_THREAD_AREA_ALLOC_SIZE 1024
#define ORIG_RSEQ_FEATURE_SIZE 20
#define ORIG_RSEQ_ALLOC_SIZE 32
union rseq_tls {
struct rseq_abi abi;
char dummy[RSEQ_THREAD_AREA_ALLOC_SIZE];
};
static
__thread union rseq_tls __rseq __attribute__((tls_model("initial-exec"))) = {
.abi = {
.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);
}
bool rseq_available(void)
{
int rc;
rc = sys_rseq(NULL, 0, 0, 0);
if (rc != -1)
abort();
switch (errno) {
case ENOSYS:
return false;
case EINVAL:
return true;
default:
abort();
}
}
static
unsigned int get_rseq_min_alloc_size(void)
{
unsigned int alloc_size = rseq_size;
if (alloc_size < ORIG_RSEQ_ALLOC_SIZE)
alloc_size = ORIG_RSEQ_ALLOC_SIZE;
return alloc_size;
}
static
unsigned int get_rseq_kernel_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;
}
int rseq_register_current_thread(void)
{
int rc;
if (!rseq_ownership) {
return 0;
}
rc = sys_rseq(&__rseq.abi, get_rseq_min_alloc_size(), 0, RSEQ_SIG);
if (rc) {
if (RSEQ_READ_ONCE(rseq_size) > 0) {
abort();
}
return -1;
}
assert(rseq_current_cpu_raw() >= 0);
if (RSEQ_READ_ONCE(rseq_size) == 0) {
RSEQ_WRITE_ONCE(rseq_size, get_rseq_kernel_feature_size());
}
return 0;
}
int rseq_unregister_current_thread(void)
{
int rc;
if (!rseq_ownership) {
return 0;
}
rc = sys_rseq(&__rseq.abi, get_rseq_min_alloc_size(), RSEQ_ABI_FLAG_UNREGISTER, RSEQ_SIG);
if (rc)
return -1;
return 0;
}
static __attribute__((constructor))
void rseq_init(void)
{
if (!libc_rseq_size_p || !*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) {
unsigned int libc_rseq_size;
rseq_offset = *libc_rseq_offset_p;
libc_rseq_size = *libc_rseq_size_p;
rseq_flags = *libc_rseq_flags_p;
switch (libc_rseq_size) {
case ORIG_RSEQ_FEATURE_SIZE:
fallthrough;
case ORIG_RSEQ_ALLOC_SIZE:
{
unsigned int rseq_kernel_feature_size = get_rseq_kernel_feature_size();
if (rseq_kernel_feature_size < ORIG_RSEQ_ALLOC_SIZE)
rseq_size = rseq_kernel_feature_size;
else
rseq_size = ORIG_RSEQ_ALLOC_SIZE;
break;
}
default:
rseq_size = libc_rseq_size;
break;
}
return;
}
rseq_ownership = 1;
rseq_offset = (void *)&__rseq.abi - rseq_thread_pointer();
rseq_flags = 0;
rseq_size = 0;
}
static __attribute__((destructor))
void rseq_exit(void)
{
if (!rseq_ownership)
return;
rseq_offset = 0;
rseq_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;
}