x86/insn: Stop decoding i64 instructions in x86-64 mode at opcode
authorMasami Hiramatsu (Google) <mhiramat@kernel.org>
Mon, 28 Apr 2025 01:48:20 +0000 (10:48 +0900)
committerIngo Molnar <mingo@kernel.org>
Tue, 6 May 2025 10:03:16 +0000 (12:03 +0200)
In commit 2e044911be75 ("x86/traps: Decode 0xEA instructions as #UD")
FineIBT starts using 0xEA as an invalid instruction like UD2. But
insn decoder always returns the length of "0xea" instruction as 7
because it does not check the (i64) superscript.

The x86 instruction decoder should also decode 0xEA on x86-64 as
a one-byte invalid instruction by decoding the "(i64)" superscript tag.

This stops decoding instruction which has (i64) but does not have (o64)
superscript in 64-bit mode at opcode and skips other fields.

With this change, insn_decoder_test says 0xea is 1 byte length if
x86-64 (-y option means 64-bit):

   $ printf "0:\tea\t\n" | insn_decoder_test -y -v
   insn_decoder_test: success: Decoded and checked 1 instructions

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/174580490000.388420.5225447607417115496.stgit@devnote2
arch/x86/include/asm/inat.h
arch/x86/lib/insn.c
arch/x86/lib/x86-opcode-map.txt
arch/x86/tools/gen-insn-attr-x86.awk
tools/arch/x86/include/asm/inat.h
tools/arch/x86/lib/insn.c
tools/arch/x86/lib/x86-opcode-map.txt
tools/arch/x86/tools/gen-insn-attr-x86.awk

index 53e4015242b4f7f86b1a6ab95891a4005219de47..97f341777db54c73b9fa9320c7af17efaae39f50 100644 (file)
@@ -82,6 +82,7 @@
 #define INAT_NO_REX2   (1 << (INAT_FLAG_OFFS + 8))
 #define INAT_REX2_VARIANT      (1 << (INAT_FLAG_OFFS + 9))
 #define INAT_EVEX_SCALABLE     (1 << (INAT_FLAG_OFFS + 10))
+#define INAT_INV64     (1 << (INAT_FLAG_OFFS + 11))
 /* Attribute making macros for attribute tables */
 #define INAT_MAKE_PREFIX(pfx)  (pfx << INAT_PFX_OFFS)
 #define INAT_MAKE_ESCAPE(esc)  (esc << INAT_ESC_OFFS)
@@ -242,4 +243,9 @@ static inline int inat_evex_scalable(insn_attr_t attr)
 {
        return attr & INAT_EVEX_SCALABLE;
 }
+
+static inline int inat_is_invalid64(insn_attr_t attr)
+{
+       return attr & INAT_INV64;
+}
 #endif
index 6ffb931b9fb14ff87251a60bc61c9639497238c9..149a57e334ab5cf2bf9b10fb7b2b7dc80b485a2b 100644 (file)
@@ -324,6 +324,11 @@ int insn_get_opcode(struct insn *insn)
        }
 
        insn->attr = inat_get_opcode_attribute(op);
+       if (insn->x86_64 && inat_is_invalid64(insn->attr)) {
+               /* This instruction is invalid, like UD2. Stop decoding. */
+               insn->attr &= INAT_INV64;
+       }
+
        while (inat_is_escape(insn->attr)) {
                /* Get escaped opcode */
                op = get_next(insn_byte_t, insn);
@@ -337,6 +342,7 @@ int insn_get_opcode(struct insn *insn)
                insn->attr = 0;
                return -EINVAL;
        }
+
 end:
        opcode->got = 1;
        return 0;
@@ -658,7 +664,6 @@ int insn_get_immediate(struct insn *insn)
        }
 
        if (!inat_has_immediate(insn->attr))
-               /* no immediates */
                goto done;
 
        switch (inat_immediate_size(insn->attr)) {
index cd3fd5155f6ecea43e3825a1943c9b39529ba46c..262f7ca1fb9527156453f3b9d85a04235f361f76 100644 (file)
@@ -147,7 +147,7 @@ AVXcode:
 # 0x60 - 0x6f
 60: PUSHA/PUSHAD (i64)
 61: POPA/POPAD (i64)
-62: BOUND Gv,Ma (i64) | EVEX (Prefix)
+62: BOUND Gv,Ma (i64) | EVEX (Prefix),(o64)
 63: ARPL Ew,Gw (i64) | MOVSXD Gv,Ev (o64)
 64: SEG=FS (Prefix)
 65: SEG=GS (Prefix)
@@ -253,8 +253,8 @@ c0: Grp2 Eb,Ib (1A)
 c1: Grp2 Ev,Ib (1A)
 c2: RETN Iw (f64)
 c3: RETN
-c4: LES Gz,Mp (i64) | VEX+2byte (Prefix)
-c5: LDS Gz,Mp (i64) | VEX+1byte (Prefix)
+c4: LES Gz,Mp (i64) | VEX+2byte (Prefix),(o64)
+c5: LDS Gz,Mp (i64) | VEX+1byte (Prefix),(o64)
 c6: Grp11A Eb,Ib (1A)
 c7: Grp11B Ev,Iz (1A)
 c8: ENTER Iw,Ib
index 5770c8097f320643e4d43be7a9cb2fb82c1f554e..2c19d7fc8a85597616b6e1796240d428e4f4a5b7 100644 (file)
@@ -64,6 +64,8 @@ BEGIN {
 
        modrm_expr = "^([CDEGMNPQRSUVW/][a-z]+|NTA|T[012])"
        force64_expr = "\\([df]64\\)"
+       invalid64_expr = "\\(i64\\)"
+       only64_expr = "\\(o64\\)"
        rex_expr = "^((REX(\\.[XRWB]+)+)|(REX$))"
        rex2_expr = "\\(REX2\\)"
        no_rex2_expr = "\\(!REX2\\)"
@@ -319,6 +321,11 @@ function convert_operands(count,opnd,       i,j,imm,mod)
                if (match(ext, force64_expr))
                        flags = add_flags(flags, "INAT_FORCE64")
 
+               # check invalid in 64-bit (and no only64)
+               if (match(ext, invalid64_expr) &&
+                   !match($0, only64_expr))
+                       flags = add_flags(flags, "INAT_INV64")
+
                # check REX2 not allowed
                if (match(ext, no_rex2_expr))
                        flags = add_flags(flags, "INAT_NO_REX2")
index 253690eb3c268a86a7d9a933279815deb185f6b6..183aa662b16523a3de3256552a68cbafb03da6fe 100644 (file)
@@ -82,6 +82,7 @@
 #define INAT_NO_REX2   (1 << (INAT_FLAG_OFFS + 8))
 #define INAT_REX2_VARIANT      (1 << (INAT_FLAG_OFFS + 9))
 #define INAT_EVEX_SCALABLE     (1 << (INAT_FLAG_OFFS + 10))
+#define INAT_INV64     (1 << (INAT_FLAG_OFFS + 11))
 /* Attribute making macros for attribute tables */
 #define INAT_MAKE_PREFIX(pfx)  (pfx << INAT_PFX_OFFS)
 #define INAT_MAKE_ESCAPE(esc)  (esc << INAT_ESC_OFFS)
@@ -242,4 +243,9 @@ static inline int inat_evex_scalable(insn_attr_t attr)
 {
        return attr & INAT_EVEX_SCALABLE;
 }
+
+static inline int inat_is_invalid64(insn_attr_t attr)
+{
+       return attr & INAT_INV64;
+}
 #endif
index e91d4c4e1c162103f9da3b8d88351563ca89a9c8..bce69c6bfa6972fc32f35da1958e6f2173a72887 100644 (file)
@@ -324,6 +324,11 @@ int insn_get_opcode(struct insn *insn)
        }
 
        insn->attr = inat_get_opcode_attribute(op);
+       if (insn->x86_64 && inat_is_invalid64(insn->attr)) {
+               /* This instruction is invalid, like UD2. Stop decoding. */
+               insn->attr &= INAT_INV64;
+       }
+
        while (inat_is_escape(insn->attr)) {
                /* Get escaped opcode */
                op = get_next(insn_byte_t, insn);
@@ -337,6 +342,7 @@ int insn_get_opcode(struct insn *insn)
                insn->attr = 0;
                return -EINVAL;
        }
+
 end:
        opcode->got = 1;
        return 0;
@@ -658,7 +664,6 @@ int insn_get_immediate(struct insn *insn)
        }
 
        if (!inat_has_immediate(insn->attr))
-               /* no immediates */
                goto done;
 
        switch (inat_immediate_size(insn->attr)) {
index cd3fd5155f6ecea43e3825a1943c9b39529ba46c..262f7ca1fb9527156453f3b9d85a04235f361f76 100644 (file)
@@ -147,7 +147,7 @@ AVXcode:
 # 0x60 - 0x6f
 60: PUSHA/PUSHAD (i64)
 61: POPA/POPAD (i64)
-62: BOUND Gv,Ma (i64) | EVEX (Prefix)
+62: BOUND Gv,Ma (i64) | EVEX (Prefix),(o64)
 63: ARPL Ew,Gw (i64) | MOVSXD Gv,Ev (o64)
 64: SEG=FS (Prefix)
 65: SEG=GS (Prefix)
@@ -253,8 +253,8 @@ c0: Grp2 Eb,Ib (1A)
 c1: Grp2 Ev,Ib (1A)
 c2: RETN Iw (f64)
 c3: RETN
-c4: LES Gz,Mp (i64) | VEX+2byte (Prefix)
-c5: LDS Gz,Mp (i64) | VEX+1byte (Prefix)
+c4: LES Gz,Mp (i64) | VEX+2byte (Prefix),(o64)
+c5: LDS Gz,Mp (i64) | VEX+1byte (Prefix),(o64)
 c6: Grp11A Eb,Ib (1A)
 c7: Grp11B Ev,Iz (1A)
 c8: ENTER Iw,Ib
index 5770c8097f320643e4d43be7a9cb2fb82c1f554e..2c19d7fc8a85597616b6e1796240d428e4f4a5b7 100644 (file)
@@ -64,6 +64,8 @@ BEGIN {
 
        modrm_expr = "^([CDEGMNPQRSUVW/][a-z]+|NTA|T[012])"
        force64_expr = "\\([df]64\\)"
+       invalid64_expr = "\\(i64\\)"
+       only64_expr = "\\(o64\\)"
        rex_expr = "^((REX(\\.[XRWB]+)+)|(REX$))"
        rex2_expr = "\\(REX2\\)"
        no_rex2_expr = "\\(!REX2\\)"
@@ -319,6 +321,11 @@ function convert_operands(count,opnd,       i,j,imm,mod)
                if (match(ext, force64_expr))
                        flags = add_flags(flags, "INAT_FORCE64")
 
+               # check invalid in 64-bit (and no only64)
+               if (match(ext, invalid64_expr) &&
+                   !match($0, only64_expr))
+                       flags = add_flags(flags, "INAT_INV64")
+
                # check REX2 not allowed
                if (match(ext, no_rex2_expr))
                        flags = add_flags(flags, "INAT_NO_REX2")