target/arm: fault on tag store to canonical tag
According to ARM ARM, section "Memory region tagging types", tag-store instructions targeting canonically tagged regions cause a stage 1 permission fault with MTX enabled. Signed-off-by: Gabriel Brookman <brookmangabriel@gmail.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260529-feat-mte4-v7-10-ccbd3c14eb3c@gmail.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Gabriel Brookman committed
May 29, 2026 at 12:52 UTC
692a389737f118416af42c30d5785fcd8dbba578
3 files changed
+65
-28
target/arm/tcg/helper-a64-defs.h
+6
-6
@@ -103,15 +103,15 @@ DEF_HELPER_FLAGS_3(mte_check_zva, TCG_CALL_NO_WG, i64, env, i32, i64)
103
DEF_HELPER_FLAGS_3(irg, TCG_CALL_NO_RWG, i64, env, i64, i64)
104
DEF_HELPER_FLAGS_4(addsubg, TCG_CALL_NO_RWG_SE, i64, env, i64, s32, i32)
105
DEF_HELPER_FLAGS_4(ldg, TCG_CALL_NO_WG, i64, env, i64, i64, i32)
106
-DEF_HELPER_FLAGS_3(stg, TCG_CALL_NO_WG, void, env, i64, i64)
107
-DEF_HELPER_FLAGS_3(stg_parallel, TCG_CALL_NO_WG, void, env, i64, i64)
106
+DEF_HELPER_FLAGS_4(stg, TCG_CALL_NO_WG, void, env, i64, i64, i32)
107
+DEF_HELPER_FLAGS_4(stg_parallel, TCG_CALL_NO_WG, void, env, i64, i64, i32)
108
DEF_HELPER_FLAGS_2(stg_stub, TCG_CALL_NO_WG, void, env, i64)
109
-DEF_HELPER_FLAGS_3(st2g, TCG_CALL_NO_WG, void, env, i64, i64)
110
-DEF_HELPER_FLAGS_3(st2g_parallel, TCG_CALL_NO_WG, void, env, i64, i64)
109
+DEF_HELPER_FLAGS_4(st2g, TCG_CALL_NO_WG, void, env, i64, i64, i32)
110
+DEF_HELPER_FLAGS_4(st2g_parallel, TCG_CALL_NO_WG, void, env, i64, i64, i32)
111
DEF_HELPER_FLAGS_2(st2g_stub, TCG_CALL_NO_WG, void, env, i64)
112
DEF_HELPER_FLAGS_3(ldgm, TCG_CALL_NO_WG, i64, env, i64, i32)
113
-DEF_HELPER_FLAGS_3(stgm, TCG_CALL_NO_WG, void, env, i64, i64)
114
-DEF_HELPER_FLAGS_3(stzgm_tags, TCG_CALL_NO_WG, void, env, i64, i64)
113
+DEF_HELPER_FLAGS_4(stgm, TCG_CALL_NO_WG, void, env, i64, i64, i32)
114
+DEF_HELPER_FLAGS_4(stzgm_tags, TCG_CALL_NO_WG, void, env, i64, i64, i32)
115
116
DEF_HELPER_FLAGS_4(arm_unaligned_access, TCG_CALL_NO_WG,
117
noreturn, env, i64, i32, i32)
target/arm/tcg/mte_helper.c
+43
-12
@@ -231,6 +231,19 @@ uint8_t *allocation_tag_mem_probe(CPUARMState *env, int ptr_mmu_idx,
231
#endif
232
}
233
234
+static G_NORETURN void canonical_tag_write_fail(CPUARMState *env,
235
+ uint64_t dirty_ptr, uintptr_t ra)
236
+{
237
+ uint64_t syn;
238
+
239
+ env->exception.vaddress = dirty_ptr;
240
+
241
+ syn = syn_data_abort_no_iss(arm_current_el(env) != 0, 0, 0, 0, 0, 1, 0);
242
+ syn |= BIT_ULL(42); /* TnD is bit 42 */
243
+
244
+ raise_exception_ra(env, EXCP_DATA_ABORT, syn, exception_target_el(env), ra);
245
+}
246
+
247
static uint8_t *allocation_tag_mem(CPUARMState *env, int ptr_mmu_idx,
248
uint64_t ptr, MMUAccessType ptr_access,
249
int ptr_size, MMUAccessType tag_access,
@@ -360,7 +373,7 @@ static void store_tag1_parallel(uint64_t ptr, uint8_t *mem, int tag)
373
typedef void stg_store1(uint64_t, uint8_t *, int);
374
375
static inline void do_stg(CPUARMState *env, uint64_t ptr, uint64_t xt,
363
- uintptr_t ra, stg_store1 store1)
376
+ uint32_t mtx, uintptr_t ra, stg_store1 store1)
377
{
378
int mmu_idx = arm_env_mmu_index(env);
379
uint8_t *mem;
@@ -374,17 +387,20 @@ static inline void do_stg(CPUARMState *env, uint64_t ptr, uint64_t xt,
387
/* Store if page supports tags. */
388
if (mem) {
389
store1(ptr, mem, allocation_tag_from_addr(xt));
390
+ } else if (mtx) {
391
+ canonical_tag_write_fail(env, ptr, ra);
392
}
393
}
394
380
-void HELPER(stg)(CPUARMState *env, uint64_t ptr, uint64_t xt)
395
+void HELPER(stg)(CPUARMState *env, uint64_t ptr, uint64_t xt, uint32_t mtx)
396
{
382
- do_stg(env, ptr, xt, GETPC(), store_tag1);
397
+ do_stg(env, ptr, xt, mtx, GETPC(), store_tag1);
398
}
399
385
-void HELPER(stg_parallel)(CPUARMState *env, uint64_t ptr, uint64_t xt)
400
+void HELPER(stg_parallel)(CPUARMState *env, uint64_t ptr, uint64_t xt,
401
+ uint32_t mtx)
402
{
387
- do_stg(env, ptr, xt, GETPC(), store_tag1_parallel);
403
+ do_stg(env, ptr, xt, mtx, GETPC(), store_tag1_parallel);
404
}
405
406
void HELPER(stg_stub)(CPUARMState *env, uint64_t ptr)
@@ -397,7 +413,7 @@ void HELPER(stg_stub)(CPUARMState *env, uint64_t ptr)
413
}
414
415
static inline void do_st2g(CPUARMState *env, uint64_t ptr, uint64_t xt,
400
- uintptr_t ra, stg_store1 store1)
416
+ uint32_t mtx, uintptr_t ra, stg_store1 store1)
417
{
418
int mmu_idx = arm_env_mmu_index(env);
419
int tag = allocation_tag_from_addr(xt);
@@ -420,9 +436,13 @@ static inline void do_st2g(CPUARMState *env, uint64_t ptr, uint64_t xt,
436
/* Store if page(s) support tags. */
437
if (mem1) {
438
store1(TAG_GRANULE, mem1, tag);
439
+ } else if (mtx) {
440
+ canonical_tag_write_fail(env, ptr, ra);
441
}
442
if (mem2) {
443
store1(0, mem2, tag);
444
+ } else if (mtx) {
445
+ canonical_tag_write_fail(env, ptr + TAG_GRANULE, ra);
446
}
447
} else {
448
/* Two stores aligned mod TAG_GRANULE*2 -- modify one byte. */
@@ -431,18 +451,22 @@ static inline void do_st2g(CPUARMState *env, uint64_t ptr, uint64_t xt,
451
if (mem1) {
452
tag |= tag << 4;
453
qatomic_set(mem1, tag);
454
+ } else if (mtx) {
455
+ /* Writing tags to canonically tagged memory region: faults */
456
+ canonical_tag_write_fail(env, ptr, ra);
457
}
458
}
459
}
460
438
-void HELPER(st2g)(CPUARMState *env, uint64_t ptr, uint64_t xt)
461
+void HELPER(st2g)(CPUARMState *env, uint64_t ptr, uint64_t xt, uint32_t mtx)
462
{
440
- do_st2g(env, ptr, xt, GETPC(), store_tag1);
463
+ do_st2g(env, ptr, xt, mtx, GETPC(), store_tag1);
464
}
465
443
-void HELPER(st2g_parallel)(CPUARMState *env, uint64_t ptr, uint64_t xt)
466
+void HELPER(st2g_parallel)(CPUARMState *env, uint64_t ptr, uint64_t xt,
467
+ uint32_t mtx)
468
{
445
- do_st2g(env, ptr, xt, GETPC(), store_tag1_parallel);
469
+ do_st2g(env, ptr, xt, mtx, GETPC(), store_tag1_parallel);
470
}
471
472
void HELPER(st2g_stub)(CPUARMState *env, uint64_t ptr)
@@ -528,7 +552,7 @@ uint64_t HELPER(ldgm)(CPUARMState *env, uint64_t ptr, uint32_t mtx)
552
return ret << shift;
553
}
554
531
-void HELPER(stgm)(CPUARMState *env, uint64_t ptr, uint64_t val)
555
+void HELPER(stgm)(CPUARMState *env, uint64_t ptr, uint64_t val, uint32_t mtx)
556
{
557
int mmu_idx = arm_env_mmu_index(env);
558
uintptr_t ra = GETPC();
@@ -548,6 +572,10 @@ void HELPER(stgm)(CPUARMState *env, uint64_t ptr, uint64_t val)
572
* and if the OS has enabled access to the tags.
573
*/
574
if (!tag_mem) {
575
+ /* Storing tags to canonically tagged region: fault. */
576
+ if (mtx) {
577
+ canonical_tag_write_fail(env, ptr, ra);
578
+ }
579
return;
580
}
581
@@ -577,7 +605,8 @@ void HELPER(stgm)(CPUARMState *env, uint64_t ptr, uint64_t val)
605
}
606
}
607
580
-void HELPER(stzgm_tags)(CPUARMState *env, uint64_t ptr, uint64_t val)
608
+void HELPER(stzgm_tags)(CPUARMState *env, uint64_t ptr, uint64_t val,
609
+ uint32_t mtx)
610
{
611
uintptr_t ra = GETPC();
612
int mmu_idx = arm_env_mmu_index(env);
@@ -601,6 +630,8 @@ void HELPER(stzgm_tags)(CPUARMState *env, uint64_t ptr, uint64_t val)
630
if (mem) {
631
int tag_pair = (val & 0xf) * 0x11;
632
memset(mem, tag_pair, tag_bytes);
633
+ } else if (mtx) {
634
+ canonical_tag_write_fail(env, ptr, ra);
635
}
636
}
637
target/arm/tcg/translate-a64.c
+16
-10
@@ -3121,7 +3121,8 @@ static void handle_sys(DisasContext *s, bool isread,
3121
/* Extract the tag from the register to match STZGM. */
3122
tag = tcg_temp_new_i64();
3123
tcg_gen_shri_i64(tag, tcg_rt, 56);
3124
- gen_helper_stzgm_tags(tcg_env, clean_addr, tag);
3124
+ gen_helper_stzgm_tags(tcg_env, clean_addr, tag,
3125
+ tcg_constant_i32(s->mtx));
3126
}
3127
}
3128
return;
@@ -3138,7 +3139,8 @@ static void handle_sys(DisasContext *s, bool isread,
3139
/* Extract the tag from the register to match STZGM. */
3140
tag = tcg_temp_new_i64();
3141
tcg_gen_shri_i64(tag, tcg_rt, 56);
3141
- gen_helper_stzgm_tags(tcg_env, clean_addr, tag);
3142
+ gen_helper_stzgm_tags(tcg_env, clean_addr, tag,
3143
+ tcg_constant_i32(s->mtx));
3144
}
3145
}
3146
return;
@@ -3955,9 +3957,11 @@ static bool trans_STGP(DisasContext *s, arg_ldstpair *a)
3957
/* Perform the tag store, if tag access enabled. */
3958
if (s->ata[0]) {
3959
if (tb_cflags(s->base.tb) & CF_PARALLEL) {
3958
- gen_helper_stg_parallel(tcg_env, dirty_addr, dirty_addr);
3960
+ gen_helper_stg_parallel(tcg_env, dirty_addr, dirty_addr,
3961
+ tcg_constant_i32(s->mtx));
3962
} else {
3960
- gen_helper_stg(tcg_env, dirty_addr, dirty_addr);
3963
+ gen_helper_stg(tcg_env, dirty_addr, dirty_addr,
3964
+ tcg_constant_i32(s->mtx));
3965
}
3966
}
3967
@@ -4737,7 +4741,7 @@ static bool trans_STZGM(DisasContext *s, arg_ldst_tag *a)
4741
tcg_rt = cpu_reg(s, a->rt);
4742
4743
if (s->ata[0]) {
4740
- gen_helper_stzgm_tags(tcg_env, addr, tcg_rt);
4744
+ gen_helper_stzgm_tags(tcg_env, addr, tcg_rt, tcg_constant_i32(s->mtx));
4745
}
4746
/*
4747
* The non-tags portion of STZGM is mostly like DC_ZVA,
@@ -4769,7 +4773,7 @@ static bool trans_STGM(DisasContext *s, arg_ldst_tag *a)
4773
tcg_rt = cpu_reg(s, a->rt);
4774
4775
if (s->ata[0]) {
4772
- gen_helper_stgm(tcg_env, addr, tcg_rt);
4776
+ gen_helper_stgm(tcg_env, addr, tcg_rt, tcg_constant_i32(s->mtx));
4777
} else {
4778
MMUAccessType acc = MMU_DATA_STORE;
4779
int size = 4 << s->gm_blocksize;
@@ -4885,15 +4889,17 @@ static bool do_STG(DisasContext *s, arg_ldst_tag *a, bool is_zero, bool is_pair)
4889
}
4890
} else if (tb_cflags(s->base.tb) & CF_PARALLEL) {
4891
if (is_pair) {
4888
- gen_helper_st2g_parallel(tcg_env, addr, tcg_rt);
4892
+ gen_helper_st2g_parallel(tcg_env, addr, tcg_rt,
4893
+ tcg_constant_i32(s->mtx));
4894
} else {
4890
- gen_helper_stg_parallel(tcg_env, addr, tcg_rt);
4895
+ gen_helper_stg_parallel(tcg_env, addr, tcg_rt,
4896
+ tcg_constant_i32(s->mtx));
4897
}
4898
} else {
4899
if (is_pair) {
4894
- gen_helper_st2g(tcg_env, addr, tcg_rt);
4900
+ gen_helper_st2g(tcg_env, addr, tcg_rt, tcg_constant_i32(s->mtx));
4901
} else {
4896
- gen_helper_stg(tcg_env, addr, tcg_rt);
4902
+ gen_helper_stg(tcg_env, addr, tcg_rt, tcg_constant_i32(s->mtx));
4903
}
4904
}
4905