Browse Source

target/hexagon: Fix invalid duplex decoding

When decoding a duplex instruction, if the slot0 sub-instruction fails
to decode after slot1 succeeds, QEMU was leaving the packet in a
partially-decoded state. This allowed invalid duplex encodings (where
one sub-instruction doesn't match any valid pattern) to be executed
incorrectly.

Fix by resetting the decoder state when slot0 fails, returning an empty
instruction that triggers an exception.

Add gen_exception_decode_fail() for raising exceptions when decode fails
before ctx->next_PC is initialized. This keeps gen_exception_end_tb()
semantics unchanged (it continues to use ctx->next_PC for the exception
PC after successful decode).

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3291
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Taylor Simpson <ltaylorsimpson@gmail.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
master
Brian Cain 2 months ago
parent
commit
f87873a134
  1. 4
      linux-user/hexagon/cpu_loop.c
  2. 13
      target/hexagon/decode.c
  3. 18
      target/hexagon/translate.c
  4. 1
      tests/tcg/hexagon/Makefile.target
  5. 81
      tests/tcg/hexagon/invalid-encoding.c

4
linux-user/hexagon/cpu_loop.c

@ -64,6 +64,10 @@ void cpu_loop(CPUHexagonState *env)
force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN,
env->gpr[HEX_REG_R31]);
break;
case HEX_CAUSE_INVALID_PACKET:
force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC,
env->gpr[HEX_REG_PC]);
break;
case EXCP_ATOMIC:
cpu_exec_step_atomic(cs);
break;

13
target/hexagon/decode.c

@ -509,8 +509,14 @@ decode_insns(DisasContext *ctx, Insn *insn, uint32_t encoding)
insn->iclass = iclass_bits(encoding);
return 2;
}
/*
* Slot0 decode failed after slot1 succeeded. This is an invalid
* duplex encoding (both sub-instructions must be valid).
*/
ctx->insn = --insn;
}
g_assert_not_reached();
/* Invalid duplex encoding - return 0 to signal failure */
return 0;
}
}
@ -674,7 +680,10 @@ int decode_packet(DisasContext *ctx, int max_words, const uint32_t *words,
encoding32 = words[words_read];
end_of_packet = is_packet_end(encoding32);
new_insns = decode_insns(ctx, insn, encoding32);
g_assert(new_insns > 0);
if (new_insns == 0) {
/* Invalid instruction encoding */
return 0;
}
/*
* If we saw an extender, mark next word extended so immediate
* decode works

18
target/hexagon/translate.c

@ -195,7 +195,21 @@ static void gen_exception_end_tb(DisasContext *ctx, int excp)
tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->next_PC);
gen_exception_raw(excp);
ctx->base.is_jmp = DISAS_NORETURN;
}
/*
* Generate exception for decode failures. Unlike gen_exception_end_tb,
* this is used when decode fails before ctx->next_PC is initialized.
*/
static void gen_exception_decode_fail(DisasContext *ctx, int nwords, int excp)
{
target_ulong fail_pc = ctx->base.pc_next + nwords * sizeof(uint32_t);
gen_exec_counters(ctx);
tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], fail_pc);
gen_exception_raw(excp);
ctx->base.is_jmp = DISAS_NORETURN;
ctx->base.pc_next = fail_pc;
}
static int read_packet_words(CPUHexagonState *env, DisasContext *ctx,
@ -935,7 +949,7 @@ static void decode_and_translate_packet(CPUHexagonState *env, DisasContext *ctx)
nwords = read_packet_words(env, ctx, words);
if (!nwords) {
gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_PACKET);
gen_exception_decode_fail(ctx, 0, HEX_CAUSE_INVALID_PACKET);
return;
}
@ -950,7 +964,7 @@ static void decode_and_translate_packet(CPUHexagonState *env, DisasContext *ctx)
gen_commit_packet(ctx);
ctx->base.pc_next += pkt.encod_pkt_size_in_bytes;
} else {
gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_PACKET);
gen_exception_decode_fail(ctx, nwords, HEX_CAUSE_INVALID_PACKET);
}
}

1
tests/tcg/hexagon/Makefile.target

@ -51,6 +51,7 @@ HEX_TESTS += scatter_gather
HEX_TESTS += hvx_misc
HEX_TESTS += hvx_histogram
HEX_TESTS += invalid-slots
HEX_TESTS += invalid-encoding
HEX_TESTS += unaligned_pc
run-and-check-exception = $(call run-test,$2,$3 2>$2.stderr; \

81
tests/tcg/hexagon/invalid-encoding.c

@ -0,0 +1,81 @@
/*
* Test that invalid instruction encodings are properly rejected.
*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void *resume_pc;
static void handle_sigill(int sig, siginfo_t *info, void *puc)
{
ucontext_t *uc = (ucontext_t *)puc;
if (sig != SIGILL) {
_exit(EXIT_FAILURE);
}
uc->uc_mcontext.r0 = SIGILL;
uc->uc_mcontext.pc = (unsigned long)resume_pc;
}
/*
* Each test function:
* - Sets r0 to something other than SIGILL
* - Stores the resume address into resume_pc
* - Executes the invalid encoding
* - The handler sets r0 = SIGILL and resumes after the faulting packet
* - Returns the value in r0
*/
/*
* Invalid duplex encoding (issue #3291):
* - Word 0: 0x0fff6fff = immext(#0xfffbffc0), parse bits = 01
* - Word 1: 0x600237b0 = duplex with:
* - slot0 = 0x17b0 (invalid S2 subinstruction encoding)
* - slot1 = 0x0002 (valid SA1_addi)
* - duplex iclass = 7 (S2 for slot0, A for slot1)
*
* Since slot0 doesn't decode to any valid S2 subinstruction, this packet
* should be rejected and raise SIGILL.
*/
static int test_invalid_duplex(void)
{
int sig;
asm volatile(
"r0 = #0\n"
"r1 = ##1f\n"
"memw(%1) = r1\n"
".word 0x0fff6fff\n" /* immext(#0xfffbffc0), parse=01 */
".word 0x600237b0\n" /* duplex: slot0=0x17b0 (invalid) */
"1:\n"
"%0 = r0\n"
: "=r"(sig)
: "r"(&resume_pc)
: "r0", "r1", "memory");
return sig;
}
int main()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = handle_sigill;
act.sa_flags = SA_SIGINFO;
assert(sigaction(SIGILL, &act, NULL) == 0);
assert(test_invalid_duplex() == SIGILL);
puts("PASS");
return EXIT_SUCCESS;
}
Loading…
Cancel
Save