From: Jiang Yuxuan <[email protected]> helper_idivw_AX() and helper_idivl_EAX() perform the division before checking whether the quotient fits in the destination. If the dividend is the minimum value and the divisor is -1, the division overflows in the host: INT32_MIN / -1 and INT64_MIN / -1 are undefined behaviour in C and trap with SIGFPE on x86 hosts. QEMU then dies of a host SIGFPE instead of raising #DE in the guest, taking down the emulator (or the whole VM under TCG).
Reject the two overflowing cases before dividing. The 8-bit form is not affected (it widens the dividend to int), and the 64-bit form already checks first. Add a regression test for both the 16-bit and 32-bit forms, run by the i386 and x86_64 linux-user test suites. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/4687 Signed-off-by: fujo <[email protected]> Link: https://lore.kernel.org/r/caat7e3+jvr+yhzc3996kdc_xrh9avj4yly8x7+1hyjvoqk0...@mail.gmail.com Signed-off-by: Paolo Bonzini <[email protected]> --- target/i386/tcg/int_helper.c | 4 +- tests/tcg/i386/test-idiv-overflow.c | 70 +++++++++++++++++++++++++++++ tests/tcg/i386/meson.build | 1 + tests/tcg/x86_64/meson.build | 1 + 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/tcg/i386/test-idiv-overflow.c diff --git a/target/i386/tcg/int_helper.c b/target/i386/tcg/int_helper.c index 772e6adcc86..d4fa89166b4 100644 --- a/target/i386/tcg/int_helper.c +++ b/target/i386/tcg/int_helper.c @@ -91,7 +91,7 @@ void HELPER(idivw_AX)(CPUX86State *env, target_ulong t0) num = (env->regs[R_EAX] & 0xffff) | ((env->regs[R_EDX] & 0xffff) << 16); den = (int16_t)t0; - if (den == 0) { + if (den == 0 || (num == INT32_MIN && den == -1)) { raise_exception_ra(env, EXCP00_DIVZ, GETPC()); } q = (num / den); @@ -130,7 +130,7 @@ void HELPER(idivl_EAX)(CPUX86State *env, target_ulong t0) num = ((uint32_t)env->regs[R_EAX]) | ((uint64_t)((uint32_t)env->regs[R_EDX]) << 32); den = t0; - if (den == 0) { + if (den == 0 || (num == INT64_MIN && den == -1)) { raise_exception_ra(env, EXCP00_DIVZ, GETPC()); } q = (num / den); diff --git a/tests/tcg/i386/test-idiv-overflow.c b/tests/tcg/i386/test-idiv-overflow.c new file mode 100644 index 00000000000..e8fc55a0481 --- /dev/null +++ b/tests/tcg/i386/test-idiv-overflow.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Test that IDIV of the minimum dividend by -1 raises #DE, which Linux + * delivers to the guest as SIGFPE with si_code == FPE_INTDIV. + */ +#define _GNU_SOURCE +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> + +static sigjmp_buf idiv_jmp; +static volatile sig_atomic_t idiv_si_code; + +static void idiv_sig_handler(int sig, siginfo_t *info, void *puc) +{ + (void)sig; + (void)puc; + idiv_si_code = info->si_code; + siglongjmp(idiv_jmp, 1); +} + +static int check_idiv_overflow(int width) +{ + struct sigaction sa, old; + int ret = 1; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = idiv_sig_handler; + sa.sa_flags = SA_SIGINFO | SA_NODEFER; + sigemptyset(&sa.sa_mask); + sigaction(SIGFPE, &sa, &old); + + idiv_si_code = 0; + if (sigsetjmp(idiv_jmp, 1) == 0) { + if (width == 16) { + /* DX:AX = 0x8000:0x0000, divisor -1 */ + __asm__ volatile ("movl $0x8000, %%edx\n\t" + "xorl %%eax, %%eax\n\t" + "movl $-1, %%ecx\n\t" + "idivw %%cx" + : : : "eax", "ecx", "edx", "cc"); + } else { + /* EDX:EAX = 0x80000000:0x00000000, divisor -1 */ + __asm__ volatile ("movl $0x80000000, %%edx\n\t" + "xorl %%eax, %%eax\n\t" + "movl $-1, %%ecx\n\t" + "idivl %%ecx" + : : : "eax", "ecx", "edx", "cc"); + } + printf("FAIL: idiv%d did not raise #DE\n", width); + } else if (idiv_si_code != FPE_INTDIV) { + printf("FAIL: idiv%d raised SIGFPE with si_code %d\n", + width, (int)idiv_si_code); + } else { + ret = 0; + } + + sigaction(SIGFPE, &old, NULL); + return ret; +} + +int main(void) +{ + int ret = 0; + + ret |= check_idiv_overflow(16); + ret |= check_idiv_overflow(32); + return ret; +} diff --git a/tests/tcg/i386/meson.build b/tests/tcg/i386/meson.build index e7c30958608..648b516582e 100644 --- a/tests/tcg/i386/meson.build +++ b/tests/tcg/i386/meson.build @@ -35,6 +35,7 @@ tests += { 'cflags': [files('test-i386-code16.S'), files('test-i386-vm86.S'), '-lm', '-fno-pie', '-Wl,--no-warn-rwx-segments'], }, + 'test-idiv-overflow.c': {}, '../multiarch/sha512.c': { 'exe_name': 'sha512-sse', 'cflags': ['-msse4.1', '-O3'], diff --git a/tests/tcg/x86_64/meson.build b/tests/tcg/x86_64/meson.build index ad4d1f558a7..fc7f48e98eb 100644 --- a/tests/tcg/x86_64/meson.build +++ b/tests/tcg/x86_64/meson.build @@ -48,6 +48,7 @@ tests += { }, '../i386/test-i386-adcox.c': {'cflags': ['-O2']}, '../i386/test-i386-bmi2.c': {'cflags': ['-O2', '-fwrapv']}, + '../i386/test-idiv-overflow.c': {}, } tests += { -- 2.56.0
