cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

tm-trap.c (9475B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * Copyright 2017, Gustavo Romero, IBM Corp.
      4 *
      5 * Check if thread endianness is flipped inadvertently to BE on trap
      6 * caught in TM whilst MSR.FP and MSR.VEC are zero (i.e. just after
      7 * load_fp and load_vec overflowed).
      8 *
      9 * The issue can be checked on LE machines simply by zeroing load_fp
     10 * and load_vec and then causing a trap in TM. Since the endianness
     11 * changes to BE on return from the signal handler, 'nop' is
     12 * thread as an illegal instruction in following sequence:
     13 *	tbegin.
     14 *	beq 1f
     15 *	trap
     16 *	tend.
     17 * 1:	nop
     18 *
     19 * However, although the issue is also present on BE machines, it's a
     20 * bit trickier to check it on BE machines because MSR.LE bit is set
     21 * to zero which determines a BE endianness that is the native
     22 * endianness on BE machines, so nothing notably critical happens,
     23 * i.e. no illegal instruction is observed immediately after returning
     24 * from the signal handler (as it happens on LE machines). Thus to test
     25 * it on BE machines LE endianness is forced after a first trap and then
     26 * the endianness is verified on subsequent traps to determine if the
     27 * endianness "flipped back" to the native endianness (BE).
     28 */
     29
     30#define _GNU_SOURCE
     31#include <error.h>
     32#include <stdio.h>
     33#include <stdlib.h>
     34#include <unistd.h>
     35#include <htmintrin.h>
     36#include <inttypes.h>
     37#include <pthread.h>
     38#include <sched.h>
     39#include <signal.h>
     40#include <stdbool.h>
     41
     42#include "tm.h"
     43#include "utils.h"
     44
     45#define pr_error(error_code, format, ...) \
     46	error_at_line(1, error_code, __FILE__, __LINE__, format, ##__VA_ARGS__)
     47
     48#define MSR_LE 1UL
     49#define LE     1UL
     50
     51pthread_t t0_ping;
     52pthread_t t1_pong;
     53
     54int exit_from_pong;
     55
     56int trap_event;
     57int le;
     58
     59bool success;
     60
     61void trap_signal_handler(int signo, siginfo_t *si, void *uc)
     62{
     63	ucontext_t *ucp = uc;
     64	uint64_t thread_endianness;
     65
     66	/* Get thread endianness: extract bit LE from MSR */
     67	thread_endianness = MSR_LE & ucp->uc_mcontext.gp_regs[PT_MSR];
     68
     69	/*
     70	 * Little-Endian Machine
     71	 */
     72
     73	if (le) {
     74		/* First trap event */
     75		if (trap_event == 0) {
     76			/* Do nothing. Since it is returning from this trap
     77			 * event that endianness is flipped by the bug, so just
     78			 * let the process return from the signal handler and
     79			 * check on the second trap event if endianness is
     80			 * flipped or not.
     81			 */
     82		}
     83		/* Second trap event */
     84		else if (trap_event == 1) {
     85			/*
     86			 * Since trap was caught in TM on first trap event, if
     87			 * endianness was still LE (not flipped inadvertently)
     88			 * after returning from the signal handler instruction
     89			 * (1) is executed (basically a 'nop'), as it's located
     90			 * at address of tbegin. +4 (rollback addr). As (1) on
     91			 * LE endianness does in effect nothing, instruction (2)
     92			 * is then executed again as 'trap', generating a second
     93			 * trap event (note that in that case 'trap' is caught
     94			 * not in transacional mode). On te other hand, if after
     95			 * the return from the signal handler the endianness in-
     96			 * advertently flipped, instruction (1) is tread as a
     97			 * branch instruction, i.e. b .+8, hence instruction (3)
     98			 * and (4) are executed (tbegin.; trap;) and we get sim-
     99			 * ilaly on the trap signal handler, but now in TM mode.
    100			 * Either way, it's now possible to check the MSR LE bit
    101			 * once in the trap handler to verify if endianness was
    102			 * flipped or not after the return from the second trap
    103			 * event. If endianness is flipped, the bug is present.
    104			 * Finally, getting a trap in TM mode or not is just
    105			 * worth noting because it affects the math to determine
    106			 * the offset added to the NIP on return: the NIP for a
    107			 * trap caught in TM is the rollback address, i.e. the
    108			 * next instruction after 'tbegin.', whilst the NIP for
    109			 * a trap caught in non-transactional mode is the very
    110			 * same address of the 'trap' instruction that generated
    111			 * the trap event.
    112			 */
    113
    114			if (thread_endianness == LE) {
    115				/* Go to 'success', i.e. instruction (6) */
    116				ucp->uc_mcontext.gp_regs[PT_NIP] += 16;
    117			} else {
    118				/*
    119				 * Thread endianness is BE, so it flipped
    120				 * inadvertently. Thus we flip back to LE and
    121				 * set NIP to go to 'failure', instruction (5).
    122				 */
    123				ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL;
    124				ucp->uc_mcontext.gp_regs[PT_NIP] += 4;
    125			}
    126		}
    127	}
    128
    129	/*
    130	 * Big-Endian Machine
    131	 */
    132
    133	else {
    134		/* First trap event */
    135		if (trap_event == 0) {
    136			/*
    137			 * Force thread endianness to be LE. Instructions (1),
    138			 * (3), and (4) will be executed, generating a second
    139			 * trap in TM mode.
    140			 */
    141			ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL;
    142		}
    143		/* Second trap event */
    144		else if (trap_event == 1) {
    145			/*
    146			 * Do nothing. If bug is present on return from this
    147			 * second trap event endianness will flip back "automat-
    148			 * ically" to BE, otherwise thread endianness will
    149			 * continue to be LE, just as it was set above.
    150			 */
    151		}
    152		/* A third trap event */
    153		else {
    154			/*
    155			 * Once here it means that after returning from the sec-
    156			 * ond trap event instruction (4) (trap) was executed
    157			 * as LE, generating a third trap event. In that case
    158			 * endianness is still LE as set on return from the
    159			 * first trap event, hence no bug. Otherwise, bug
    160			 * flipped back to BE on return from the second trap
    161			 * event and instruction (4) was executed as 'tdi' (so
    162			 * basically a 'nop') and branch to 'failure' in
    163			 * instruction (5) was taken to indicate failure and we
    164			 * never get here.
    165			 */
    166
    167			/*
    168			 * Flip back to BE and go to instruction (6), i.e. go to
    169			 * 'success'.
    170			 */
    171			ucp->uc_mcontext.gp_regs[PT_MSR] &= ~1UL;
    172			ucp->uc_mcontext.gp_regs[PT_NIP] += 8;
    173		}
    174	}
    175
    176	trap_event++;
    177}
    178
    179void usr1_signal_handler(int signo, siginfo_t *si, void *not_used)
    180{
    181	/* Got a USR1 signal from ping(), so just tell pong() to exit */
    182	exit_from_pong = 1;
    183}
    184
    185void *ping(void *not_used)
    186{
    187	uint64_t i;
    188
    189	trap_event = 0;
    190
    191	/*
    192	 * Wait an amount of context switches so load_fp and load_vec overflows
    193	 * and MSR_[FP|VEC|V] is 0.
    194	 */
    195	for (i = 0; i < 1024*1024*512; i++)
    196		;
    197
    198	asm goto(
    199		/*
    200		 * [NA] means "Native Endianness", i.e. it tells how a
    201		 * instruction is executed on machine's native endianness (in
    202		 * other words, native endianness matches kernel endianness).
    203		 * [OP] means "Opposite Endianness", i.e. on a BE machine, it
    204		 * tells how a instruction is executed as a LE instruction; con-
    205		 * versely, on a LE machine, it tells how a instruction is
    206		 * executed as a BE instruction. When [NA] is omitted, it means
    207		 * that the native interpretation of a given instruction is not
    208		 * relevant for the test. Likewise when [OP] is omitted.
    209		 */
    210
    211		" tbegin.        ;" /* (0) tbegin. [NA]                    */
    212		" tdi  0, 0, 0x48;" /* (1) nop     [NA]; b (3) [OP]        */
    213		" trap           ;" /* (2) trap    [NA]                    */
    214		".long 0x1D05007C;" /* (3) tbegin. [OP]                    */
    215		".long 0x0800E07F;" /* (4) trap    [OP]; nop   [NA]        */
    216		" b %l[failure]  ;" /* (5) b [NA]; MSR.LE flipped (bug)    */
    217		" b %l[success]  ;" /* (6) b [NA]; MSR.LE did not flip (ok)*/
    218
    219		: : : : failure, success);
    220
    221failure:
    222	success = false;
    223	goto exit_from_ping;
    224
    225success:
    226	success = true;
    227
    228exit_from_ping:
    229	/* Tell pong() to exit before leaving */
    230	pthread_kill(t1_pong, SIGUSR1);
    231	return NULL;
    232}
    233
    234void *pong(void *not_used)
    235{
    236	while (!exit_from_pong)
    237		/*
    238		 * Induce context switches on ping() thread
    239		 * until ping() finishes its job and signs
    240		 * to exit from this loop.
    241		 */
    242		sched_yield();
    243
    244	return NULL;
    245}
    246
    247int tm_trap_test(void)
    248{
    249	uint16_t k = 1;
    250	int cpu, rc;
    251
    252	pthread_attr_t attr;
    253	cpu_set_t cpuset;
    254
    255	struct sigaction trap_sa;
    256
    257	SKIP_IF(!have_htm());
    258	SKIP_IF(htm_is_synthetic());
    259
    260	trap_sa.sa_flags = SA_SIGINFO;
    261	trap_sa.sa_sigaction = trap_signal_handler;
    262	sigaction(SIGTRAP, &trap_sa, NULL);
    263
    264	struct sigaction usr1_sa;
    265
    266	usr1_sa.sa_flags = SA_SIGINFO;
    267	usr1_sa.sa_sigaction = usr1_signal_handler;
    268	sigaction(SIGUSR1, &usr1_sa, NULL);
    269
    270	cpu = pick_online_cpu();
    271	FAIL_IF(cpu < 0);
    272
    273	// Set only one CPU in the mask. Both threads will be bound to that CPU.
    274	CPU_ZERO(&cpuset);
    275	CPU_SET(cpu, &cpuset);
    276
    277	/* Init pthread attribute */
    278	rc = pthread_attr_init(&attr);
    279	if (rc)
    280		pr_error(rc, "pthread_attr_init()");
    281
    282	/*
    283	 * Bind thread ping() and pong() both to CPU 0 so they ping-pong and
    284	 * speed up context switches on ping() thread, speeding up the load_fp
    285	 * and load_vec overflow.
    286	 */
    287	rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
    288	if (rc)
    289		pr_error(rc, "pthread_attr_setaffinity()");
    290
    291	/* Figure out the machine endianness */
    292	le = (int) *(uint8_t *)&k;
    293
    294	printf("%s machine detected. Checking if endianness flips %s",
    295		le ? "Little-Endian" : "Big-Endian",
    296		"inadvertently on trap in TM... ");
    297
    298	rc = fflush(0);
    299	if (rc)
    300		pr_error(rc, "fflush()");
    301
    302	/* Launch ping() */
    303	rc = pthread_create(&t0_ping, &attr, ping, NULL);
    304	if (rc)
    305		pr_error(rc, "pthread_create()");
    306
    307	exit_from_pong = 0;
    308
    309	/* Launch pong() */
    310	rc = pthread_create(&t1_pong, &attr, pong, NULL);
    311	if (rc)
    312		pr_error(rc, "pthread_create()");
    313
    314	rc = pthread_join(t0_ping, NULL);
    315	if (rc)
    316		pr_error(rc, "pthread_join()");
    317
    318	rc = pthread_join(t1_pong, NULL);
    319	if (rc)
    320		pr_error(rc, "pthread_join()");
    321
    322	if (success) {
    323		printf("no.\n"); /* no, endianness did not flip inadvertently */
    324		return EXIT_SUCCESS;
    325	}
    326
    327	printf("yes!\n"); /* yes, endianness did flip inadvertently */
    328	return EXIT_FAILURE;
    329}
    330
    331int main(int argc, char **argv)
    332{
    333	return test_harness(tm_trap_test, "tm_trap_test");
    334}