plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks

This patch adds functionality to enforce the requested QEMU_PLUGIN_CB_
flags level passed when registering a callback function using the
plugins API. Each time a callback is about to be invoked, a thread-local
variable will be updated with the level that callback requested. Then,
called API functions (in particular, the register read and write API)
will call qemu_plugin_get_cb_flags() to check the level is at least the
level they require.

Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Rowan Hart <rowanbhart@gmail.com>
Message-ID: <20250624175351.440780-4-rowanbhart@gmail.com>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Message-ID: <20250627112512.1880708-8-alex.bennee@linaro.org>
This commit is contained in:
Rowan Hart 2025-06-27 12:25:03 +01:00 committed by Alex Bennée
parent 1a92b65859
commit 766e00bd57
6 changed files with 96 additions and 6 deletions

View file

@ -117,10 +117,20 @@ static TCGv_i32 gen_cpu_index(void)
static void gen_udata_cb(struct qemu_plugin_regular_cb *cb) static void gen_udata_cb(struct qemu_plugin_regular_cb *cb)
{ {
TCGv_i32 cpu_index = gen_cpu_index(); TCGv_i32 cpu_index = gen_cpu_index();
enum qemu_plugin_cb_flags cb_flags =
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
TCGv_i32 flags = tcg_constant_i32(cb_flags);
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
tcg_gen_st_i32(flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
tcgv_i32_temp(cpu_index), tcgv_i32_temp(cpu_index),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
tcg_gen_st_i32(clear_flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index); tcg_temp_free_i32(cpu_index);
tcg_temp_free_i32(flags);
tcg_temp_free_i32(clear_flags);
} }
static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry) static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry)
@ -173,10 +183,20 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb)
tcg_gen_ld_i64(val, ptr, 0); tcg_gen_ld_i64(val, ptr, 0);
tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb); tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb);
TCGv_i32 cpu_index = gen_cpu_index(); TCGv_i32 cpu_index = gen_cpu_index();
enum qemu_plugin_cb_flags cb_flags =
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
TCGv_i32 flags = tcg_constant_i32(cb_flags);
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
tcg_gen_st_i32(flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL, tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
tcgv_i32_temp(cpu_index), tcgv_i32_temp(cpu_index),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
tcg_gen_st_i32(clear_flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index); tcg_temp_free_i32(cpu_index);
tcg_temp_free_i32(flags);
tcg_temp_free_i32(clear_flags);
gen_set_label(after_cb); gen_set_label(after_cb);
tcg_temp_free_i64(val); tcg_temp_free_i64(val);
@ -210,12 +230,22 @@ static void gen_mem_cb(struct qemu_plugin_regular_cb *cb,
qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) qemu_plugin_meminfo_t meminfo, TCGv_i64 addr)
{ {
TCGv_i32 cpu_index = gen_cpu_index(); TCGv_i32 cpu_index = gen_cpu_index();
enum qemu_plugin_cb_flags cb_flags =
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
TCGv_i32 flags = tcg_constant_i32(cb_flags);
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
tcg_gen_st_i32(flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL, tcg_gen_call4(cb->f.vcpu_mem, cb->info, NULL,
tcgv_i32_temp(cpu_index), tcgv_i32_temp(cpu_index),
tcgv_i32_temp(tcg_constant_i32(meminfo)), tcgv_i32_temp(tcg_constant_i32(meminfo)),
tcgv_i64_temp(addr), tcgv_i64_temp(addr),
tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
tcg_gen_st_i32(clear_flags, tcg_env,
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
tcg_temp_free_i32(cpu_index); tcg_temp_free_i32(cpu_index);
tcg_temp_free_i32(flags);
tcg_temp_free_i32(clear_flags);
} }
static void inject_cb(struct qemu_plugin_dyn_cb *cb) static void inject_cb(struct qemu_plugin_dyn_cb *cb)

View file

@ -368,6 +368,7 @@ typedef struct CPUNegativeOffsetState {
GArray *plugin_mem_cbs; GArray *plugin_mem_cbs;
uint64_t plugin_mem_value_low; uint64_t plugin_mem_value_low;
uint64_t plugin_mem_value_high; uint64_t plugin_mem_value_high;
int32_t plugin_cb_flags;
#endif #endif
IcountDecr icount_decr; IcountDecr icount_decr;
bool can_do_io; bool can_do_io;

View file

@ -209,6 +209,21 @@ void qemu_plugin_user_prefork_lock(void);
*/ */
void qemu_plugin_user_postfork(bool is_child); void qemu_plugin_user_postfork(bool is_child);
enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags);
static inline void qemu_plugin_set_cb_flags(CPUState *cpu,
enum qemu_plugin_cb_flags flags)
{
assert(cpu);
cpu->neg.plugin_cb_flags = flags;
}
static inline enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void)
{
assert(current_cpu);
return current_cpu->neg.plugin_cb_flags;
}
#else /* !CONFIG_PLUGIN */ #else /* !CONFIG_PLUGIN */
static inline void qemu_plugin_add_opts(void) static inline void qemu_plugin_add_opts(void)

View file

@ -254,9 +254,6 @@ typedef struct {
* @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs * @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs
* @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs * @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs
* @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs * @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs
*
* Note: currently QEMU_PLUGIN_CB_RW_REGS is unused, plugins cannot change
* system register state.
*/ */
enum qemu_plugin_cb_flags { enum qemu_plugin_cb_flags {
QEMU_PLUGIN_CB_NO_REGS, QEMU_PLUGIN_CB_NO_REGS,
@ -901,7 +898,12 @@ GArray *qemu_plugin_get_registers(void);
* @buf: A GByteArray for the data owned by the plugin * @buf: A GByteArray for the data owned by the plugin
* *
* This function is only available in a context that register read access is * This function is only available in a context that register read access is
* explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag. * explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag, if called inside a
* callback that can be registered with a qemu_plugin_cb_flags argument. This
* function can also be used in any callback context that does not use a flags
* argument, such as in a callback registered with
* qemu_plugin_register_vcpu_init_cb(), except for callbacks registered with
* qemu_plugin_register_atexit_cb() and qemu_plugin_register_flush_cb().
* *
* Returns the size of the read register. The content of @buf is in target byte * Returns the size of the read register. The content of @buf is in target byte
* order. On failure returns -1. * order. On failure returns -1.
@ -916,8 +918,13 @@ int qemu_plugin_read_register(struct qemu_plugin_register *handle,
* @handle: a @qemu_plugin_reg_handle handle * @handle: a @qemu_plugin_reg_handle handle
* @buf: A GByteArray for the data owned by the plugin * @buf: A GByteArray for the data owned by the plugin
* *
* This function is only available in a context that register write access is * This function is only available in a context that register read access is
* explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag. * explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag, if called inside a
* callback that can be registered with a qemu_plugin_cb_flags argument. This
* function can also be used in any callback context that does not use a flags
* argument, such as in a callback registered with
* qemu_plugin_register_vcpu_init_cb(), except for callbacks registered with
* qemu_plugin_register_atexit_cb() and qemu_plugin_register_flush_cb().
* *
* The size of @buf must be at least the size of the requested register. * The size of @buf must be at least the size of the requested register.
* Attempting to write a register with @buf smaller than the register size * Attempting to write a register with @buf smaller than the register size

View file

@ -437,6 +437,10 @@ int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf)
{ {
g_assert(current_cpu); g_assert(current_cpu);
if (qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_NO_REGS) {
return -1;
}
return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1); return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1);
} }

View file

@ -15,6 +15,7 @@
#include "qemu/lockable.h" #include "qemu/lockable.h"
#include "qemu/option.h" #include "qemu/option.h"
#include "qemu/plugin.h" #include "qemu/plugin.h"
#include "qemu/qemu-plugin.h"
#include "qemu/queue.h" #include "qemu/queue.h"
#include "qemu/rcu_queue.h" #include "qemu/rcu_queue.h"
#include "qemu/rcu.h" #include "qemu/rcu.h"
@ -266,7 +267,9 @@ static void qemu_plugin_vcpu_init__async(CPUState *cpu, run_on_cpu_data unused)
plugin_grow_scoreboards__locked(cpu); plugin_grow_scoreboards__locked(cpu);
qemu_rec_mutex_unlock(&plugin.lock); qemu_rec_mutex_unlock(&plugin.lock);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_INIT); plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_INIT);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
void qemu_plugin_vcpu_init_hook(CPUState *cpu) void qemu_plugin_vcpu_init_hook(CPUState *cpu)
@ -279,7 +282,9 @@ void qemu_plugin_vcpu_exit_hook(CPUState *cpu)
{ {
bool success; bool success;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_EXIT); plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_EXIT);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
assert(cpu->cpu_index != UNASSIGNED_CPU_INDEX); assert(cpu->cpu_index != UNASSIGNED_CPU_INDEX);
qemu_rec_mutex_lock(&plugin.lock); qemu_rec_mutex_lock(&plugin.lock);
@ -367,6 +372,7 @@ void plugin_register_dyn_cb__udata(GArray **arr,
static TCGHelperInfo info[3] = { static TCGHelperInfo info[3] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
/* /*
* Match qemu_plugin_vcpu_udata_cb_t: * Match qemu_plugin_vcpu_udata_cb_t:
* void (*)(uint32_t, void *) * void (*)(uint32_t, void *)
@ -396,6 +402,7 @@ void plugin_register_dyn_cond_cb__udata(GArray **arr,
static TCGHelperInfo info[3] = { static TCGHelperInfo info[3] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
/* /*
* Match qemu_plugin_vcpu_udata_cb_t: * Match qemu_plugin_vcpu_udata_cb_t:
* void (*)(uint32_t, void *) * void (*)(uint32_t, void *)
@ -434,6 +441,7 @@ void plugin_register_vcpu_mem_cb(GArray **arr,
static TCGHelperInfo info[3] = { static TCGHelperInfo info[3] = {
[QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG,
[QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG,
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
/* /*
* Match qemu_plugin_vcpu_mem_cb_t: * Match qemu_plugin_vcpu_mem_cb_t:
* void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *) * void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
@ -473,7 +481,9 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_tb_trans_cb_t func = cb->f.vcpu_tb_trans; qemu_plugin_vcpu_tb_trans_cb_t func = cb->f.vcpu_tb_trans;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
func(cb->ctx->id, tb); func(cb->ctx->id, tb);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
} }
@ -498,7 +508,9 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall; qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8); func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
} }
@ -520,7 +532,9 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret; qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret;
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
func(cb->ctx->id, cpu->cpu_index, num, ret); func(cb->ctx->id, cpu->cpu_index, num, ret);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
} }
@ -528,14 +542,18 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
{ {
/* idle and resume cb may be called before init, ignore in this case */ /* idle and resume cb may be called before init, ignore in this case */
if (cpu->cpu_index < plugin.num_vcpus) { if (cpu->cpu_index < plugin.num_vcpus) {
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE); plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
} }
void qemu_plugin_vcpu_resume_cb(CPUState *cpu) void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
{ {
if (cpu->cpu_index < plugin.num_vcpus) { if (cpu->cpu_index < plugin.num_vcpus) {
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME); plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
} }
@ -615,9 +633,13 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
switch (cb->type) { switch (cb->type) {
case PLUGIN_CB_MEM_REGULAR: case PLUGIN_CB_MEM_REGULAR:
if (rw & cb->regular.rw) { if (rw & cb->regular.rw) {
qemu_plugin_set_cb_flags(cpu,
tcg_call_to_qemu_plugin_cb_flags(cb->regular.info->flags));
cb->regular.f.vcpu_mem(cpu->cpu_index, cb->regular.f.vcpu_mem(cpu->cpu_index,
make_plugin_meminfo(oi, rw), make_plugin_meminfo(oi, rw),
vaddr, cb->regular.userp); vaddr, cb->regular.userp);
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
} }
break; break;
case PLUGIN_CB_INLINE_ADD_U64: case PLUGIN_CB_INLINE_ADD_U64:
@ -760,3 +782,14 @@ void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score)
g_array_free(score->data, TRUE); g_array_free(score->data, TRUE);
g_free(score); g_free(score);
} }
enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags)
{
if (flags & TCG_CALL_NO_RWG) {
return QEMU_PLUGIN_CB_NO_REGS;
} else if (flags & TCG_CALL_NO_WG) {
return QEMU_PLUGIN_CB_R_REGS;
} else {
return QEMU_PLUGIN_CB_RW_REGS;
}
}