master
c 134 lines 4.36 KB
Raw
1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 *
4 * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
5 *
6 * This test set exercises the qemu_plugin_set_pc() function in four different
7 * contexts:
8 * 1. in an instruction callback during normal execution,
9 * 2. in an instruction callback during signal handling,
10 * 3. in a memory access callback.
11 * 4. in a syscall callback,
12 */
13 #include <assert.h>
14 #include <signal.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <unistd.h>
19
20 /* If we issue this magic syscall, ... */
21 #define MAGIC_SYSCALL 4096
22 /* ... the plugin either jumps directly to the target address ... */
23 #define SETPC 0
24 /* ... or just updates the target address for future use in callbacks. */
25 #define SETTARGET 1
26
27 static int signal_handled;
28
29 void panic(const char *msg)
30 {
31 fprintf(stderr, "Panic: %s\n", msg);
32 abort();
33 }
34
35 /*
36 * This test executes a magic syscall which communicates two addresses to the
37 * plugin via the syscall arguments. Whenever we reach the "bad" instruction
38 * during normal execution, the plugin should redirect control flow to the
39 * "good" instruction instead.
40 */
41 void test_insn(void)
42 {
43 long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_insn, &&good_insn,
44 NULL);
45 assert(ret == 0 && "Syscall filter did not return expected value");
46 bad_insn:
47 panic("PC redirection in instruction callback failed");
48 good_insn:
49 puts("PC redirection in instruction callback succeeded");
50 }
51
52 /*
53 * This signal handler communicates a "bad" and a "good" address to the plugin
54 * similar to the previous test, and skips to the "good" address when the "bad"
55 * one is reached. This serves to test whether PC redirection via
56 * qemu_plugin_set_pc() also works properly in a signal handler context.
57 */
58 void usr1_handler(int signum)
59 {
60 long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_signal, &&good_signal,
61 NULL);
62 assert(ret == 0 && "Syscall filter did not return expected value");
63 bad_signal:
64 panic("PC redirection in instruction callback failed");
65 good_signal:
66 signal_handled = 1;
67 puts("PC redirection in instruction callback succeeded");
68 }
69
70 /*
71 * This test sends a signal to the process, which should trigger the above
72 * signal handler. The signal handler should then exercise the PC redirection
73 * functionality in the context of a signal handler, which behaves a bit
74 * differently from normal execution.
75 */
76 void test_sighandler(void)
77 {
78 struct sigaction sa = {0};
79 sa.sa_handler = usr1_handler;
80 sigaction(SIGUSR1, &sa, NULL);
81 pid_t pid = getpid();
82 kill(pid, SIGUSR1);
83 assert(signal_handled == 1 && "Signal handler was not executed properly");
84 }
85
86 /*
87 * This test communicates a "good" address and the address of a local variable
88 * to the plugin. Upon accessing the local variable, the plugin should then
89 * redirect control flow to the "good" address via qemu_plugin_set_pc().
90 */
91 void test_mem(void)
92 {
93 static uint32_t test = 1;
94 long ret = syscall(MAGIC_SYSCALL, SETTARGET, NULL, &&good_mem, &test);
95 assert(ret == 0 && "Syscall filter did not return expected value");
96 /* Ensure read access to the variable to trigger the plugin callback */
97 assert(test == 1);
98 panic("PC redirection in memory access callback failed");
99 good_mem:
100 puts("PC redirection in memory access callback succeeded");
101 }
102
103 /*
104 * This test executes a magic syscall which is intercepted and its actual
105 * execution skipped via the qemu_plugin_set_pc() API. In a proper plugin,
106 * syscall skipping would rather be implemented via the syscall filtering
107 * callback, but we want to make sure qemu_plugin_set_pc() works in different
108 * contexts.
109 */
110 __attribute__((noreturn))
111 void test_syscall(void)
112 {
113 syscall(MAGIC_SYSCALL, SETPC, &&good_syscall);
114 panic("PC redirection in syscall callback failed");
115 good_syscall:
116 /*
117 * Note: we execute this test last and exit straight from here because when
118 * the plugin redirects control flow upon syscall, the stack frame for the
119 * syscall function (and potential other functions in the call chain in
120 * libc) is still live and the stack is not unwound properly. Thus,
121 * returning from here is risky and breaks on some architectures, so we
122 * just exit directly from this test.
123 */
124 _exit(EXIT_SUCCESS);
125 }
126
127
128 int main(int argc, char *argv[])
129 {
130 test_insn();
131 test_sighandler();
132 test_mem();
133 test_syscall();
134 }