From: H.J. Lu Date: Wed, 11 Mar 2020 16:46:19 +0000 (-0700) Subject: i386: Generate lfence with load/indirect branch/ret [CVE-2020-0551] X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=ae531041c7c5956672342f89c486a011c84f027f;p=binutils-gdb.git i386: Generate lfence with load/indirect branch/ret [CVE-2020-0551] Add 3 command-line options to generate lfence for load, indirect near branch and ret to help mitigate: https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00334.html http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0551 1. -mlfence-after-load=[no|yes]: -mlfence-after-load=yes generates lfence after load instructions. 2. -mlfence-before-indirect-branch=[none|all|memory|register]: a. -mlfence-before-indirect-branch=all generates lfence before indirect near branches via register and a warning before indirect near branches via memory. b. -mlfence-before-indirect-branch=memory issue a warning before indirect near branches via memory. c. -mlfence-before-indirect-branch=register generates lfence before indirect near branches via register. Note that lfence won't be generated before indirect near branches via register with -mlfence-after-load=yes since lfence will be generated after loading branch target register. 3. -mlfence-before-ret=[none|or|not] a. -mlfence-before-ret=or generates or with lfence before ret. b. -mlfence-before-ret=not generates not with lfence before ret. A warning will be issued and lfence won't be generated before indirect near branch and ret if the previous item is a prefix or a constant directive, which may be used to hardcode an instruction, since there is no clear instruction boundary. * config/tc-i386.c (lfence_after_load): New. (lfence_before_indirect_branch_kind): New. (lfence_before_indirect_branch): New. (lfence_before_ret_kind): New. (lfence_before_ret): New. (last_insn): New. (load_insn_p): New. (insert_lfence_after): New. (insert_lfence_before): New. (md_assemble): Call insert_lfence_before and insert_lfence_after. Set last_insn. (OPTION_MLFENCE_AFTER_LOAD): New. (OPTION_MLFENCE_BEFORE_INDIRECT_BRANCH): New. (OPTION_MLFENCE_BEFORE_RET): New. (md_longopts): Add -mlfence-after-load=, -mlfence-before-indirect-branch= and -mlfence-before-ret=. (md_parse_option): Handle -mlfence-after-load=, -mlfence-before-indirect-branch= and -mlfence-before-ret=. (md_show_usage): Display -mlfence-after-load=, -mlfence-before-indirect-branch= and -mlfence-before-ret=. (i386_cons_align): New. * config/tc-i386.h (i386_cons_align): New. (md_cons_align): New. * doc/c-i386.texi: Document -mlfence-after-load=, -mlfence-before-indirect-branch= and -mlfence-before-ret=. --- diff --git a/gas/ChangeLog b/gas/ChangeLog index b4b1a341117..19f4bc2cd8b 100644 --- a/gas/ChangeLog +++ b/gas/ChangeLog @@ -1,3 +1,31 @@ +2020-03-11 H.J. Lu + + * config/tc-i386.c (lfence_after_load): New. + (lfence_before_indirect_branch_kind): New. + (lfence_before_indirect_branch): New. + (lfence_before_ret_kind): New. + (lfence_before_ret): New. + (last_insn): New. + (load_insn_p): New. + (insert_lfence_after): New. + (insert_lfence_before): New. + (md_assemble): Call insert_lfence_before and insert_lfence_after. + Set last_insn. + (OPTION_MLFENCE_AFTER_LOAD): New. + (OPTION_MLFENCE_BEFORE_INDIRECT_BRANCH): New. + (OPTION_MLFENCE_BEFORE_RET): New. + (md_longopts): Add -mlfence-after-load=, + -mlfence-before-indirect-branch= and -mlfence-before-ret=. + (md_parse_option): Handle -mlfence-after-load=, + -mlfence-before-indirect-branch= and -mlfence-before-ret=. + (md_show_usage): Display -mlfence-after-load=, + -mlfence-before-indirect-branch= and -mlfence-before-ret=. + (i386_cons_align): New. + * config/tc-i386.h (i386_cons_align): New. + (md_cons_align): New. + * doc/c-i386.texi: Document -mlfence-after-load=, + -mlfence-before-indirect-branch= and -mlfence-before-ret=. + 2020-03-11 Nick Clifton PR 25611 diff --git a/gas/config/tc-i386.c b/gas/config/tc-i386.c index b020f39c863..09063f784b7 100644 --- a/gas/config/tc-i386.c +++ b/gas/config/tc-i386.c @@ -629,7 +629,29 @@ static int omit_lock_prefix = 0; "lock addl $0, (%{re}sp)". */ static int avoid_fence = 0; -/* Type of the previous instruction. */ +/* 1 if lfence should be inserted after every load. */ +static int lfence_after_load = 0; + +/* Non-zero if lfence should be inserted before indirect branch. */ +static enum lfence_before_indirect_branch_kind + { + lfence_branch_none = 0, + lfence_branch_register, + lfence_branch_memory, + lfence_branch_all + } +lfence_before_indirect_branch; + +/* Non-zero if lfence should be inserted before ret. */ +static enum lfence_before_ret_kind + { + lfence_before_ret_none = 0, + lfence_before_ret_not, + lfence_before_ret_or + } +lfence_before_ret; + +/* Types of previous instruction is .byte or prefix. */ static struct { segT seg; @@ -4311,6 +4333,283 @@ optimize_encoding (void) } } +/* Return non-zero for load instruction. */ + +static int +load_insn_p (void) +{ + unsigned int dest; + int any_vex_p = is_any_vex_encoding (&i.tm); + unsigned int base_opcode = i.tm.base_opcode | 1; + + if (!any_vex_p) + { + /* lea */ + if (i.tm.base_opcode == 0x8d) + return 0; + + /* pop */ + if ((i.tm.base_opcode & ~7) == 0x58 + || (i.tm.base_opcode == 0x8f && i.tm.extension_opcode == 0)) + return 1; + + /* movs, cmps, lods, scas. */ + if ((i.tm.base_opcode | 0xb) == 0xaf) + return 1; + + /* outs */ + if (base_opcode == 0x6f) + return 1; + } + + /* No memory operand. */ + if (!i.mem_operands) + return 0; + + if (any_vex_p) + { + /* vldmxcsr. */ + if (i.tm.base_opcode == 0xae + && i.tm.opcode_modifier.vex + && i.tm.opcode_modifier.vexopcode == VEX0F + && i.tm.extension_opcode == 2) + return 1; + } + else + { + /* test, not, neg, mul, imul, div, idiv. */ + if ((i.tm.base_opcode == 0xf6 || i.tm.base_opcode == 0xf7) + && i.tm.extension_opcode != 1) + return 1; + + /* inc, dec. */ + if (base_opcode == 0xff && i.tm.extension_opcode <= 1) + return 1; + + /* add, or, adc, sbb, and, sub, xor, cmp. */ + if (i.tm.base_opcode >= 0x80 && i.tm.base_opcode <= 0x83) + return 1; + + /* bt, bts, btr, btc. */ + if (i.tm.base_opcode == 0xfba + && (i.tm.extension_opcode >= 4 && i.tm.extension_opcode <= 7)) + return 1; + + /* rol, ror, rcl, rcr, shl/sal, shr, sar. */ + if ((base_opcode == 0xc1 + || (i.tm.base_opcode >= 0xd0 && i.tm.base_opcode <= 0xd3)) + && i.tm.extension_opcode != 6) + return 1; + + /* cmpxchg8b, cmpxchg16b, xrstors. */ + if (i.tm.base_opcode == 0xfc7 + && (i.tm.extension_opcode == 1 || i.tm.extension_opcode == 3)) + return 1; + + /* fxrstor, ldmxcsr, xrstor. */ + if (i.tm.base_opcode == 0xfae + && (i.tm.extension_opcode == 1 + || i.tm.extension_opcode == 2 + || i.tm.extension_opcode == 5)) + return 1; + + /* lgdt, lidt, lmsw. */ + if (i.tm.base_opcode == 0xf01 + && (i.tm.extension_opcode == 2 + || i.tm.extension_opcode == 3 + || i.tm.extension_opcode == 6)) + return 1; + + /* vmptrld */ + if (i.tm.base_opcode == 0xfc7 + && i.tm.extension_opcode == 6) + return 1; + + /* Check for x87 instructions. */ + if (i.tm.base_opcode >= 0xd8 && i.tm.base_opcode <= 0xdf) + { + /* Skip fst, fstp, fstenv, fstcw. */ + if (i.tm.base_opcode == 0xd9 + && (i.tm.extension_opcode == 2 + || i.tm.extension_opcode == 3 + || i.tm.extension_opcode == 6 + || i.tm.extension_opcode == 7)) + return 0; + + /* Skip fisttp, fist, fistp, fstp. */ + if (i.tm.base_opcode == 0xdb + && (i.tm.extension_opcode == 1 + || i.tm.extension_opcode == 2 + || i.tm.extension_opcode == 3 + || i.tm.extension_opcode == 7)) + return 0; + + /* Skip fisttp, fst, fstp, fsave, fstsw. */ + if (i.tm.base_opcode == 0xdd + && (i.tm.extension_opcode == 1 + || i.tm.extension_opcode == 2 + || i.tm.extension_opcode == 3 + || i.tm.extension_opcode == 6 + || i.tm.extension_opcode == 7)) + return 0; + + /* Skip fisttp, fist, fistp, fbstp, fistp. */ + if (i.tm.base_opcode == 0xdf + && (i.tm.extension_opcode == 1 + || i.tm.extension_opcode == 2 + || i.tm.extension_opcode == 3 + || i.tm.extension_opcode == 6 + || i.tm.extension_opcode == 7)) + return 0; + + return 1; + } + } + + dest = i.operands - 1; + + /* Check fake imm8 operand and 3 source operands. */ + if ((i.tm.opcode_modifier.immext + || i.tm.opcode_modifier.vexsources == VEX3SOURCES) + && i.types[dest].bitfield.imm8) + dest--; + + /* add, or, adc, sbb, and, sub, xor, cmp, test, xchg, xadd */ + if (!any_vex_p + && (base_opcode == 0x1 + || base_opcode == 0x9 + || base_opcode == 0x11 + || base_opcode == 0x19 + || base_opcode == 0x21 + || base_opcode == 0x29 + || base_opcode == 0x31 + || base_opcode == 0x39 + || (i.tm.base_opcode >= 0x84 && i.tm.base_opcode <= 0x87) + || base_opcode == 0xfc1)) + return 1; + + /* Check for load instruction. */ + return (i.types[dest].bitfield.class != ClassNone + || i.types[dest].bitfield.instance == Accum); +} + +/* Output lfence, 0xfaee8, after instruction. */ + +static void +insert_lfence_after (void) +{ + if (lfence_after_load && load_insn_p ()) + { + char *p = frag_more (3); + *p++ = 0xf; + *p++ = 0xae; + *p = 0xe8; + } +} + +/* Output lfence, 0xfaee8, before instruction. */ + +static void +insert_lfence_before (void) +{ + char *p; + + if (is_any_vex_encoding (&i.tm)) + return; + + if (i.tm.base_opcode == 0xff + && (i.tm.extension_opcode == 2 || i.tm.extension_opcode == 4)) + { + /* Insert lfence before indirect branch if needed. */ + + if (lfence_before_indirect_branch == lfence_branch_none) + return; + + if (i.operands != 1) + abort (); + + if (i.reg_operands == 1) + { + /* Indirect branch via register. Don't insert lfence with + -mlfence-after-load=yes. */ + if (lfence_after_load + || lfence_before_indirect_branch == lfence_branch_memory) + return; + } + else if (i.mem_operands == 1 + && lfence_before_indirect_branch != lfence_branch_register) + { + as_warn (_("indirect `%s` with memory operand should be avoided"), + i.tm.name); + return; + } + else + return; + + if (last_insn.kind != last_insn_other + && last_insn.seg == now_seg) + { + as_warn_where (last_insn.file, last_insn.line, + _("`%s` skips -mlfence-before-indirect-branch on `%s`"), + last_insn.name, i.tm.name); + return; + } + + p = frag_more (3); + *p++ = 0xf; + *p++ = 0xae; + *p = 0xe8; + return; + } + + /* Output or/not and lfence before ret. */ + if (lfence_before_ret != lfence_before_ret_none + && (i.tm.base_opcode == 0xc2 + || i.tm.base_opcode == 0xc3 + || i.tm.base_opcode == 0xca + || i.tm.base_opcode == 0xcb)) + { + if (last_insn.kind != last_insn_other + && last_insn.seg == now_seg) + { + as_warn_where (last_insn.file, last_insn.line, + _("`%s` skips -mlfence-before-ret on `%s`"), + last_insn.name, i.tm.name); + return; + } + if (lfence_before_ret == lfence_before_ret_or) + { + /* orl: 0x830c2400. */ + p = frag_more ((flag_code == CODE_64BIT ? 1 : 0) + 4 + 3); + if (flag_code == CODE_64BIT) + *p++ = 0x48; + *p++ = 0x83; + *p++ = 0xc; + *p++ = 0x24; + *p++ = 0x0; + } + else + { + p = frag_more ((flag_code == CODE_64BIT ? 2 : 0) + 6 + 3); + /* notl: 0xf71424. */ + if (flag_code == CODE_64BIT) + *p++ = 0x48; + *p++ = 0xf7; + *p++ = 0x14; + *p++ = 0x24; + /* notl: 0xf71424. */ + if (flag_code == CODE_64BIT) + *p++ = 0x48; + *p++ = 0xf7; + *p++ = 0x14; + *p++ = 0x24; + } + *p++ = 0xf; + *p++ = 0xae; + *p = 0xe8; + } +} + /* This is the guts of the machine-dependent assembler. LINE points to a machine dependent instruction. This function is supposed to emit the frags/bytes it assembles to. */ @@ -4628,9 +4927,13 @@ md_assemble (char *line) if (i.rex != 0) add_prefix (REX_OPCODE | i.rex); + insert_lfence_before (); + /* We are ready to output the insn. */ output_insn (); + insert_lfence_after (); + last_insn.seg = now_seg; if (i.tm.opcode_modifier.isprefix) @@ -12250,6 +12553,9 @@ const char *md_shortopts = "qnO::"; #define OPTION_MALIGN_BRANCH_PREFIX_SIZE (OPTION_MD_BASE + 28) #define OPTION_MALIGN_BRANCH (OPTION_MD_BASE + 29) #define OPTION_MBRANCHES_WITH_32B_BOUNDARIES (OPTION_MD_BASE + 30) +#define OPTION_MLFENCE_AFTER_LOAD (OPTION_MD_BASE + 31) +#define OPTION_MLFENCE_BEFORE_INDIRECT_BRANCH (OPTION_MD_BASE + 32) +#define OPTION_MLFENCE_BEFORE_RET (OPTION_MD_BASE + 33) struct option md_longopts[] = { @@ -12289,6 +12595,10 @@ struct option md_longopts[] = {"malign-branch-prefix-size", required_argument, NULL, OPTION_MALIGN_BRANCH_PREFIX_SIZE}, {"malign-branch", required_argument, NULL, OPTION_MALIGN_BRANCH}, {"mbranches-within-32B-boundaries", no_argument, NULL, OPTION_MBRANCHES_WITH_32B_BOUNDARIES}, + {"mlfence-after-load", required_argument, NULL, OPTION_MLFENCE_AFTER_LOAD}, + {"mlfence-before-indirect-branch", required_argument, NULL, + OPTION_MLFENCE_BEFORE_INDIRECT_BRANCH}, + {"mlfence-before-ret", required_argument, NULL, OPTION_MLFENCE_BEFORE_RET}, {"mamd64", no_argument, NULL, OPTION_MAMD64}, {"mintel64", no_argument, NULL, OPTION_MINTEL64}, {NULL, no_argument, NULL, 0} @@ -12668,6 +12978,41 @@ md_parse_option (int c, const char *arg) as_fatal (_("invalid -mfence-as-lock-add= option: `%s'"), arg); break; + case OPTION_MLFENCE_AFTER_LOAD: + if (strcasecmp (arg, "yes") == 0) + lfence_after_load = 1; + else if (strcasecmp (arg, "no") == 0) + lfence_after_load = 0; + else + as_fatal (_("invalid -mlfence-after-load= option: `%s'"), arg); + break; + + case OPTION_MLFENCE_BEFORE_INDIRECT_BRANCH: + if (strcasecmp (arg, "all") == 0) + lfence_before_indirect_branch = lfence_branch_all; + else if (strcasecmp (arg, "memory") == 0) + lfence_before_indirect_branch = lfence_branch_memory; + else if (strcasecmp (arg, "register") == 0) + lfence_before_indirect_branch = lfence_branch_register; + else if (strcasecmp (arg, "none") == 0) + lfence_before_indirect_branch = lfence_branch_none; + else + as_fatal (_("invalid -mlfence-before-indirect-branch= option: `%s'"), + arg); + break; + + case OPTION_MLFENCE_BEFORE_RET: + if (strcasecmp (arg, "or") == 0) + lfence_before_ret = lfence_before_ret_or; + else if (strcasecmp (arg, "not") == 0) + lfence_before_ret = lfence_before_ret_not; + else if (strcasecmp (arg, "none") == 0) + lfence_before_ret = lfence_before_ret_none; + else + as_fatal (_("invalid -mlfence-before-ret= option: `%s'"), + arg); + break; + case OPTION_MRELAX_RELOCATIONS: if (strcasecmp (arg, "yes") == 0) generate_relax_relocations = 1; @@ -13025,6 +13370,15 @@ md_show_usage (FILE *stream) -mbranches-within-32B-boundaries\n\ align branches within 32 byte boundary\n")); fprintf (stream, _("\ + -mlfence-after-load=[no|yes] (default: no)\n\ + generate lfence after load\n")); + fprintf (stream, _("\ + -mlfence-before-indirect-branch=[none|all|register|memory] (default: none)\n\ + generate lfence before indirect near branch\n")); + fprintf (stream, _("\ + -mlfence-before-ret=[none|or|not] (default: none)\n\ + generate lfence before ret\n")); + fprintf (stream, _("\ -mamd64 accept only AMD64 ISA [default]\n")); fprintf (stream, _("\ -mintel64 accept only Intel64 ISA\n")); @@ -13254,6 +13608,16 @@ i386_cons_align (int ignore ATTRIBUTE_UNUSED) last_insn.kind = last_insn_directive; last_insn.name = "constant directive"; last_insn.file = as_where (&last_insn.line); + if (lfence_before_ret != lfence_before_ret_none) + { + if (lfence_before_indirect_branch != lfence_branch_none) + as_warn (_("constant directive skips -mlfence-before-ret " + "and -mlfence-before-indirect-branch")); + else + as_warn (_("constant directive skips -mlfence-before-ret")); + } + else if (lfence_before_indirect_branch != lfence_branch_none) + as_warn (_("constant directive skips -mlfence-before-indirect-branch")); } } diff --git a/gas/doc/c-i386.texi b/gas/doc/c-i386.texi index c536759cb38..1dd99f91bb0 100644 --- a/gas/doc/c-i386.texi +++ b/gas/doc/c-i386.texi @@ -464,6 +464,49 @@ on an instruction. It is equivalent to @option{-malign-branch-prefix-size=5}. The default doesn't align branches. +@cindex @samp{-mlfence-after-load=} option, i386 +@cindex @samp{-mlfence-after-load=} option, x86-64 +@item -mlfence-after-load=@var{no} +@itemx -mlfence-after-load=@var{yes} +These options control whether the assembler should generate lfence +after load instructions. @option{-mlfence-after-load=@var{yes}} will +generate lfence. @option{-mlfence-after-load=@var{no}} will not generate +lfence, which is the default. + +@cindex @samp{-mlfence-before-indirect-branch=} option, i386 +@cindex @samp{-mlfence-before-indirect-branch=} option, x86-64 +@item -mlfence-before-indirect-branch=@var{none} +@item -mlfence-before-indirect-branch=@var{all} +@item -mlfence-before-indirect-branch=@var{register} +@itemx -mlfence-before-indirect-branch=@var{memory} +These options control whether the assembler should generate lfence +after indirect near branch instructions. +@option{-mlfence-before-indirect-branch=@var{all}} will generate lfence +after indirect near branch via register and issue a warning before +indirect near branch via memory. +@option{-mlfence-before-indirect-branch=@var{register}} will generate +lfence after indirect near branch via register. +@option{-mlfence-before-indirect-branch=@var{memory}} will issue a +warning before indirect near branch via memory. +@option{-mlfence-before-indirect-branch=@var{none}} will not generate +lfence nor issue warning, which is the default. Note that lfence won't +be generated before indirect near branch via register with +@option{-mlfence-after-load=@var{yes}} since lfence will be generated +after loading branch target register. + +@cindex @samp{-mlfence-before-ret=} option, i386 +@cindex @samp{-mlfence-before-ret=} option, x86-64 +@item -mlfence-before-ret=@var{none} +@item -mlfence-before-ret=@var{or} +@itemx -mlfence-before-ret=@var{not} +These options control whether the assembler should generate lfence +before ret. @option{-mlfence-before-ret=@var{or}} will generate +generate or instruction with lfence. +@option{-mlfence-before-ret=@var{not}} will generate not instruction +with lfence. +@option{-mlfence-before-ret=@var{none}} will not generate lfence, +which is the default. + @cindex @samp{-mx86-used-note=} option, i386 @cindex @samp{-mx86-used-note=} option, x86-64 @item -mx86-used-note=@var{no}