When we remove dest from orig's links, we lose the link that we rely on later to reset links. This can lead to failure to release from spinlock with self-modifying code.
Reported-by: 李威威 <[email protected]> Signed-off-by: Richard Henderson <[email protected]> --- accel/tcg/tb-maint.c | 8 +++++ tests/tcg/riscv64/tb-link.c | 60 +++++++++++++++++++++++++++++++ tests/tcg/riscv64/Makefile.target | 1 + 3 files changed, 69 insertions(+) create mode 100644 tests/tcg/riscv64/tb-link.c diff --git a/accel/tcg/tb-maint.c b/accel/tcg/tb-maint.c index 0048316f99..e6d45c9c12 100644 --- a/accel/tcg/tb-maint.c +++ b/accel/tcg/tb-maint.c @@ -836,6 +836,14 @@ static inline void tb_remove_from_jmp_list(TranslationBlock *orig, int n_orig) * We first acquired the lock, and since the destination pointer matches, * we know for sure that @orig is in the jmp list. */ + if (dest == orig) { + /* + * In the case of a TB that links to itself, removing the entry + * from the list means that it won't be present later during + * tb_jmp_unlink -- unlink now. + */ + tb_reset_jump(orig, n_orig); + } pprev = &dest->jmp_list_head; TB_FOR_EACH_JMP(dest, tb, n) { if (tb == orig && n == n_orig) { diff --git a/tests/tcg/riscv64/tb-link.c b/tests/tcg/riscv64/tb-link.c new file mode 100644 index 0000000000..b6fcca8668 --- /dev/null +++ b/tests/tcg/riscv64/tb-link.c @@ -0,0 +1,60 @@ +#include <assert.h> +#include <string.h> +#include <sys/mman.h> +#include <pthread.h> +#include <stdint.h> +#include <unistd.h> + + +int main() +{ + /* + * ## 1. RISC-V machine code. + * Assembly: + * L: j L ; Jump to self (spin). + * li a0, 42 ; Place 42 into the return value register a0. + * ret ; Return to caller. + */ + static const uint32_t machine_code[] = { + 0x0000006f, /* jal zero, #0 */ + 0x02a00513, /* addi a0, zero, 42 */ + 0x00008067 /* jalr zero, ra, 0 */ + }; + size_t code_size = sizeof(machine_code); + int tmp; + pthread_t thread_id; + void *thread_return_value; + uint32_t *buffer; + + /* ## 2. Allocate executable memory. */ + buffer = mmap( + NULL, + code_size, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0 + ); + assert(buffer != MAP_FAILED); + + /* ## 3. Copy machine code into buffer. */ + memcpy(buffer, machine_code, code_size); + + /* ## 4. Execute the code in a separate thread. */ + tmp = pthread_create(&thread_id, NULL, (void *(*)(void *))buffer, NULL); + assert(tmp == 0); + + /* + * Wait a second and then try to patch the generated code to get the + * runner thread to get unstuck by patching the spin jump. + */ + sleep(1); + buffer[0] = 0x00000013; /* nop */ + __builtin___clear_cache((char *)buffer, (char *)(buffer + 1)); + + tmp = pthread_join(thread_id, &thread_return_value); + assert(tmp == 0); + + tmp = (intptr_t)thread_return_value; + assert(tmp == 42); + return 0; +} diff --git a/tests/tcg/riscv64/Makefile.target b/tests/tcg/riscv64/Makefile.target index 4da5b9a3b3..ba684616fd 100644 --- a/tests/tcg/riscv64/Makefile.target +++ b/tests/tcg/riscv64/Makefile.target @@ -4,6 +4,7 @@ VPATH += $(SRC_PATH)/tests/tcg/riscv64 TESTS += test-div TESTS += noexec +TESTS += tb-link # Disable compressed instructions for test-noc TESTS += test-noc -- 2.43.0
