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

loongson3-llsc-check.c (6206B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2#include <byteswap.h>
      3#include <elf.h>
      4#include <endian.h>
      5#include <errno.h>
      6#include <fcntl.h>
      7#include <inttypes.h>
      8#include <stdbool.h>
      9#include <stdio.h>
     10#include <stdlib.h>
     11#include <string.h>
     12#include <sys/mman.h>
     13#include <sys/types.h>
     14#include <sys/stat.h>
     15#include <unistd.h>
     16
     17#ifdef be32toh
     18/* If libc provides le{16,32,64}toh() then we'll use them */
     19#elif BYTE_ORDER == LITTLE_ENDIAN
     20# define le16toh(x)	(x)
     21# define le32toh(x)	(x)
     22# define le64toh(x)	(x)
     23#elif BYTE_ORDER == BIG_ENDIAN
     24# define le16toh(x)	bswap_16(x)
     25# define le32toh(x)	bswap_32(x)
     26# define le64toh(x)	bswap_64(x)
     27#endif
     28
     29/* MIPS opcodes, in bits 31:26 of an instruction */
     30#define OP_SPECIAL	0x00
     31#define OP_REGIMM	0x01
     32#define OP_BEQ		0x04
     33#define OP_BNE		0x05
     34#define OP_BLEZ		0x06
     35#define OP_BGTZ		0x07
     36#define OP_BEQL		0x14
     37#define OP_BNEL		0x15
     38#define OP_BLEZL	0x16
     39#define OP_BGTZL	0x17
     40#define OP_LL		0x30
     41#define OP_LLD		0x34
     42#define OP_SC		0x38
     43#define OP_SCD		0x3c
     44
     45/* Bits 20:16 of OP_REGIMM instructions */
     46#define REGIMM_BLTZ	0x00
     47#define REGIMM_BGEZ	0x01
     48#define REGIMM_BLTZL	0x02
     49#define REGIMM_BGEZL	0x03
     50#define REGIMM_BLTZAL	0x10
     51#define REGIMM_BGEZAL	0x11
     52#define REGIMM_BLTZALL	0x12
     53#define REGIMM_BGEZALL	0x13
     54
     55/* Bits 5:0 of OP_SPECIAL instructions */
     56#define SPECIAL_SYNC	0x0f
     57
     58static void usage(FILE *f)
     59{
     60	fprintf(f, "Usage: loongson3-llsc-check /path/to/vmlinux\n");
     61}
     62
     63static int se16(uint16_t x)
     64{
     65	return (int16_t)x;
     66}
     67
     68static bool is_ll(uint32_t insn)
     69{
     70	switch (insn >> 26) {
     71	case OP_LL:
     72	case OP_LLD:
     73		return true;
     74
     75	default:
     76		return false;
     77	}
     78}
     79
     80static bool is_sc(uint32_t insn)
     81{
     82	switch (insn >> 26) {
     83	case OP_SC:
     84	case OP_SCD:
     85		return true;
     86
     87	default:
     88		return false;
     89	}
     90}
     91
     92static bool is_sync(uint32_t insn)
     93{
     94	/* Bits 31:11 should all be zeroes */
     95	if (insn >> 11)
     96		return false;
     97
     98	/* Bits 5:0 specify the SYNC special encoding */
     99	if ((insn & 0x3f) != SPECIAL_SYNC)
    100		return false;
    101
    102	return true;
    103}
    104
    105static bool is_branch(uint32_t insn, int *off)
    106{
    107	switch (insn >> 26) {
    108	case OP_BEQ:
    109	case OP_BEQL:
    110	case OP_BNE:
    111	case OP_BNEL:
    112	case OP_BGTZ:
    113	case OP_BGTZL:
    114	case OP_BLEZ:
    115	case OP_BLEZL:
    116		*off = se16(insn) + 1;
    117		return true;
    118
    119	case OP_REGIMM:
    120		switch ((insn >> 16) & 0x1f) {
    121		case REGIMM_BGEZ:
    122		case REGIMM_BGEZL:
    123		case REGIMM_BGEZAL:
    124		case REGIMM_BGEZALL:
    125		case REGIMM_BLTZ:
    126		case REGIMM_BLTZL:
    127		case REGIMM_BLTZAL:
    128		case REGIMM_BLTZALL:
    129			*off = se16(insn) + 1;
    130			return true;
    131
    132		default:
    133			return false;
    134		}
    135
    136	default:
    137		return false;
    138	}
    139}
    140
    141static int check_ll(uint64_t pc, uint32_t *code, size_t sz)
    142{
    143	ssize_t i, max, sc_pos;
    144	int off;
    145
    146	/*
    147	 * Every LL must be preceded by a sync instruction in order to ensure
    148	 * that instruction reordering doesn't allow a prior memory access to
    149	 * execute after the LL & cause erroneous results.
    150	 */
    151	if (!is_sync(le32toh(code[-1]))) {
    152		fprintf(stderr, "%" PRIx64 ": LL not preceded by sync\n", pc);
    153		return -EINVAL;
    154	}
    155
    156	/* Find the matching SC instruction */
    157	max = sz / 4;
    158	for (sc_pos = 0; sc_pos < max; sc_pos++) {
    159		if (is_sc(le32toh(code[sc_pos])))
    160			break;
    161	}
    162	if (sc_pos >= max) {
    163		fprintf(stderr, "%" PRIx64 ": LL has no matching SC\n", pc);
    164		return -EINVAL;
    165	}
    166
    167	/*
    168	 * Check branches within the LL/SC loop target sync instructions,
    169	 * ensuring that speculative execution can't generate memory accesses
    170	 * due to instructions outside of the loop.
    171	 */
    172	for (i = 0; i < sc_pos; i++) {
    173		if (!is_branch(le32toh(code[i]), &off))
    174			continue;
    175
    176		/*
    177		 * If the branch target is within the LL/SC loop then we don't
    178		 * need to worry about it.
    179		 */
    180		if ((off >= -i) && (off <= sc_pos))
    181			continue;
    182
    183		/* If the branch targets a sync instruction we're all good... */
    184		if (is_sync(le32toh(code[i + off])))
    185			continue;
    186
    187		/* ...but if not, we have a problem */
    188		fprintf(stderr, "%" PRIx64 ": Branch target not a sync\n",
    189			pc + (i * 4));
    190		return -EINVAL;
    191	}
    192
    193	return 0;
    194}
    195
    196static int check_code(uint64_t pc, uint32_t *code, size_t sz)
    197{
    198	int err = 0;
    199
    200	if (sz % 4) {
    201		fprintf(stderr, "%" PRIx64 ": Section size not a multiple of 4\n",
    202			pc);
    203		err = -EINVAL;
    204		sz -= (sz % 4);
    205	}
    206
    207	if (is_ll(le32toh(code[0]))) {
    208		fprintf(stderr, "%" PRIx64 ": First instruction in section is an LL\n",
    209			pc);
    210		err = -EINVAL;
    211	}
    212
    213#define advance() (	\
    214	code++,		\
    215	pc += 4,	\
    216	sz -= 4		\
    217)
    218
    219	/*
    220	 * Skip the first instruction, allowing check_ll to look backwards
    221	 * unconditionally.
    222	 */
    223	advance();
    224
    225	/* Now scan through the code looking for LL instructions */
    226	for (; sz; advance()) {
    227		if (is_ll(le32toh(code[0])))
    228			err |= check_ll(pc, code, sz);
    229	}
    230
    231	return err;
    232}
    233
    234int main(int argc, char *argv[])
    235{
    236	int vmlinux_fd, status, err, i;
    237	const char *vmlinux_path;
    238	struct stat st;
    239	Elf64_Ehdr *eh;
    240	Elf64_Shdr *sh;
    241	void *vmlinux;
    242
    243	status = EXIT_FAILURE;
    244
    245	if (argc < 2) {
    246		usage(stderr);
    247		goto out_ret;
    248	}
    249
    250	vmlinux_path = argv[1];
    251	vmlinux_fd = open(vmlinux_path, O_RDONLY);
    252	if (vmlinux_fd == -1) {
    253		perror("Unable to open vmlinux");
    254		goto out_ret;
    255	}
    256
    257	err = fstat(vmlinux_fd, &st);
    258	if (err) {
    259		perror("Unable to stat vmlinux");
    260		goto out_close;
    261	}
    262
    263	vmlinux = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, vmlinux_fd, 0);
    264	if (vmlinux == MAP_FAILED) {
    265		perror("Unable to mmap vmlinux");
    266		goto out_close;
    267	}
    268
    269	eh = vmlinux;
    270	if (memcmp(eh->e_ident, ELFMAG, SELFMAG)) {
    271		fprintf(stderr, "vmlinux is not an ELF?\n");
    272		goto out_munmap;
    273	}
    274
    275	if (eh->e_ident[EI_CLASS] != ELFCLASS64) {
    276		fprintf(stderr, "vmlinux is not 64b?\n");
    277		goto out_munmap;
    278	}
    279
    280	if (eh->e_ident[EI_DATA] != ELFDATA2LSB) {
    281		fprintf(stderr, "vmlinux is not little endian?\n");
    282		goto out_munmap;
    283	}
    284
    285	for (i = 0; i < le16toh(eh->e_shnum); i++) {
    286		sh = vmlinux + le64toh(eh->e_shoff) + (i * le16toh(eh->e_shentsize));
    287
    288		if (sh->sh_type != SHT_PROGBITS)
    289			continue;
    290		if (!(sh->sh_flags & SHF_EXECINSTR))
    291			continue;
    292
    293		err = check_code(le64toh(sh->sh_addr),
    294				 vmlinux + le64toh(sh->sh_offset),
    295				 le64toh(sh->sh_size));
    296		if (err)
    297			goto out_munmap;
    298	}
    299
    300	status = EXIT_SUCCESS;
    301out_munmap:
    302	munmap(vmlinux, st.st_size);
    303out_close:
    304	close(vmlinux_fd);
    305out_ret:
    306	fprintf(stdout, "loongson3-llsc-check returns %s\n",
    307		status ? "failure" : "success");
    308	return status;
    309}