target-arm: Avoid buffer overrun on UNPREDICTABLE ldrd/strd

A LDRD or STRD where rd is not an even number is UNPREDICTABLE.
We were letting this fall through, which is OK unless rd is 15,
in which case we would attempt to do a load_reg or store_reg
to a nonexistent r16 for the second half of the double-word.
Catch the odd-numbered-rd cases and UNDEF them instead.

To do this we rearrange the structure of the code a little
so we can put the UNDEF catches at the top before we've
allocated TCG temporaries.

Backports commit a4bb522ee51087af61998f290d12ba2e14c7910e from qemu
This commit is contained in:
Peter Maydell 2018-02-12 16:45:48 -05:00 committed by Lioncash
parent df41e9ffd3
commit db9901f2ad
No known key found for this signature in database
GPG key ID: 4E3C3CC1031BA9C7

View file

@ -8594,18 +8594,53 @@ static void disas_arm_insn(DisasContext *s, unsigned int insn) // qq
}
} else {
int address_offset;
int load;
bool load = insn & (1 << 20);
bool doubleword = false;
/* Misc load/store */
rn = (insn >> 16) & 0xf;
rd = (insn >> 12) & 0xf;
if (!load && (sh & 2)) {
/* doubleword */
ARCH(5TE);
if (rd & 1) {
/* UNPREDICTABLE; we choose to UNDEF */
goto illegal_op;
}
load = (sh & 1) == 0;
doubleword = true;
}
addr = load_reg(s, rn);
if (insn & (1 << 24))
gen_add_datah_offset(s, insn, 0, addr);
address_offset = 0;
if (insn & (1 << 20)) {
if (doubleword) {
if (!load) {
/* store */
tmp = load_reg(s, rd);
gen_aa32_st32(s, tmp, addr, get_mem_index(s));
tcg_temp_free_i32(tcg_ctx, tmp);
tcg_gen_addi_i32(tcg_ctx, addr, addr, 4);
tmp = load_reg(s, rd + 1);
gen_aa32_st32(s, tmp, addr, get_mem_index(s));
tcg_temp_free_i32(tcg_ctx, tmp);
} else {
/* load */
tmp = tcg_temp_new_i32(tcg_ctx);
gen_aa32_ld32u(s, tmp, addr, get_mem_index(s));
store_reg(s, rd, tmp);
tcg_gen_addi_i32(tcg_ctx, addr, addr, 4);
tmp = tcg_temp_new_i32(tcg_ctx);
gen_aa32_ld32u(s, tmp, addr, get_mem_index(s));
rd++;
}
address_offset = -4;
} else if (load) {
/* load */
tmp = tcg_temp_new_i32(tcg_ctx);
switch(sh) {
switch (sh) {
case 1:
gen_aa32_ld16u(s, tmp, addr, get_mem_index(s));
break;
@ -8617,38 +8652,11 @@ static void disas_arm_insn(DisasContext *s, unsigned int insn) // qq
gen_aa32_ld16s(s, tmp, addr, get_mem_index(s));
break;
}
load = 1;
} else if (sh & 2) {
ARCH(5TE);
/* doubleword */
if (sh & 1) {
/* store */
tmp = load_reg(s, rd);
gen_aa32_st32(s, tmp, addr, get_mem_index(s));
tcg_temp_free_i32(tcg_ctx, tmp);
tcg_gen_addi_i32(tcg_ctx, addr, addr, 4);
tmp = load_reg(s, rd + 1);
gen_aa32_st32(s, tmp, addr, get_mem_index(s));
tcg_temp_free_i32(tcg_ctx, tmp);
load = 0;
} else {
/* load */
tmp = tcg_temp_new_i32(tcg_ctx);
gen_aa32_ld32u(s, tmp, addr, get_mem_index(s));
store_reg(s, rd, tmp);
tcg_gen_addi_i32(tcg_ctx, addr, addr, 4);
tmp = tcg_temp_new_i32(tcg_ctx);
gen_aa32_ld32u(s, tmp, addr, get_mem_index(s));
rd++;
load = 1;
}
address_offset = -4;
} else {
/* store */
tmp = load_reg(s, rd);
gen_aa32_st16(s, tmp, addr, get_mem_index(s));
tcg_temp_free_i32(tcg_ctx, tmp);
load = 0;
}
/* Perform base writeback before the loaded value to
ensure correct behavior with overlapping index registers.