* [PATCH v3 1/2] linux-user: add plugin API to filter syscalls
2025-12-14 14:19 [PATCH v3 0/2] linux-user: add plugin API to filter syscalls Ziyang Zhang
@ 2025-12-14 14:19 ` Ziyang Zhang
2025-12-14 14:19 ` [PATCH v3 2/2] tcg tests: add a test to verify the syscall filter plugin API Ziyang Zhang
1 sibling, 0 replies; 4+ messages in thread
From: Ziyang Zhang @ 2025-12-14 14:19 UTC (permalink / raw)
To: qemu-devel
Cc: Riku Voipio, Laurent Vivier, Alex Bennee, Alexandre Iooss,
Mahmoud Mandour, Pierrick Bouvier, Richard Henderson, Zhengwei Qi,
Yun Wang, Mingyuan Xia, Kailiang Xu, Ziyang Zhang
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 | 33 +++++++++++++++++++++++---------
include/qemu/qemu-plugin.h | 32 +++++++++++++++++++++++++++++++
include/user/syscall-trace.h | 17 +++++++++++++++++
linux-user/syscall.c | 7 +++++--
plugins/api.c | 7 +++++++
plugins/core.c | 37 ++++++++++++++++++++++++++++++++++++
7 files changed, 123 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..91df1e78d2 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,15 @@ 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)
+{
+ return false;
+}
+
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..f3bff82257 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -798,6 +798,33 @@ 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
+ * @id: plugin id
+ * @vcpu_index: the executing vCPU
+ * @num: the syscall number
+ * @a1: the 1st syscall argument
+ * @a2: the 2nd syscall argument
+ * @a3: the 3rd syscall argument
+ * @a4: the 4th syscall argument
+ * @a5: the 5th syscall argument
+ * @a6: the 6th syscall argument
+ * @a7: the 7th syscall argument
+ * @a8: the 8th syscall argument
+ * @sysret: the reference of the syscall return value, must set this if filtered
+ *
+ * Returns true if you want to filter this syscall (i.e. stop it being
+ * handled further), otherwise returns 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 *sysret);
+
QEMU_PLUGIN_API
void qemu_plugin_register_vcpu_syscall_cb(qemu_plugin_id_t id,
qemu_plugin_vcpu_syscall_cb_t cb);
@@ -811,6 +838,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..4ee6c4e513 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 *sysret)
+{
+ uint64_t sysret64 = 0;
+ bool filtered = qemu_plugin_vcpu_syscall_filter(cpu, num, arg1, arg2,
+ arg3, arg4, arg5, arg6,
+ arg7, arg8, &sysret64);
+ if (filtered) {
+ *sysret = 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..85fabf9ec8 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -564,6 +564,43 @@ 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 *sysret)
+{
+ struct qemu_plugin_cb *cb, *next;
+ enum qemu_plugin_event ev = QEMU_PLUGIN_EV_VCPU_SYSCALL_FILTER;
+ bool filtered = false;
+
+ if (!test_bit(ev, cpu->plugin_state->event_mask)) {
+ return false;
+ }
+
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
+
+ QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
+ qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter;
+
+ if (func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4,
+ a5, a6, a7, a8, sysret)) {
+ filtered = true;
+ break;
+ }
+ }
+
+ qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
+
+ 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
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v3 2/2] tcg tests: add a test to verify the syscall filter plugin API
2025-12-14 14:19 [PATCH v3 0/2] linux-user: add plugin API to filter syscalls Ziyang Zhang
2025-12-14 14:19 ` [PATCH v3 1/2] " Ziyang Zhang
@ 2025-12-14 14:19 ` Ziyang Zhang
1 sibling, 0 replies; 4+ messages in thread
From: Ziyang Zhang @ 2025-12-14 14:19 UTC (permalink / raw)
To: qemu-devel
Cc: Riku Voipio, Laurent Vivier, Alex Bennee, Alexandre Iooss,
Mahmoud Mandour, Pierrick Bouvier, Richard Henderson, Zhengwei Qi,
Yun Wang, Mingyuan Xia, Kailiang Xu, Ziyang Zhang
Register a syscall filter callback in tests/tcg/plugins/sycall.c,
returns a specific value for a magic system call number, and check
it in tests/tcg/multiarch/test-plugin-syscall-filter.c.
Signed-off-by: Ziyang Zhang <functioner@sjtu.edu.cn>
Co-authored-by: Mingyuan Xia <xiamy@ultrarisc.com>
---
tests/tcg/multiarch/Makefile.target | 4 ++-
.../multiarch/test-plugin-syscall-filter.c | 35 +++++++++++++++++++
tests/tcg/plugins/syscall.c | 19 ++++++++++
3 files changed, 57 insertions(+), 1 deletion(-)
create mode 100644 tests/tcg/multiarch/test-plugin-syscall-filter.c
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index f5b4d2b813..4005e3a8a9 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -202,8 +202,10 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \
CHECK_PLUGIN_OUTPUT_COMMAND= \
$(SRC_PATH)/tests/tcg/multiarch/check-plugin-output.sh \
$(QEMU) $<
+run-plugin-test-plugin-syscall-filter-with-libsyscall.so:
-EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so
+EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so \
+ run-plugin-test-plugin-syscall-filter-with-libsyscall.so
endif
# Update TESTS
diff --git a/tests/tcg/multiarch/test-plugin-syscall-filter.c b/tests/tcg/multiarch/test-plugin-syscall-filter.c
new file mode 100644
index 0000000000..caa2063a46
--- /dev/null
+++ b/tests/tcg/multiarch/test-plugin-syscall-filter.c
@@ -0,0 +1,35 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This test attempts to execute a magic syscall. The syscall test plugin
+ * should intercept this and return an expected value.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int main(int argc, char *argv[])
+{
+ /*
+ * We cannot use a very large magic syscall number, because on some ISAs,
+ * QEMU will treat it as an illegal instruction and trigger a critical
+ * exception. For instance, on arm32, the syscall number cannot exceed
+ * ARM_NR_BASE (0xf0000), as can be seen in
+ * "linux-user/arm/cpu_loop.c:cpu_loop".
+ *
+ * Therefore, we pick 2048 because, as of now, no ISA in Linux uses this
+ * number. This is just a test case; replace this number as needed in the
+ * future.
+ *
+ * The corresponding syscall filter is implemented in
+ * "tests/tcg/plugins/syscall.c".
+ */
+ long ret = syscall(2048, 0x66CCFF);
+ if (ret != 0xFFCC66) {
+ fprintf(stderr, "Error: unexpected syscall return value %ld\n", ret);
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/tests/tcg/plugins/syscall.c b/tests/tcg/plugins/syscall.c
index 42801f5c86..e7d4e9b589 100644
--- a/tests/tcg/plugins/syscall.c
+++ b/tests/tcg/plugins/syscall.c
@@ -170,6 +170,24 @@ static void vcpu_syscall_ret(qemu_plugin_id_t id, unsigned int vcpu_idx,
}
}
+static bool vcpu_syscall_filter(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 *sysret)
+{
+ /* Special syscall to test the filter functionality. */
+ if (num == 2048 && a1 == 0x66CCFF) {
+ *sysret = 0xFFCC66;
+
+ if (!statistics) {
+ qemu_plugin_outs("magic syscall filtered, set magic return\n");
+ }
+ return true;
+ }
+ return false;
+}
+
static void print_entry(gpointer val, gpointer user_data)
{
SyscallStats *entry = (SyscallStats *) val;
@@ -255,6 +273,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
qemu_plugin_register_vcpu_syscall_cb(id, vcpu_syscall);
qemu_plugin_register_vcpu_syscall_ret_cb(id, vcpu_syscall_ret);
+ qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter);
qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
return 0;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread