6ba48ff46f
The current x86 instruction decoder steps along through the instruction stream but always ensures that it never steps farther than the largest possible instruction size (MAX_INSN_SIZE). The MPX code is now going to be doing some decoding of userspace instructions. We copy those from userspace in to the kernel and they're obviously completely untrusted coming from userspace. In addition to the constraint that instructions can only be so long, we also have to be aware of how long the buffer is that came in from userspace. This _looks_ to be similar to what the perf and kprobes is doing, but it's unclear to me whether they are affected. The whole reason we need this is that it is perfectly valid to be executing an instruction within MAX_INSN_SIZE bytes of an unreadable page. We should be able to gracefully handle short reads in those cases. This adds support to the decoder to record how long the buffer being decoded is and to refuse to "validate" the instruction if we would have gone over the end of the buffer to decode it. The kprobes code probably needs to be looked at here a bit more carefully. This patch still respects the MAX_INSN_SIZE limit there but the kprobes code does look like it might be able to be a bit more strict than it currently is. Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com> Acked-by: Jim Keniston <jkenisto@us.ibm.com> Acked-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> Cc: x86@kernel.org Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Paul Mackerras <paulus@samba.org> Cc: Arnaldo Carvalho de Melo <acme@kernel.org> Cc: Srikar Dronamraju <srikar@linux.vnet.ibm.com> Cc: Ananth N Mavinakayanahalli <ananth@in.ibm.com> Cc: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> Cc: "David S. Miller" <davem@davemloft.net> Link: http://lkml.kernel.org/r/20141114153957.E6B01535@viggo.jf.intel.com Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
281 lines
7.3 KiB
C
281 lines
7.3 KiB
C
/*
|
|
* x86 decoder sanity test - based on test_get_insn.c
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* Copyright (C) IBM Corporation, 2009
|
|
* Copyright (C) Hitachi, Ltd., 2011
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#define unlikely(cond) (cond)
|
|
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
|
|
|
|
#include <asm/insn.h>
|
|
#include <inat.c>
|
|
#include <insn.c>
|
|
|
|
/*
|
|
* Test of instruction analysis against tampering.
|
|
* Feed random binary to instruction decoder and ensure not to
|
|
* access out-of-instruction-buffer.
|
|
*/
|
|
|
|
#define DEFAULT_MAX_ITER 10000
|
|
#define INSN_NOP 0x90
|
|
|
|
static const char *prog; /* Program name */
|
|
static int verbose; /* Verbosity */
|
|
static int x86_64; /* x86-64 bit mode flag */
|
|
static unsigned int seed; /* Random seed */
|
|
static unsigned long iter_start; /* Start of iteration number */
|
|
static unsigned long iter_end = DEFAULT_MAX_ITER; /* End of iteration number */
|
|
static FILE *input_file; /* Input file name */
|
|
|
|
static void usage(const char *err)
|
|
{
|
|
if (err)
|
|
fprintf(stderr, "%s: Error: %s\n\n", prog, err);
|
|
fprintf(stderr, "Usage: %s [-y|-n|-v] [-s seed[,no]] [-m max] [-i input]\n", prog);
|
|
fprintf(stderr, "\t-y 64bit mode\n");
|
|
fprintf(stderr, "\t-n 32bit mode\n");
|
|
fprintf(stderr, "\t-v Verbosity(-vv dumps any decoded result)\n");
|
|
fprintf(stderr, "\t-s Give a random seed (and iteration number)\n");
|
|
fprintf(stderr, "\t-m Give a maximum iteration number\n");
|
|
fprintf(stderr, "\t-i Give an input file with decoded binary\n");
|
|
exit(1);
|
|
}
|
|
|
|
static void dump_field(FILE *fp, const char *name, const char *indent,
|
|
struct insn_field *field)
|
|
{
|
|
fprintf(fp, "%s.%s = {\n", indent, name);
|
|
fprintf(fp, "%s\t.value = %d, bytes[] = {%x, %x, %x, %x},\n",
|
|
indent, field->value, field->bytes[0], field->bytes[1],
|
|
field->bytes[2], field->bytes[3]);
|
|
fprintf(fp, "%s\t.got = %d, .nbytes = %d},\n", indent,
|
|
field->got, field->nbytes);
|
|
}
|
|
|
|
static void dump_insn(FILE *fp, struct insn *insn)
|
|
{
|
|
fprintf(fp, "Instruction = {\n");
|
|
dump_field(fp, "prefixes", "\t", &insn->prefixes);
|
|
dump_field(fp, "rex_prefix", "\t", &insn->rex_prefix);
|
|
dump_field(fp, "vex_prefix", "\t", &insn->vex_prefix);
|
|
dump_field(fp, "opcode", "\t", &insn->opcode);
|
|
dump_field(fp, "modrm", "\t", &insn->modrm);
|
|
dump_field(fp, "sib", "\t", &insn->sib);
|
|
dump_field(fp, "displacement", "\t", &insn->displacement);
|
|
dump_field(fp, "immediate1", "\t", &insn->immediate1);
|
|
dump_field(fp, "immediate2", "\t", &insn->immediate2);
|
|
fprintf(fp, "\t.attr = %x, .opnd_bytes = %d, .addr_bytes = %d,\n",
|
|
insn->attr, insn->opnd_bytes, insn->addr_bytes);
|
|
fprintf(fp, "\t.length = %d, .x86_64 = %d, .kaddr = %p}\n",
|
|
insn->length, insn->x86_64, insn->kaddr);
|
|
}
|
|
|
|
static void dump_stream(FILE *fp, const char *msg, unsigned long nr_iter,
|
|
unsigned char *insn_buf, struct insn *insn)
|
|
{
|
|
int i;
|
|
|
|
fprintf(fp, "%s:\n", msg);
|
|
|
|
dump_insn(fp, insn);
|
|
|
|
fprintf(fp, "You can reproduce this with below command(s);\n");
|
|
|
|
/* Input a decoded instruction sequence directly */
|
|
fprintf(fp, " $ echo ");
|
|
for (i = 0; i < MAX_INSN_SIZE; i++)
|
|
fprintf(fp, " %02x", insn_buf[i]);
|
|
fprintf(fp, " | %s -i -\n", prog);
|
|
|
|
if (!input_file) {
|
|
fprintf(fp, "Or \n");
|
|
/* Give a seed and iteration number */
|
|
fprintf(fp, " $ %s -s 0x%x,%lu\n", prog, seed, nr_iter);
|
|
}
|
|
}
|
|
|
|
static void init_random_seed(void)
|
|
{
|
|
int fd;
|
|
|
|
fd = open("/dev/urandom", O_RDONLY);
|
|
if (fd < 0)
|
|
goto fail;
|
|
|
|
if (read(fd, &seed, sizeof(seed)) != sizeof(seed))
|
|
goto fail;
|
|
|
|
close(fd);
|
|
return;
|
|
fail:
|
|
usage("Failed to open /dev/urandom");
|
|
}
|
|
|
|
/* Read given instruction sequence from the input file */
|
|
static int read_next_insn(unsigned char *insn_buf)
|
|
{
|
|
char buf[256] = "", *tmp;
|
|
int i;
|
|
|
|
tmp = fgets(buf, ARRAY_SIZE(buf), input_file);
|
|
if (tmp == NULL || feof(input_file))
|
|
return 0;
|
|
|
|
for (i = 0; i < MAX_INSN_SIZE; i++) {
|
|
insn_buf[i] = (unsigned char)strtoul(tmp, &tmp, 16);
|
|
if (*tmp != ' ')
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static int generate_insn(unsigned char *insn_buf)
|
|
{
|
|
int i;
|
|
|
|
if (input_file)
|
|
return read_next_insn(insn_buf);
|
|
|
|
/* Fills buffer with random binary up to MAX_INSN_SIZE */
|
|
for (i = 0; i < MAX_INSN_SIZE - 1; i += 2)
|
|
*(unsigned short *)(&insn_buf[i]) = random() & 0xffff;
|
|
|
|
while (i < MAX_INSN_SIZE)
|
|
insn_buf[i++] = random() & 0xff;
|
|
|
|
return i;
|
|
}
|
|
|
|
static void parse_args(int argc, char **argv)
|
|
{
|
|
int c;
|
|
char *tmp = NULL;
|
|
int set_seed = 0;
|
|
|
|
prog = argv[0];
|
|
while ((c = getopt(argc, argv, "ynvs:m:i:")) != -1) {
|
|
switch (c) {
|
|
case 'y':
|
|
x86_64 = 1;
|
|
break;
|
|
case 'n':
|
|
x86_64 = 0;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case 'i':
|
|
if (strcmp("-", optarg) == 0)
|
|
input_file = stdin;
|
|
else
|
|
input_file = fopen(optarg, "r");
|
|
if (!input_file)
|
|
usage("Failed to open input file");
|
|
break;
|
|
case 's':
|
|
seed = (unsigned int)strtoul(optarg, &tmp, 0);
|
|
if (*tmp == ',') {
|
|
optarg = tmp + 1;
|
|
iter_start = strtoul(optarg, &tmp, 0);
|
|
}
|
|
if (*tmp != '\0' || tmp == optarg)
|
|
usage("Failed to parse seed");
|
|
set_seed = 1;
|
|
break;
|
|
case 'm':
|
|
iter_end = strtoul(optarg, &tmp, 0);
|
|
if (*tmp != '\0' || tmp == optarg)
|
|
usage("Failed to parse max_iter");
|
|
break;
|
|
default:
|
|
usage(NULL);
|
|
}
|
|
}
|
|
|
|
/* Check errors */
|
|
if (iter_end < iter_start)
|
|
usage("Max iteration number must be bigger than iter-num");
|
|
|
|
if (set_seed && input_file)
|
|
usage("Don't use input file (-i) with random seed (-s)");
|
|
|
|
/* Initialize random seed */
|
|
if (!input_file) {
|
|
if (!set_seed) /* No seed is given */
|
|
init_random_seed();
|
|
srand(seed);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct insn insn;
|
|
int insns = 0;
|
|
int errors = 0;
|
|
unsigned long i;
|
|
unsigned char insn_buf[MAX_INSN_SIZE * 2];
|
|
|
|
parse_args(argc, argv);
|
|
|
|
/* Prepare stop bytes with NOPs */
|
|
memset(insn_buf + MAX_INSN_SIZE, INSN_NOP, MAX_INSN_SIZE);
|
|
|
|
for (i = 0; i < iter_end; i++) {
|
|
if (generate_insn(insn_buf) <= 0)
|
|
break;
|
|
|
|
if (i < iter_start) /* Skip to given iteration number */
|
|
continue;
|
|
|
|
/* Decode an instruction */
|
|
insn_init(&insn, insn_buf, sizeof(insn_buf), x86_64);
|
|
insn_get_length(&insn);
|
|
|
|
if (insn.next_byte <= insn.kaddr ||
|
|
insn.kaddr + MAX_INSN_SIZE < insn.next_byte) {
|
|
/* Access out-of-range memory */
|
|
dump_stream(stderr, "Error: Found an access violation", i, insn_buf, &insn);
|
|
errors++;
|
|
} else if (verbose && !insn_complete(&insn))
|
|
dump_stream(stdout, "Info: Found an undecodable input", i, insn_buf, &insn);
|
|
else if (verbose >= 2)
|
|
dump_insn(stdout, &insn);
|
|
insns++;
|
|
}
|
|
|
|
fprintf(stdout, "%s: %s: decoded and checked %d %s instructions with %d errors (seed:0x%x)\n",
|
|
prog,
|
|
(errors) ? "Failure" : "Success",
|
|
insns,
|
|
(input_file) ? "given" : "random",
|
|
errors,
|
|
seed);
|
|
|
|
return errors ? 1 : 0;
|
|
}
|