target/i386: validate VEX prefixes via the instructions' exception classes

Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Paolo Bonzini 2022-09-18 00:43:52 +02:00
parent 608db8dbfb
commit 20581aadec
4 changed files with 239 additions and 12 deletions

View file

@ -93,6 +93,23 @@
#define zext0 .special = X86_SPECIAL_ZExtOp0,
#define zext2 .special = X86_SPECIAL_ZExtOp2,
#define vex1 .vex_class = 1,
#define vex1_rep3 .vex_class = 1, .vex_special = X86_VEX_REPScalar,
#define vex2 .vex_class = 2,
#define vex2_rep3 .vex_class = 2, .vex_special = X86_VEX_REPScalar,
#define vex3 .vex_class = 3,
#define vex4 .vex_class = 4,
#define vex4_unal .vex_class = 4, .vex_special = X86_VEX_SSEUnaligned,
#define vex5 .vex_class = 5,
#define vex6 .vex_class = 6,
#define vex7 .vex_class = 7,
#define vex8 .vex_class = 8,
#define vex11 .vex_class = 11,
#define vex12 .vex_class = 12,
#define vex13 .vex_class = 13,
#define avx2_256 .vex_special = X86_VEX_AVX2_256,
static uint8_t get_modrm(DisasContext *s, CPUX86State *env)
{
if (!s->has_modrm) {
@ -157,6 +174,18 @@ static const X86OpEntry opcodes_root[256] = {
};
#undef mmx
#undef vex1
#undef vex2
#undef vex3
#undef vex4
#undef vex4_unal
#undef vex5
#undef vex6
#undef vex7
#undef vex8
#undef vex11
#undef vex12
#undef vex13
/*
* Decode the fixed part of the opcode and place the last
@ -564,6 +593,136 @@ static bool has_cpuid_feature(DisasContext *s, X86CPUIDFeature cpuid)
g_assert_not_reached();
}
static bool validate_vex(DisasContext *s, X86DecodedInsn *decode)
{
X86OpEntry *e = &decode->e;
switch (e->vex_special) {
case X86_VEX_REPScalar:
/*
* Instructions which differ between 00/66 and F2/F3 in the
* exception classification and the size of the memory operand.
*/
assert(e->vex_class == 1 || e->vex_class == 2);
if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) {
e->vex_class = 3;
if (s->vex_l) {
goto illegal;
}
assert(decode->e.s2 == X86_SIZE_x);
if (decode->op[2].has_ea) {
decode->op[2].ot = s->prefix & PREFIX_REPZ ? MO_32 : MO_64;
}
}
break;
case X86_VEX_SSEUnaligned:
/* handled in sse_needs_alignment. */
break;
case X86_VEX_AVX2_256:
if ((s->prefix & PREFIX_VEX) && s->vex_l && !has_cpuid_feature(s, X86_FEAT_AVX2)) {
goto illegal;
}
}
/* TODO: instructions that require VEX.W=0 (Table 2-16) */
switch (e->vex_class) {
case 0:
if (s->prefix & PREFIX_VEX) {
goto illegal;
}
return true;
case 1:
case 2:
case 3:
case 4:
case 5:
case 7:
if (s->prefix & PREFIX_VEX) {
if (!(s->flags & HF_AVX_EN_MASK)) {
goto illegal;
}
} else {
if (!(s->flags & HF_OSFXSR_MASK)) {
goto illegal;
}
}
break;
case 12:
/* Must have a VSIB byte and no address prefix. */
assert(s->has_modrm);
if ((s->modrm & 7) != 4 || s->aflag == MO_16) {
goto illegal;
}
/* Check no overlap between registers. */
if (!decode->op[0].has_ea &&
(decode->op[0].n == decode->mem.index || decode->op[0].n == decode->op[1].n)) {
goto illegal;
}
assert(!decode->op[1].has_ea);
if (decode->op[1].n == decode->mem.index) {
goto illegal;
}
if (!decode->op[2].has_ea &&
(decode->op[2].n == decode->mem.index || decode->op[2].n == decode->op[1].n)) {
goto illegal;
}
/* fall through */
case 6:
case 11:
if (!(s->prefix & PREFIX_VEX)) {
goto illegal;
}
if (!(s->flags & HF_AVX_EN_MASK)) {
goto illegal;
}
break;
case 8:
if (!(s->prefix & PREFIX_VEX)) {
/* EMMS */
return true;
}
if (!(s->flags & HF_AVX_EN_MASK)) {
goto illegal;
}
break;
case 13:
if (!(s->prefix & PREFIX_VEX)) {
goto illegal;
}
if (s->vex_l) {
goto illegal;
}
/* All integer instructions use VEX.vvvv, so exit. */
return true;
}
if (s->vex_v != 0 &&
e->op0 != X86_TYPE_H && e->op0 != X86_TYPE_B &&
e->op1 != X86_TYPE_H && e->op1 != X86_TYPE_B &&
e->op2 != X86_TYPE_H && e->op2 != X86_TYPE_B) {
goto illegal;
}
if (s->flags & HF_TS_MASK) {
goto nm_exception;
}
if (s->flags & HF_EM_MASK) {
goto illegal;
}
return true;
nm_exception:
gen_NM_exception(s);
return false;
illegal:
gen_illegal_opcode(s);
return false;
}
static void decode_temp_free(X86DecodedOp *op)
{
if (op->v_ptr) {
@ -804,8 +963,11 @@ static void disas_insn_new(DisasContext *s, CPUState *cpu, int b)
break;
}
if (!validate_vex(s, &decode)) {
return;
}
if (decode.op[0].has_ea || decode.op[1].has_ea || decode.op[2].has_ea) {
gen_load_ea(s, &decode.mem);
gen_load_ea(s, &decode.mem, decode.e.vex_class == 12);
}
if (s->prefix & PREFIX_LOCK) {
if (decode.op[0].unit != X86_OP_INT || !decode.op[0].has_ea) {

View file

@ -152,6 +152,36 @@ typedef enum X86InsnSpecial {
X86_SPECIAL_o64,
} X86InsnSpecial;
/*
* Special cases for instructions that operate on XMM/YMM registers. Intel
* retconned all of them to have VEX exception classes other than 0 and 13, so
* all these only matter for instructions that have a VEX exception class.
* Based on tables in the "AVX and SSE Instruction Exception Specification"
* section of the manual.
*/
typedef enum X86VEXSpecial {
/* Legacy SSE instructions that allow unaligned operands */
X86_VEX_SSEUnaligned,
/*
* Used for instructions that distinguish the XMM operand type with an
* instruction prefix; legacy SSE encodings will allow unaligned operands
* for scalar operands only (identified by a REP prefix). In this case,
* the decoding table uses "x" for the vector operands instead of specifying
* pd/ps/sd/ss individually.
*/
X86_VEX_REPScalar,
/*
* VEX instructions that only support 256-bit operands with AVX2 (Table 2-17
* column 3). Columns 2 and 4 (instructions limited to 256- and 127-bit
* operands respectively) are implicit in the presence of dq and qq
* operands, and thus handled by decode_op_size.
*/
X86_VEX_AVX2_256,
} X86VEXSpecial;
typedef struct X86OpEntry X86OpEntry;
typedef struct X86DecodedInsn X86DecodedInsn;
@ -180,6 +210,8 @@ struct X86OpEntry {
X86InsnSpecial special:8;
X86CPUIDFeature cpuid:8;
unsigned vex_class:8;
X86VEXSpecial vex_special:8;
bool is_decode:1;
};

View file

@ -19,14 +19,19 @@
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
static void gen_NM_exception(DisasContext *s)
{
gen_exception(s, EXCP07_PREX);
}
static void gen_illegal(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode)
{
gen_illegal_opcode(s);
}
static void gen_load_ea(DisasContext *s, AddressParts *mem)
static void gen_load_ea(DisasContext *s, AddressParts *mem, bool is_vsib)
{
TCGv ea = gen_lea_modrm_1(s, *mem);
TCGv ea = gen_lea_modrm_1(s, *mem, is_vsib);
gen_lea_v_seg(s, s->aflag, ea, mem->def_seg, s->override);
}
@ -113,6 +118,25 @@ static void gen_load_sse(DisasContext *s, TCGv temp, MemOp ot, int dest_ofs, boo
}
}
static bool sse_needs_alignment(DisasContext *s, X86DecodedInsn *decode, MemOp ot)
{
switch (decode->e.vex_class) {
case 2:
case 4:
if ((s->prefix & PREFIX_VEX) ||
decode->e.vex_special == X86_VEX_SSEUnaligned) {
/* MOST legacy SSE instructions require aligned memory operands, but not all. */
return false;
}
/* fall through */
case 1:
return ot >= MO_128;
default:
return false;
}
}
static void gen_load(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv v)
{
X86DecodedOp *op = &decode->op[opn];
@ -149,7 +173,8 @@ static void gen_load(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv v)
compute_xmm_offset(op);
load_vector:
if (op->has_ea) {
gen_load_sse(s, v, op->ot, op->offset, true);
bool aligned = sse_needs_alignment(s, decode, op->ot);
gen_load_sse(s, v, op->ot, op->offset, aligned);
}
break;
@ -176,7 +201,13 @@ static void gen_writeback(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv
}
break;
case X86_OP_MMX:
break;
case X86_OP_SSE:
if ((s->prefix & PREFIX_VEX) && op->ot == MO_128) {
tcg_gen_gvec_dup_imm(MO_64,
offsetof(CPUX86State, xmm_regs[op->n].ZMM_X(1)),
16, 16, 0);
}
break;
case X86_OP_CR:
case X86_OP_DR:

View file

@ -23,6 +23,7 @@
#include "disas/disas.h"
#include "exec/exec-all.h"
#include "tcg/tcg-op.h"
#include "tcg/tcg-op-gvec.h"
#include "exec/cpu_ldst.h"
#include "exec/translator.h"
@ -2281,11 +2282,11 @@ static AddressParts gen_lea_modrm_0(CPUX86State *env, DisasContext *s,
}
/* Compute the address, with a minimum number of TCG ops. */
static TCGv gen_lea_modrm_1(DisasContext *s, AddressParts a)
static TCGv gen_lea_modrm_1(DisasContext *s, AddressParts a, bool is_vsib)
{
TCGv ea = NULL;
if (a.index >= 0) {
if (a.index >= 0 && !is_vsib) {
if (a.scale == 0) {
ea = cpu_regs[a.index];
} else {
@ -2318,7 +2319,7 @@ static TCGv gen_lea_modrm_1(DisasContext *s, AddressParts a)
static void gen_lea_modrm(CPUX86State *env, DisasContext *s, int modrm)
{
AddressParts a = gen_lea_modrm_0(env, s, modrm);
TCGv ea = gen_lea_modrm_1(s, a);
TCGv ea = gen_lea_modrm_1(s, a, false);
gen_lea_v_seg(s, s->aflag, ea, a.def_seg, s->override);
}
@ -2331,7 +2332,8 @@ static void gen_nop_modrm(CPUX86State *env, DisasContext *s, int modrm)
static void gen_bndck(CPUX86State *env, DisasContext *s, int modrm,
TCGCond cond, TCGv_i64 bndv)
{
TCGv ea = gen_lea_modrm_1(s, gen_lea_modrm_0(env, s, modrm));
AddressParts a = gen_lea_modrm_0(env, s, modrm);
TCGv ea = gen_lea_modrm_1(s, a, false);
tcg_gen_extu_tl_i64(s->tmp1_i64, ea);
if (!CODE64(s)) {
@ -6011,7 +6013,7 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
reg = ((modrm >> 3) & 7) | REX_R(s);
{
AddressParts a = gen_lea_modrm_0(env, s, modrm);
TCGv ea = gen_lea_modrm_1(s, a);
TCGv ea = gen_lea_modrm_1(s, a, false);
gen_lea_v_seg(s, s->aflag, ea, -1, -1);
gen_op_mov_reg_v(s, dflag, reg, s->A0);
}
@ -6234,7 +6236,7 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
if (mod != 3) {
/* memory op */
AddressParts a = gen_lea_modrm_0(env, s, modrm);
TCGv ea = gen_lea_modrm_1(s, a);
TCGv ea = gen_lea_modrm_1(s, a, false);
TCGv last_addr = tcg_temp_new();
bool update_fdp = true;
@ -7229,7 +7231,7 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
gen_exts(ot, s->T1);
tcg_gen_sari_tl(s->tmp0, s->T1, 3 + ot);
tcg_gen_shli_tl(s->tmp0, s->tmp0, ot);
tcg_gen_add_tl(s->A0, gen_lea_modrm_1(s, a), s->tmp0);
tcg_gen_add_tl(s->A0, gen_lea_modrm_1(s, a, false), s->tmp0);
gen_lea_v_seg(s, s->aflag, s->A0, a.def_seg, s->override);
if (!(s->prefix & PREFIX_LOCK)) {
gen_op_ld_v(s, ot, s->T0, s->A0);
@ -8278,7 +8280,7 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
/* rip-relative generates #ud */
goto illegal_op;
}
tcg_gen_not_tl(s->A0, gen_lea_modrm_1(s, a));
tcg_gen_not_tl(s->A0, gen_lea_modrm_1(s, a, false));
if (!CODE64(s)) {
tcg_gen_ext32u_tl(s->A0, s->A0);
}