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).
Update the invalid-slots test to expect SIGILL (exit code 132) instead
of the old exit(1) behavior now that the exception is properly handled.
Add invalid-duplex test for the specific case of invalid duplex encoding
after immext.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3291
Signed-off-by: Brian Cain <bcain@quicinc.com>
---
linux-user/hexagon/cpu_loop.c | 4 ++++
target/hexagon/decode.c | 13 +++++++++++--
target/hexagon/translate.c | 18 ++++++++++++++++--
tests/tcg/hexagon/invalid-duplex.c | 28 ++++++++++++++++++++++++++++
tests/tcg/hexagon/Makefile.target | 11 ++++++++++-
5 files changed, 69 insertions(+), 5 deletions(-)
create mode 100644 tests/tcg/hexagon/invalid-duplex.c
diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c
index 1941f4c9c1..c0e1098e3f 100644
--- a/linux-user/hexagon/cpu_loop.c
+++ b/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;
diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c
index b5ece60450..69ba1ec96c 100644
--- a/target/hexagon/decode.c
+++ b/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
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index e88e19cc1a..7fe8b35351 100644
--- a/target/hexagon/translate.c
+++ b/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);
}
}
diff --git a/tests/tcg/hexagon/invalid-duplex.c b/tests/tcg/hexagon/invalid-duplex.c
new file mode 100644
index 0000000000..88ad5024e9
--- /dev/null
+++ b/tests/tcg/hexagon/invalid-duplex.c
@@ -0,0 +1,28 @@
+/*
+ * Test that invalid duplex encodings are properly rejected.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * This test uses an invalid packet encoding where:
+ * - 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.
+ */
+
+int main()
+{
+ asm volatile(
+ /* Invalid packet: immext followed by duplex with invalid slot0 */
+ ".word 0x0fff6fff\n" /* immext(#0xfffbffc0), parse=01 */
+ ".word 0x600237b0\n" /* duplex: slot0=0x17b0 (invalid), slot1=0x0002 */
+ : : : "memory");
+ return 0;
+}
diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target
index e5182c01d8..b0e20139c2 100644
--- a/tests/tcg/hexagon/Makefile.target
+++ b/tests/tcg/hexagon/Makefile.target
@@ -51,13 +51,22 @@ HEX_TESTS += scatter_gather
HEX_TESTS += hvx_misc
HEX_TESTS += hvx_histogram
HEX_TESTS += invalid-slots
+HEX_TESTS += invalid-duplex
HEX_TESTS += unaligned_pc
run-and-check-exception = $(call run-test,$2,$3 2>$2.stderr; \
test $$? -eq 1 && grep -q "exception $(strip $1)" $2.stderr)
+# Check that command dies from SIGILL (exit code = 128 + 4 = 132)
run-invalid-slots: invalid-slots
- $(call run-and-check-exception, 0x15, $@, $(QEMU) $(QEMU_OPTS) $<)
+ $(call quiet-command, \
+ $(QEMU) $(QEMU_OPTS) $< ; test $$? -eq 132, \
+ TEST, invalid-slots on $(TARGET_NAME))
+
+run-invalid-duplex: invalid-duplex
+ $(call quiet-command, \
+ $(QEMU) $(QEMU_OPTS) $< ; test $$? -eq 132, \
+ TEST, invalid-duplex on $(TARGET_NAME))
HEX_TESTS += test_abs
HEX_TESTS += test_bitcnt
--
2.34.1
On 2/6/26 10:38 AM, Brian Cain wrote: > 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). > > Update the invalid-slots test to expect SIGILL (exit code 132) instead > of the old exit(1) behavior now that the exception is properly handled. > Add invalid-duplex test for the specific case of invalid duplex encoding > after immext. > > Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3291 > Signed-off-by: Brian Cain <bcain@quicinc.com> > --- > linux-user/hexagon/cpu_loop.c | 4 ++++ > target/hexagon/decode.c | 13 +++++++++++-- > target/hexagon/translate.c | 18 ++++++++++++++++-- > tests/tcg/hexagon/invalid-duplex.c | 28 ++++++++++++++++++++++++++++ > tests/tcg/hexagon/Makefile.target | 11 ++++++++++- > 5 files changed, 69 insertions(+), 5 deletions(-) > create mode 100644 tests/tcg/hexagon/invalid-duplex.c > Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
© 2016 - 2026 Red Hat, Inc.