1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2019 ARM Limited */
#ifndef __TEST_SIGNALS_UTILS_H__
#define __TEST_SIGNALS_UTILS_H__
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <linux/compiler.h>
#include "test_signals.h"
int test_init(struct tdescr *td);
int test_setup(struct tdescr *td);
void test_cleanup(struct tdescr *td);
int test_run(struct tdescr *td);
void test_result(struct tdescr *td);
#ifndef __NR_prctl
#define __NR_prctl 167
#endif
/*
* The prctl takes 1 argument but we need to ensure that the other
* values passed in registers to the syscall are zero since the kernel
* validates them.
*/
#define gcs_set_state(state) \
({ \
register long _num __asm__ ("x8") = __NR_prctl; \
register long _arg1 __asm__ ("x0") = PR_SET_SHADOW_STACK_STATUS; \
register long _arg2 __asm__ ("x1") = (long)(state); \
register long _arg3 __asm__ ("x2") = 0; \
register long _arg4 __asm__ ("x3") = 0; \
register long _arg5 __asm__ ("x4") = 0; \
\
__asm__ volatile ( \
"svc #0\n" \
: "=r"(_arg1) \
: "r"(_arg1), "r"(_arg2), \
"r"(_arg3), "r"(_arg4), \
"r"(_arg5), "r"(_num) \
: "memory", "cc" \
); \
_arg1; \
})
static inline __attribute__((always_inline)) uint64_t get_gcspr_el0(void)
{
uint64_t val;
asm volatile("mrs %0, S3_3_C2_C5_1" : "=r" (val));
return val;
}
static inline bool feats_ok(struct tdescr *td)
{
if (td->feats_incompatible & td->feats_supported)
return false;
return (td->feats_required & td->feats_supported) == td->feats_required;
}
/*
* Obtaining a valid and full-blown ucontext_t from userspace is tricky:
* libc getcontext does() not save all the regs and messes with some of
* them (pstate value in particular is not reliable).
*
* Here we use a service signal to grab the ucontext_t from inside a
* dedicated signal handler, since there, it is populated by Kernel
* itself in setup_sigframe(). The grabbed context is then stored and
* made available in td->live_uc.
*
* As service-signal is used a SIGTRAP induced by a 'brk' instruction,
* because here we have to avoid syscalls to trigger the signal since
* they would cause any SVE sigframe content (if any) to be removed.
*
* Anyway this function really serves a dual purpose:
*
* 1. grab a valid sigcontext into td->live_uc for result analysis: in
* such case it returns 1.
*
* 2. detect if, somehow, a previously grabbed live_uc context has been
* used actively with a sigreturn: in such a case the execution would have
* magically resumed in the middle of this function itself (seen_already==1):
* in such a case return 0, since in fact we have not just simply grabbed
* the context.
*
* This latter case is useful to detect when a fake_sigreturn test-case has
* unexpectedly survived without hitting a SEGV.
*
* Note that the case of runtime dynamically sized sigframes (like in SVE
* context) is still NOT addressed: sigframe size is supposed to be fixed
* at sizeof(ucontext_t).
*/
static __always_inline bool get_current_context(struct tdescr *td,
ucontext_t *dest_uc,
size_t dest_sz)
{
static volatile bool seen_already;
int i;
char *uc = (char *)dest_uc;
assert(td && dest_uc);
/* it's a genuine invocation..reinit */
seen_already = 0;
td->live_uc_valid = 0;
td->live_sz = dest_sz;
/*
* This is a memset() but we don't want the compiler to
* optimise it into either instructions or a library call
* which might be incompatible with streaming mode.
*/
for (i = 0; i < td->live_sz; i++) {
uc[i] = 0;
OPTIMIZER_HIDE_VAR(uc[0]);
}
td->live_uc = dest_uc;
/*
* Grab ucontext_t triggering a SIGTRAP.
*
* Note that:
* - live_uc_valid is declared volatile sig_atomic_t in
* struct tdescr since it will be changed inside the
* sig_copyctx handler
* - the additional 'memory' clobber is there to avoid possible
* compiler's assumption on live_uc_valid and the content
* pointed by dest_uc, which are all changed inside the signal
* handler
* - BRK causes a debug exception which is handled by the Kernel
* and finally causes the SIGTRAP signal to be delivered to this
* test thread. Since such delivery happens on the ret_to_user()
* /do_notify_resume() debug exception return-path, we are sure
* that the registered SIGTRAP handler has been run to completion
* before the execution path is restored here: as a consequence
* we can be sure that the volatile sig_atomic_t live_uc_valid
* carries a meaningful result. Being in a single thread context
* we'll also be sure that any access to memory modified by the
* handler (namely ucontext_t) will be visible once returned.
* - note that since we are using a breakpoint instruction here
* to cause a SIGTRAP, the ucontext_t grabbed from the signal
* handler would naturally contain a PC pointing exactly to this
* BRK line, which means that, on return from the signal handler,
* or if we place the ucontext_t on the stack to fake a sigreturn,
* we'll end up in an infinite loop of BRK-SIGTRAP-handler.
* For this reason we take care to artificially move forward the
* PC to the next instruction while inside the signal handler.
*/
asm volatile ("brk #666"
: "+m" (*dest_uc)
:
: "memory");
/*
* If we were grabbing a streaming mode context then we may
* have entered streaming mode behind the system's back and
* libc or compiler generated code might decide to do
* something invalid in streaming mode, or potentially even
* the state of ZA. Issue a SMSTOP to exit both now we have
* grabbed the state.
*/
if (td->feats_supported & FEAT_SME)
asm volatile("msr S0_3_C4_C6_3, xzr");
/*
* If we get here with seen_already==1 it implies the td->live_uc
* context has been used to get back here....this probably means
* a test has failed to cause a SEGV...anyway live_uc does not
* point to a just acquired copy of ucontext_t...so return 0
*/
if (seen_already) {
fprintf(stdout,
"Unexpected successful sigreturn detected: live_uc is stale !\n");
return 0;
}
seen_already = 1;
return td->live_uc_valid;
}
int fake_sigreturn(void *sigframe, size_t sz, int misalign_bytes);
#endif
|