Skip to content

i386, x86_64: Improper decoding of 0f c7 xx instructions leads to CFI integrity check failure

Host environment

  • Operating system: Kali Linux 2025.3

  • OS/kernel version: Linux kali 6.16.8+kali-amd64 #1 SMP PREEMPT_DYNAMIC Kali 6.16.8-1kali1 (2025-09-24) x86_64 GNU/Linux

  • Architecture: x86_64

  • QEMU flavor: qemu-system-i386, qemu-system-x86_64, qemu-i386, qemu-x86_64

  • QEMU version: 10.1.50 (built from source)

  • QEMU command line:

    qemu-x86_64 ./poc

Emulated/Virtualized environment

  • Operating system: Does not matter
  • OS/kernel version: Does not matter
  • Architecture: i386, x86_64

Description of problem

When decoding 0f c7 xx instructions, decode_group9 does not update the value of *entry when certain values of modrm byte are used. This makes decode.e.gen point to decode_group9 after decoding the instruction, and then decode.e.gen is called in disas_insn. If QEMU was compiled with CFI and CFI debug, the following error message is displayed: ../target/i386/tcg/decode-new.c.inc:2862:9: runtime error: control flow integrity check for type 'void (struct DisasContext *, struct X86DecodedInsn *)' failed during indirect function call

It seems that parameters env, entry and b of decode_group9 are not used when decode.e.gen is called, rendering this bug harmless and making the instructions that trigger this bug behave like nop (which is the reason this bug is not reported as a security issue).

The expected behavior for such instructions is #UD exception being raised.

Steps to reproduce

To get an error message, QEMU should be built with CFI and CFI debug enabled. Without CFI, the instruction will execute as nop

qemu-i386, qemu-x86_64:

  1. Compile the following source code with nasm -f elf64 (or nasm -f elf32 if building for qemu-i386):
bits 64

section .text
global _start
_start:
db 0xf, 0xc7, 0xbf ; this should trigger the bug
jmp $
  1. Link the binary using ld or ld.lld (add -melf_i386 if building for qemu-i386 and linking using ld)
  2. Execute the binary with qemu-x86_64 (or qemu-i386, depending on the format used when you compiled the code):
./qemu-x86_64 <path/to/the/linked/binary>

qemu-system-i386, qemu-system-x86_64:

  1. Compile the following source code with nasm -f bin:
bits 16

org 0x7c00

db 0xf, 0xc7, 0xbf
jmp $

times 510 - ($ - $$) db 0
dw 0xaa55
  1. Start qemu-system-x86_64 or qemu-system-i386 (without KVM!):
./qemu-system-x86_64 <path/to/output/file/from/nasm>

Additional information

Backtrace on CFI error (qemu-x86_64):

(gdb) bt
#0  0x0000555555b427c4 in internal_syscall<int, unsigned long, unsigned long> ()
    at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_syscall_linux_x86_64.inc:41
#1  internal_read () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp:328
#2  0x0000555555b44d70 in __sanitizer::ReadFromFile(int, void*, unsigned long, unsigned long*, int*) ()
    at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_posix.cpp:186
#3  0x0000555555b59e9a in ReadFromSymbolizer () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:548
#4  0x0000555555b59bdc in SendCommandImpl () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:526
#5  SendCommand () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:510
#6  0x0000555555b598ef in __sanitizer::LLVMSymbolizer::SymbolizePC(unsigned long, __sanitizer::SymbolizedStack*) ()
    at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:430
#7  0x0000555555b580e7 in SymbolizePC () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp:97
#8  0x0000555555b69c2e in handleCFIBadIcall () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/ubsan/ubsan_handlers.cpp:878
#9  0x0000555555b69e9d in __ubsan_handle_cfi_check_fail_abort () at /home/user/Projects/Tools/llvm-x86_64/compiler-rt/lib/ubsan/ubsan_handlers.cpp:938
#10 0x0000555555eb525a in disas_insn (s=0x7bfff4b00420, cpu=0x7ebff5be0200) at ../target/i386/tcg/decode-new.c.inc:2862
#11 0x0000555555eb115f in i386_tr_translate_insn (dcbase=0x7bfff4b00420, cpu=0x7ebff5be0200) at ../target/i386/tcg/translate.c:3840
#12 0x0000555555cb1c78 in translator_loop (cpu=0x7ebff5be0200, tb=0x7bffea200040 <code_gen_buffer+19>, max_insns=0x7bfff4920160, pc=4198400, host_pc=0x401000, 
    ops=0x55555604c560 <i386_tr_ops>, db=0x7bfff4b00420) at ../accel/tcg/translator.c:176
#13 0x0000555555ec2ea1 in x86_translate_code (cpu=0x7ebff5be0200, tb=0x7bffea200040 <code_gen_buffer+19>, max_insns=0x7bfff4920160, pc=4198400, host_pc=0x401000)
    at ../target/i386/tcg/translate.c:3931
#14 0x0000555555caf713 in setjmp_gen_code (env=0x7ebff5be3d00, tb=0x7bffea200040 <code_gen_buffer+19>, pc=4198400, host_pc=0x401000, max_insns=0x7bfff4920160, 
    ti=0x7bfff4920170) at ../accel/tcg/translate-all.c:251
#15 0x0000555555cad70e in tb_gen_code (cpu=0x7ebff5be0200, s=...) at ../accel/tcg/translate-all.c:324
#16 0x0000555555c8ab32 in cpu_exec_loop (cpu=0x7ebff5be0200, sc=0x7bfff48108a0) at ../accel/tcg/cpu-exec.c:972
#17 0x0000555555c8a20d in cpu_exec_setjmp (cpu=0x7ebff5be0200, sc=0x7bfff48108a0) at ../accel/tcg/cpu-exec.c:1018
#18 0x0000555555c8a09a in cpu_exec (cpu=0x7ebff5be0200) at ../accel/tcg/cpu-exec.c:1044
#19 0x0000555555ce43b3 in cpu_loop (env=0x7ebff5be3d00) at ../linux-user/x86_64/../i386/cpu_loop.c:215
#20 0x0000555555eef799 in main (argc=2, argv=0x7fffffffdc78, envp=0x7fffffffdc90) at ../linux-user/main.c:1035
(gdb) 

The bug was discovered while fuzzing qemu-x86_64 with afl++

Edited by Andrey
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information