This commit adds a syscall filter API to the TCG plugin API set.
Plugins can register a filter callback to QEMU to decide whether
to intercept a syscall, process it and bypass the QEMU syscall
handler.
Signed-off-by: Ziyang Zhang <functioner@sjtu.edu.cn>
Co-authored-by: Mingyuan Xia <xiamy@ultrarisc.com>
---
include/qemu/plugin-event.h | 1 +
include/qemu/plugin.h | 29 +++++++++++++++++++++--------
include/qemu/qemu-plugin.h | 24 ++++++++++++++++++++++++
include/user/syscall-trace.h | 17 +++++++++++++++++
linux-user/syscall.c | 7 +++++--
plugins/api.c | 7 +++++++
plugins/core.c | 36 ++++++++++++++++++++++++++++++++++++
7 files changed, 111 insertions(+), 10 deletions(-)
diff --git a/include/qemu/plugin-event.h b/include/qemu/plugin-event.h
index 7056d8427b..bbb1c2b91f 100644
--- a/include/qemu/plugin-event.h
+++ b/include/qemu/plugin-event.h
@@ -20,6 +20,7 @@ enum qemu_plugin_event {
QEMU_PLUGIN_EV_VCPU_SYSCALL_RET,
QEMU_PLUGIN_EV_FLUSH,
QEMU_PLUGIN_EV_ATEXIT,
+ QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER,
QEMU_PLUGIN_EV_MAX, /* total number of plugin events we support */
};
diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h
index f355c7cb8a..9f90a233e7 100644
--- a/include/qemu/plugin.h
+++ b/include/qemu/plugin.h
@@ -55,14 +55,15 @@ void qemu_plugin_opt_parse(const char *optstr, QemuPluginList *head);
int qemu_plugin_load_list(QemuPluginList *head, Error **errp);
union qemu_plugin_cb_sig {
- qemu_plugin_simple_cb_t simple;
- qemu_plugin_udata_cb_t udata;
- qemu_plugin_vcpu_simple_cb_t vcpu_simple;
- qemu_plugin_vcpu_udata_cb_t vcpu_udata;
- qemu_plugin_vcpu_tb_trans_cb_t vcpu_tb_trans;
- qemu_plugin_vcpu_mem_cb_t vcpu_mem;
- qemu_plugin_vcpu_syscall_cb_t vcpu_syscall;
- qemu_plugin_vcpu_syscall_ret_cb_t vcpu_syscall_ret;
+ qemu_plugin_simple_cb_t simple;
+ qemu_plugin_udata_cb_t udata;
+ qemu_plugin_vcpu_simple_cb_t vcpu_simple;
+ qemu_plugin_vcpu_udata_cb_t vcpu_udata;
+ qemu_plugin_vcpu_tb_trans_cb_t vcpu_tb_trans;
+ qemu_plugin_vcpu_mem_cb_t vcpu_mem;
+ qemu_plugin_vcpu_syscall_cb_t vcpu_syscall;
+ qemu_plugin_vcpu_syscall_ret_cb_t vcpu_syscall_ret;
+ qemu_plugin_vcpu_syscall_filter_cb_t vcpu_syscall_filter;
void *generic;
};
@@ -165,6 +166,11 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1,
uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5,
uint64_t a6, uint64_t a7, uint64_t a8);
void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret);
+bool
+qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
+ uint64_t a2, uint64_t a3, uint64_t a4,
+ uint64_t a5, uint64_t a6, uint64_t a7,
+ uint64_t a8, uint64_t *ret);
void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
uint64_t value_low,
@@ -267,6 +273,13 @@ static inline
void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
{ }
+static inline bool
+qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
+ uint64_t a2, uint64_t a3, uint64_t a4,
+ uint64_t a5, uint64_t a6, uint64_t a7,
+ uint64_t a8, uint64_t *ret)
+{ }
+
static inline void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
uint64_t value_low,
uint64_t value_high,
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
index c450106af1..cdd0b2c4da 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -738,6 +738,25 @@ typedef void
uint64_t a3, uint64_t a4, uint64_t a5,
uint64_t a6, uint64_t a7, uint64_t a8);
+/*
+ * typedef qemu_plugin_vcpu_syscall_filter_cb_t - vCPU syscall filter callback
+ * function type
+ * @vcpu_index: the executing vCPU
+ * @num: the syscall number
+ * @a1-a8: the syscall arguments
+ * @ret: the address of the syscall return value, set this if filtered
+ *
+ * Returns: true if you want to filter this syscall (i.e. stop it being
+ * handled further), otherwise return false.
+ */
+typedef bool
+(*qemu_plugin_vcpu_syscall_filter_cb_t)(qemu_plugin_id_t id,
+ unsigned int vcpu_index,
+ int64_t num, uint64_t a1, uint64_t a2,
+ uint64_t a3, uint64_t a4, uint64_t a5,
+ uint64_t a6, uint64_t a7, uint64_t a8,
+ uint64_t *ret);
+
QEMU_PLUGIN_API
void qemu_plugin_register_vcpu_syscall_cb(qemu_plugin_id_t id,
qemu_plugin_vcpu_syscall_cb_t cb);
@@ -751,6 +770,11 @@ void
qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id,
qemu_plugin_vcpu_syscall_ret_cb_t cb);
+QEMU_PLUGIN_API
+void
+qemu_plugin_register_vcpu_syscall_filter_cb(qemu_plugin_id_t id,
+ qemu_plugin_vcpu_syscall_filter_cb_t cb);
+
/**
* qemu_plugin_insn_disas() - return disassembly string for instruction
diff --git a/include/user/syscall-trace.h b/include/user/syscall-trace.h
index 9bd7ca19c8..61cdbd7583 100644
--- a/include/user/syscall-trace.h
+++ b/include/user/syscall-trace.h
@@ -39,5 +39,22 @@ static inline void record_syscall_return(CPUState *cpu, int num, abi_long ret)
gdb_syscall_return(cpu, num);
}
+static bool send_through_syscall_filters(CPUState *cpu, int num,
+ abi_long arg1, abi_long arg2,
+ abi_long arg3, abi_long arg4,
+ abi_long arg5, abi_long arg6,
+ abi_long arg7, abi_long arg8,
+ abi_long *ret)
+{
+ uint64_t sysret64 = 0;
+ bool filtered = qemu_plugin_vcpu_syscall_filter(cpu, num, arg1, arg2,
+ arg3, arg4, arg5, arg6,
+ arg7, arg8, &sysret64);
+ if (filtered) {
+ *ret = sysret64;
+ }
+ return filtered;
+}
+
#endif /* SYSCALL_TRACE_H */
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index d78b2029fa..1fb99dcf0c 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -14084,8 +14084,11 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
print_syscall(cpu_env, num, arg1, arg2, arg3, arg4, arg5, arg6);
}
- ret = do_syscall1(cpu_env, num, arg1, arg2, arg3, arg4,
- arg5, arg6, arg7, arg8);
+ if (!send_through_syscall_filters(cpu, num, arg1, arg2, arg3, arg4, arg5,
+ arg6, arg7, arg8, &ret)) {
+ ret = do_syscall1(cpu_env, num, arg1, arg2, arg3, arg4,
+ arg5, arg6, arg7, arg8);
+ }
if (unlikely(qemu_loglevel_mask(LOG_STRACE))) {
print_syscall_ret(cpu_env, num, ret, arg1, arg2,
diff --git a/plugins/api.c b/plugins/api.c
index eac04cc1f6..478d0c8889 100644
--- a/plugins/api.c
+++ b/plugins/api.c
@@ -208,6 +208,13 @@ qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id,
plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_RET, cb);
}
+void
+qemu_plugin_register_vcpu_syscall_filter_cb(qemu_plugin_id_t id,
+ qemu_plugin_vcpu_syscall_filter_cb_t cb)
+{
+ plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER, cb);
+}
+
/*
* Plugin Queries
*
diff --git a/plugins/core.c b/plugins/core.c
index ead09fd2f1..8cd773cbca 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -538,6 +538,42 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
}
}
+/*
+ * Disable CFI checks.
+ * The callback function has been loaded from an external library so we do not
+ * have type information
+ */
+QEMU_DISABLE_CFI
+bool
+qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
+ uint64_t a2, uint64_t a3, uint64_t a4,
+ uint64_t a5, uint64_t a6, uint64_t a7,
+ uint64_t a8, uint64_t *ret)
+{
+ struct qemu_plugin_cb *cb, *next;
+ enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER;
+
+ if (!test_bit(ev, cpu->plugin_state->event_mask)) {
+ return false;
+ }
+
+ bool filtered = false;
+ QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
+ qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter;
+
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+ if (func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4,
+ a5, a6, a7, a8, ret)) {
+ filtered = true;
+ }
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
+
+ if (filtered)
+ break;
+ }
+ return filtered;
+}
+
void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
{
/* idle and resume cb may be called before init, ignore in this case */
--
2.34.1
Ziyang Zhang <functioner@sjtu.edu.cn> writes:
> This commit adds a syscall filter API to the TCG plugin API set.
> Plugins can register a filter callback to QEMU to decide whether
> to intercept a syscall, process it and bypass the QEMU syscall
> handler.
>
> Signed-off-by: Ziyang Zhang <functioner@sjtu.edu.cn>
> Co-authored-by: Mingyuan Xia <xiamy@ultrarisc.com>
> ---
> include/qemu/plugin-event.h | 1 +
> include/qemu/plugin.h | 29 +++++++++++++++++++++--------
> include/qemu/qemu-plugin.h | 24 ++++++++++++++++++++++++
> include/user/syscall-trace.h | 17 +++++++++++++++++
> linux-user/syscall.c | 7 +++++--
> plugins/api.c | 7 +++++++
> plugins/core.c | 36 ++++++++++++++++++++++++++++++++++++
> 7 files changed, 111 insertions(+), 10 deletions(-)
>
> diff --git a/include/qemu/plugin-event.h b/include/qemu/plugin-event.h
> index 7056d8427b..bbb1c2b91f 100644
> --- a/include/qemu/plugin-event.h
> +++ b/include/qemu/plugin-event.h
> @@ -20,6 +20,7 @@ enum qemu_plugin_event {
> QEMU_PLUGIN_EV_VCPU_SYSCALL_RET,
> QEMU_PLUGIN_EV_FLUSH,
> QEMU_PLUGIN_EV_ATEXIT,
> + QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER,
> QEMU_PLUGIN_EV_MAX, /* total number of plugin events we support */
> };
>
> diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h
> index f355c7cb8a..9f90a233e7 100644
> --- a/include/qemu/plugin.h
> +++ b/include/qemu/plugin.h
> @@ -55,14 +55,15 @@ void qemu_plugin_opt_parse(const char *optstr, QemuPluginList *head);
> int qemu_plugin_load_list(QemuPluginList *head, Error **errp);
>
> union qemu_plugin_cb_sig {
> - qemu_plugin_simple_cb_t simple;
> - qemu_plugin_udata_cb_t udata;
> - qemu_plugin_vcpu_simple_cb_t vcpu_simple;
> - qemu_plugin_vcpu_udata_cb_t vcpu_udata;
> - qemu_plugin_vcpu_tb_trans_cb_t vcpu_tb_trans;
> - qemu_plugin_vcpu_mem_cb_t vcpu_mem;
> - qemu_plugin_vcpu_syscall_cb_t vcpu_syscall;
> - qemu_plugin_vcpu_syscall_ret_cb_t vcpu_syscall_ret;
> + qemu_plugin_simple_cb_t simple;
> + qemu_plugin_udata_cb_t udata;
> + qemu_plugin_vcpu_simple_cb_t vcpu_simple;
> + qemu_plugin_vcpu_udata_cb_t vcpu_udata;
> + qemu_plugin_vcpu_tb_trans_cb_t vcpu_tb_trans;
> + qemu_plugin_vcpu_mem_cb_t vcpu_mem;
> + qemu_plugin_vcpu_syscall_cb_t vcpu_syscall;
> + qemu_plugin_vcpu_syscall_ret_cb_t vcpu_syscall_ret;
> + qemu_plugin_vcpu_syscall_filter_cb_t vcpu_syscall_filter;
> void *generic;
> };
>
> @@ -165,6 +166,11 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1,
> uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5,
> uint64_t a6, uint64_t a7, uint64_t a8);
> void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret);
> +bool
> +qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
> + uint64_t a2, uint64_t a3, uint64_t a4,
> + uint64_t a5, uint64_t a6, uint64_t a7,
> + uint64_t a8, uint64_t *ret);
>
> void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
> uint64_t value_low,
> @@ -267,6 +273,13 @@ static inline
> void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
> { }
>
> +static inline bool
> +qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
> + uint64_t a2, uint64_t a3, uint64_t a4,
> + uint64_t a5, uint64_t a6, uint64_t a7,
> + uint64_t a8, uint64_t *ret)
> +{ }
> +
This needs to return false so it compile and work with --disable-plugins.
> static inline void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
> uint64_t value_low,
> uint64_t value_high,
> diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
> index c450106af1..cdd0b2c4da 100644
> --- a/include/qemu/qemu-plugin.h
> +++ b/include/qemu/qemu-plugin.h
> @@ -738,6 +738,25 @@ typedef void
> uint64_t a3, uint64_t a4, uint64_t a5,
> uint64_t a6, uint64_t a7, uint64_t a8);
>
> +/*
> + * typedef qemu_plugin_vcpu_syscall_filter_cb_t - vCPU syscall filter callback
> + * function type
> + * @vcpu_index: the executing vCPU
> + * @num: the syscall number
> + * @a1-a8: the syscall arguments
> + * @ret: the address of the syscall return value, set this if
> filtered
"must set this..."
> + *
> + * Returns: true if you want to filter this syscall (i.e. stop it being
> + * handled further), otherwise return false.
> + */
> +typedef bool
> +(*qemu_plugin_vcpu_syscall_filter_cb_t)(qemu_plugin_id_t id,
> + unsigned int vcpu_index,
> + int64_t num, uint64_t a1, uint64_t a2,
> + uint64_t a3, uint64_t a4, uint64_t a5,
> + uint64_t a6, uint64_t a7, uint64_t a8,
> + uint64_t *ret);
> +
> QEMU_PLUGIN_API
> void qemu_plugin_register_vcpu_syscall_cb(qemu_plugin_id_t id,
> qemu_plugin_vcpu_syscall_cb_t cb);
> @@ -751,6 +770,11 @@ void
> qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id,
> qemu_plugin_vcpu_syscall_ret_cb_t cb);
>
> +QEMU_PLUGIN_API
> +void
> +qemu_plugin_register_vcpu_syscall_filter_cb(qemu_plugin_id_t id,
> + qemu_plugin_vcpu_syscall_filter_cb_t cb);
> +
>
> /**
> * qemu_plugin_insn_disas() - return disassembly string for instruction
> diff --git a/include/user/syscall-trace.h b/include/user/syscall-trace.h
> index 9bd7ca19c8..61cdbd7583 100644
> --- a/include/user/syscall-trace.h
> +++ b/include/user/syscall-trace.h
> @@ -39,5 +39,22 @@ static inline void record_syscall_return(CPUState *cpu, int num, abi_long ret)
> gdb_syscall_return(cpu, num);
> }
>
> +static bool send_through_syscall_filters(CPUState *cpu, int num,
> + abi_long arg1, abi_long arg2,
> + abi_long arg3, abi_long arg4,
> + abi_long arg5, abi_long arg6,
> + abi_long arg7, abi_long arg8,
> + abi_long *ret)
> +{
> + uint64_t sysret64 = 0;
> + bool filtered = qemu_plugin_vcpu_syscall_filter(cpu, num, arg1, arg2,
> + arg3, arg4, arg5, arg6,
> + arg7, arg8, &sysret64);
> + if (filtered) {
> + *ret = sysret64;
> + }
> + return filtered;
> +}
> +
>
> #endif /* SYSCALL_TRACE_H */
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index d78b2029fa..1fb99dcf0c 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -14084,8 +14084,11 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1,
> print_syscall(cpu_env, num, arg1, arg2, arg3, arg4, arg5, arg6);
> }
>
> - ret = do_syscall1(cpu_env, num, arg1, arg2, arg3, arg4,
> - arg5, arg6, arg7, arg8);
> + if (!send_through_syscall_filters(cpu, num, arg1, arg2, arg3, arg4, arg5,
> + arg6, arg7, arg8, &ret)) {
> + ret = do_syscall1(cpu_env, num, arg1, arg2, arg3, arg4,
> + arg5, arg6, arg7, arg8);
> + }
>
> if (unlikely(qemu_loglevel_mask(LOG_STRACE))) {
> print_syscall_ret(cpu_env, num, ret, arg1, arg2,
> diff --git a/plugins/api.c b/plugins/api.c
> index eac04cc1f6..478d0c8889 100644
> --- a/plugins/api.c
> +++ b/plugins/api.c
> @@ -208,6 +208,13 @@ qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id,
> plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_RET, cb);
> }
>
> +void
> +qemu_plugin_register_vcpu_syscall_filter_cb(qemu_plugin_id_t id,
> + qemu_plugin_vcpu_syscall_filter_cb_t cb)
> +{
> + plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER, cb);
> +}
> +
> /*
> * Plugin Queries
> *
> diff --git a/plugins/core.c b/plugins/core.c
> index ead09fd2f1..8cd773cbca 100644
> --- a/plugins/core.c
> +++ b/plugins/core.c
> @@ -538,6 +538,42 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
> }
> }
>
> +/*
> + * Disable CFI checks.
> + * The callback function has been loaded from an external library so we do not
> + * have type information
> + */
> +QEMU_DISABLE_CFI
> +bool
> +qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1,
> + uint64_t a2, uint64_t a3, uint64_t a4,
> + uint64_t a5, uint64_t a6, uint64_t a7,
> + uint64_t a8, uint64_t *ret)
> +{
> + struct qemu_plugin_cb *cb, *next;
> + enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER;
> +
> + if (!test_bit(ev, cpu->plugin_state->event_mask)) {
> + return false;
> + }
> +
> + bool filtered = false;
nit: keep args together in the block
> + QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
> + qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter;
> +
> + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
I think setting these flags could be outside the loop.
> + if (func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4,
> + a5, a6, a7, a8, ret)) {
> + filtered = true;
then you could just break here.
> + }
> + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
> +
> + if (filtered)
> + break;
nit: brackets
> + }
> + return filtered;
> +}
> +
> void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
> {
> /* idle and resume cb may be called before init, ignore in this case */
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
On 11/10/25 5:34 AM, Ziyang Zhang wrote: > This commit adds a syscall filter API to the TCG plugin API set. > Plugins can register a filter callback to QEMU to decide whether > to intercept a syscall, process it and bypass the QEMU syscall > handler. > > Signed-off-by: Ziyang Zhang <functioner@sjtu.edu.cn> > Co-authored-by: Mingyuan Xia <xiamy@ultrarisc.com> > --- > include/qemu/plugin-event.h | 1 + > include/qemu/plugin.h | 29 +++++++++++++++++++++-------- > include/qemu/qemu-plugin.h | 24 ++++++++++++++++++++++++ > include/user/syscall-trace.h | 17 +++++++++++++++++ > linux-user/syscall.c | 7 +++++-- > plugins/api.c | 7 +++++++ > plugins/core.c | 36 ++++++++++++++++++++++++++++++++++++ > 7 files changed, 111 insertions(+), 10 deletions(-) > Looks good to me, and will be useful for syscalls obervation, fuzzing, and integrating native libraries through hypercall mechanism. It's worth merging this. Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
© 2016 - 2025 Red Hat, Inc.