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>
174 lines
4.6 KiB
C
174 lines
4.6 KiB
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
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
|
|
#define unlikely(cond) (cond)
|
|
|
|
#include <asm/insn.h>
|
|
#include <inat.c>
|
|
#include <insn.c>
|
|
|
|
/*
|
|
* Test of instruction analysis in general and insn_get_length() in
|
|
* particular. See if insn_get_length() and the disassembler agree
|
|
* on the length of each instruction in an elf disassembly.
|
|
*
|
|
* Usage: objdump -d a.out | awk -f distill.awk | ./test_get_len
|
|
*/
|
|
|
|
const char *prog;
|
|
static int verbose;
|
|
static int x86_64;
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: objdump -d a.out | awk -f distill.awk |"
|
|
" %s [-y|-n] [-v]\n", prog);
|
|
fprintf(stderr, "\t-y 64bit mode\n");
|
|
fprintf(stderr, "\t-n 32bit mode\n");
|
|
fprintf(stderr, "\t-v verbose mode\n");
|
|
exit(1);
|
|
}
|
|
|
|
static void malformed_line(const char *line, int line_nr)
|
|
{
|
|
fprintf(stderr, "%s: malformed line %d:\n%s", prog, line_nr, line);
|
|
exit(3);
|
|
}
|
|
|
|
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 parse_args(int argc, char **argv)
|
|
{
|
|
int c;
|
|
prog = argv[0];
|
|
while ((c = getopt(argc, argv, "ynv")) != -1) {
|
|
switch (c) {
|
|
case 'y':
|
|
x86_64 = 1;
|
|
break;
|
|
case 'n':
|
|
x86_64 = 0;
|
|
break;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
}
|
|
|
|
#define BUFSIZE 256
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char line[BUFSIZE], sym[BUFSIZE] = "<unknown>";
|
|
unsigned char insn_buf[16];
|
|
struct insn insn;
|
|
int insns = 0;
|
|
int warnings = 0;
|
|
|
|
parse_args(argc, argv);
|
|
|
|
while (fgets(line, BUFSIZE, stdin)) {
|
|
char copy[BUFSIZE], *s, *tab1, *tab2;
|
|
int nb = 0;
|
|
unsigned int b;
|
|
|
|
if (line[0] == '<') {
|
|
/* Symbol line */
|
|
strcpy(sym, line);
|
|
continue;
|
|
}
|
|
|
|
insns++;
|
|
memset(insn_buf, 0, 16);
|
|
strcpy(copy, line);
|
|
tab1 = strchr(copy, '\t');
|
|
if (!tab1)
|
|
malformed_line(line, insns);
|
|
s = tab1 + 1;
|
|
s += strspn(s, " ");
|
|
tab2 = strchr(s, '\t');
|
|
if (!tab2)
|
|
malformed_line(line, insns);
|
|
*tab2 = '\0'; /* Characters beyond tab2 aren't examined */
|
|
while (s < tab2) {
|
|
if (sscanf(s, "%x", &b) == 1) {
|
|
insn_buf[nb++] = (unsigned char) b;
|
|
s += 3;
|
|
} else
|
|
break;
|
|
}
|
|
/* Decode an instruction */
|
|
insn_init(&insn, insn_buf, sizeof(insn_buf), x86_64);
|
|
insn_get_length(&insn);
|
|
if (insn.length != nb) {
|
|
warnings++;
|
|
fprintf(stderr, "Warning: %s found difference at %s\n",
|
|
prog, sym);
|
|
fprintf(stderr, "Warning: %s", line);
|
|
fprintf(stderr, "Warning: objdump says %d bytes, but "
|
|
"insn_get_length() says %d\n", nb,
|
|
insn.length);
|
|
if (verbose)
|
|
dump_insn(stderr, &insn);
|
|
}
|
|
}
|
|
if (warnings)
|
|
fprintf(stderr, "Warning: decoded and checked %d"
|
|
" instructions with %d warnings\n", insns, warnings);
|
|
else
|
|
fprintf(stderr, "Succeed: decoded and checked %d"
|
|
" instructions\n", insns);
|
|
return 0;
|
|
}
|