From: Ziyang Zhang <functioner@sjtu.edu.cn>
To: qemu-devel <qemu-devel@nongnu.org>
Cc: Riku Voipio <riku.voipio@iki.fi>,
Laurent Vivier <laurent@vivier.eu>,
Alex Bennee <alex.bennee@linaro.org>,
Alexandre Iooss <erdnaxe@crans.org>,
Mahmoud Mandour <ma.mandourr@gmail.com>,
Pierrick Bouvier <pierrick.bouvier@linaro.org>,
Richard Henderson <richard.henderson@linaro.org>,
Zhengwei Qi <qizhwei@sjtu.edu.cn>,
Yun Wang <yunwang94@sjtu.edu.cn>,
Mingyuan Xia <xiamy@ultrarisc.com>,
Kailiang Xu <xukl2019@sjtu.edu.cn>,
Ziyang Zhang <functioner@sjtu.edu.cn>
Subject: [PATCH v2 1/2] linux-user: add a plugin API to filter syscalls
Date: Fri, 12 Dec 2025 22:15:40 +0800 [thread overview]
Message-ID: <20251212141541.1792111-2-functioner@sjtu.edu.cn> (raw)
In-Reply-To: <20251212141541.1792111-1-functioner@sjtu.edu.cn>
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 | 31 ++++++++++++++++++++++---------
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, 112 insertions(+), 11 deletions(-)
diff --git a/include/qemu/plugin-event.h b/include/qemu/plugin-event.h
index 1100dae212..7f3c5927f1 100644
--- a/include/qemu/plugin-event.h
+++ b/include/qemu/plugin-event.h
@@ -23,6 +23,7 @@ enum qemu_plugin_event {
QEMU_PLUGIN_EV_VCPU_INTERRUPT,
QEMU_PLUGIN_EV_VCPU_EXCEPTION,
QEMU_PLUGIN_EV_VCPU_HOSTCALL,
+ 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 cea0a68858..4eb8612cb9 100644
--- a/include/qemu/plugin.h
+++ b/include/qemu/plugin.h
@@ -55,15 +55,16 @@ 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_discon_cb_t vcpu_discon;
- 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_discon_cb_t vcpu_discon;
+ 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;
};
@@ -169,6 +170,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,
@@ -280,6 +286,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 60de4fdd3f..b70049c80c 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -798,6 +798,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);
@@ -811,6 +830,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 2060e561a2..9ae3c5bfbd 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -14201,8 +14201,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 b4b783008f..de82d0f5d6 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -564,6 +564,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
next prev parent reply other threads:[~2025-12-12 14:18 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-12-12 14:15 [PATCH v2 0/2] linux-user: add a syscall-filter plugin API Ziyang Zhang
2025-12-12 14:15 ` Ziyang Zhang [this message]
2025-12-12 14:15 ` [PATCH v2 2/2] tcg tests: add a test to verify the syscall filter " Ziyang Zhang
2025-12-12 16:00 ` Alex Bennée
2025-12-12 17:32 ` Pierrick Bouvier
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251212141541.1792111-2-functioner@sjtu.edu.cn \
--to=functioner@sjtu.edu.cn \
--cc=alex.bennee@linaro.org \
--cc=erdnaxe@crans.org \
--cc=laurent@vivier.eu \
--cc=ma.mandourr@gmail.com \
--cc=pierrick.bouvier@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=qizhwei@sjtu.edu.cn \
--cc=richard.henderson@linaro.org \
--cc=riku.voipio@iki.fi \
--cc=xiamy@ultrarisc.com \
--cc=xukl2019@sjtu.edu.cn \
--cc=yunwang94@sjtu.edu.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.