| From ae531041c7c5956672342f89c486a011c84f027f Mon Sep 17 00:00:00 2001 |
| From: "H.J. Lu" <hjl.tools@gmail.com> |
| Date: Wed, 11 Mar 2020 09:46:19 -0700 |
| Subject: [PATCH 1/1] 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=. |
| |
| Signed-off-by: Anuj Mittal <anuj.mittal@intel.com> |
| Upstream-Status: Backport [https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=ae531041c7c5956672342f89c486a011c84f027f] |
| CVE: CVE-2020-0551 |
| --- |
| 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} |
| -- |
| 2.18.2 |