88
99Usage:
1010 from qiling import Qiling
11- from qiling.os.posix.kernel_proxy import KernelProxy
11+ from qiling.os.posix.kernel_proxy import KernelProxy, FD, PtrIn, PtrOut
1212
1313 ql = Qiling(argv=["/bin/myserver"], rootfs="rootfs/x8664_linux")
1414 proxy = KernelProxy(ql)
15- proxy.forward_syscall("epoll_create", returns_fd=True)
16- proxy.forward_syscall("epoll_ctl")
17- proxy.forward_syscall("epoll_wait")
15+
16+ # integer-arg syscall returning a new FD
17+ proxy.forward_syscall("epoll_create1", returns_fd=True)
18+
19+ # epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
20+ proxy.forward_syscall("epoll_ctl",
21+ arg_types=(FD, "int", FD, PtrIn(size=12)))
22+
23+ # epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
24+ proxy.forward_syscall("epoll_wait",
25+ arg_types=(FD, PtrOut(size=lambda a: a[2] * 12), "int", "int"))
26+
1827 ql.run()
1928"""
2029
2433import sys
2534import socket
2635import subprocess
27- from typing import Dict , Optional , TYPE_CHECKING
36+ import weakref
37+ from typing import Dict , Optional , Sequence , Tuple , TYPE_CHECKING
2838
29- from qiling .const import QL_INTERCEPT , QL_OS
39+ from qiling .const import QL_INTERCEPT
3040from qiling .exception import QlErrorArch , QlErrorSyscallError , QlErrorSyscallNotFound
41+ from qiling .os .posix .kernel_proxy .argtypes import (
42+ INT , FD , PtrIn , PtrOut , PtrInOut , is_pointer ,
43+ )
3144from qiling .os .posix .kernel_proxy .ipc import ProxyClient
3245from qiling .os .posix .kernel_proxy .proxy_fd import ql_proxy_fd
3346
3447if TYPE_CHECKING :
3548 from qiling import Qiling
3649
3750
51+ __all__ = ['KernelProxy' , 'INT' , 'FD' , 'PtrIn' , 'PtrOut' , 'PtrInOut' ]
52+
53+
3854class KernelProxy :
3955 """Forward specific syscalls to a real Linux kernel via a helper process.
4056
@@ -117,40 +133,74 @@ def _resolve_syscall_nr(self, name: str) -> int:
117133 )
118134 return table [name ]
119135
120- def forward_syscall (self , name : str , returns_fd : bool = False ):
136+ def forward_syscall (self , name : str , returns_fd : bool = False ,
137+ arg_types : Optional [Sequence ] = None ):
121138 """Register a CALL hook that forwards this syscall to the kernel proxy.
122139
123140 Args:
124- name: syscall name (e.g. "epoll_create ", "eventfd2")
141+ name: syscall name (e.g. "epoll_create1 ", "eventfd2").
125142 returns_fd: if True, wrap the return value in ql_proxy_fd and store
126- in the Qiling FD table. Use this for syscalls that return
127- file descriptors (epoll_create, eventfd, timerfd_create, etc.)
143+ it in the Qiling FD table. Use this for syscalls that
144+ return file descriptors (epoll_create1, eventfd2, etc.).
145+ arg_types: optional per-arg descriptors. Each entry is one of:
146+ INT (or "int") — pass through unchanged (default).
147+ FD (or "fd") — guest FD; translated to the proxy FD.
148+ PtrIn(size) — pointer; bytes copied from guest to proxy.
149+ PtrOut(size) — pointer; bytes copied back from proxy to guest.
150+ PtrInOut(size) — pointer; both directions.
151+ If omitted, all arguments are treated as INT.
128152 """
129153 nr = self ._resolve_syscall_nr (name )
130154 self ._forwarded [name ] = nr
131155
132- forwarder = self ._make_forwarder (name , nr , returns_fd )
156+ forwarder = self ._make_forwarder (name , nr , returns_fd , arg_types )
133157 self .ql .os .set_syscall (name , forwarder , QL_INTERCEPT .CALL )
134158
135- self .ql .log .info (f"forwarding syscall '{ name } ' (nr={ nr } ) to kernel proxy"
136- f"{ ' [returns FD]' if returns_fd else '' } " )
159+ kind = []
160+ if returns_fd :
161+ kind .append ('returns FD' )
162+ if arg_types :
163+ kind .append (f'arg_types={ tuple (type (a ).__name__ if not isinstance (a , str ) else a for a in arg_types )} ' )
137164
138- def _make_forwarder (self , name : str , guest_nr : int , returns_fd : bool ):
139- """Create a CALL hook closure for one syscall."""
165+ suffix = f" [{ ', ' .join (kind )} ]" if kind else ''
166+ self .ql .log .info (f"forwarding syscall '{ name } ' (nr={ nr } ) to kernel proxy{ suffix } " )
167+
168+ def _make_forwarder (self , name : str , guest_nr : int , returns_fd : bool ,
169+ arg_types : Optional [Sequence ]):
170+ """Create a CALL hook closure for one syscall.
171+
172+ Captures only the data the closure needs (host syscall nr, client, weakref
173+ to self) so the registered hook does not keep the KernelProxy alive.
174+ """
175+ # resolve once at registration time so the hot path stays simple
176+ host_nr = self ._get_host_syscall_nr (name )
140177 client = self ._client
178+ weak_self = weakref .ref (self )
141179
142- def _forwarder (ql , * args ):
143- # use the HOST syscall number, not the guest number.
144- # for now, resolve from the host's syscall table at runtime.
145- host_nr = self ._get_host_syscall_nr (name )
180+ # normalize arg_types to a tuple, treating the string aliases as-is
181+ spec = tuple (arg_types ) if arg_types else ()
182+ has_pointers = any (is_pointer (s ) for s in spec )
146183
147- padded = args + (0 ,) * (6 - len (args ))
148- retval = client .syscall (host_nr , padded [:6 ])
184+ def _forwarder (ql , * args ):
185+ self_ref = weak_self ()
186+ if self_ref is None :
187+ ql .log .error (f"kernel_proxy: { name } () called after proxy was destroyed" )
188+ return - 1
189+
190+ translated = self_ref ._translate_args (name , args , spec )
191+
192+ if has_pointers :
193+ in_bufs , out_specs , out_arg_indices = self_ref ._collect_buffers (
194+ ql , translated , spec
195+ )
196+ retval , out_data = client .syscall_ex (host_nr , translated , in_bufs , out_specs )
197+ self_ref ._writeback_buffers (ql , args , out_arg_indices , out_data )
198+ else :
199+ retval = client .syscall (host_nr , translated )
149200
150201 if returns_fd and retval >= 0 :
151- # the proxy created a real FD. wrap it and store in Qiling's FD table.
152202 proxy_fd_obj = ql_proxy_fd (client , retval )
153- guest_fd = self ._alloc_fd (ql , proxy_fd_obj )
203+ guest_fd = self_ref ._alloc_fd (ql , proxy_fd_obj )
154204 ql .log .debug (f"kernel_proxy: { name } () -> proxy_fd={ retval } , guest_fd={ guest_fd } " )
155205 return guest_fd
156206
@@ -160,6 +210,60 @@ def _forwarder(ql, *args):
160210 _forwarder .__name__ = f'ql_syscall_{ name } '
161211 return _forwarder
162212
213+ def _translate_args (self , name : str , args : Tuple [int , ...],
214+ spec : Tuple ) -> Tuple [int , ...]:
215+ """Translate guest FD args to proxy FD numbers; pad to 6 args.
216+
217+ Pointer args are left untouched here — _collect_buffers replaces them
218+ with the proxy-side buffer addresses just before invocation.
219+ """
220+ out = list (args ) + [0 ] * (6 - len (args ))
221+
222+ for idx , kind in enumerate (spec ):
223+ if kind == FD :
224+ guest_fd = args [idx ]
225+ fd_obj = self .ql .os .fd [guest_fd ] if 0 <= guest_fd < len (self .ql .os .fd ) else None
226+
227+ if not isinstance (fd_obj , ql_proxy_fd ):
228+ raise QlErrorSyscallError (
229+ f"kernel_proxy: { name } () arg{ idx } guest_fd={ guest_fd } "
230+ f"does not refer to a proxy-owned FD"
231+ )
232+
233+ out [idx ] = fd_obj ._proxy_fd
234+
235+ return tuple (out [:6 ])
236+
237+ def _collect_buffers (self , ql , args : Tuple [int , ...], spec : Tuple ):
238+ """Read PtrIn/PtrInOut buffers from guest memory; collect PtrOut sizes."""
239+ in_bufs = []
240+ out_specs = []
241+ out_arg_indices = []
242+
243+ for idx , kind in enumerate (spec ):
244+ if isinstance (kind , (PtrIn , PtrInOut )):
245+ size = kind .resolve (args )
246+ if size > 0 :
247+ data = bytes (ql .mem .read (args [idx ], size ))
248+ in_bufs .append ((idx , data ))
249+
250+ if isinstance (kind , (PtrOut , PtrInOut )):
251+ size = kind .resolve (args )
252+ if size > 0 :
253+ out_specs .append ((idx , size ))
254+ out_arg_indices .append (idx )
255+
256+ return in_bufs , out_specs , out_arg_indices
257+
258+ @staticmethod
259+ def _writeback_buffers (ql , args : Tuple [int , ...],
260+ out_arg_indices : Sequence [int ],
261+ out_data : Sequence [bytes ]):
262+ """Write PtrOut/PtrInOut response buffers back into guest memory."""
263+ for idx , data in zip (out_arg_indices , out_data ):
264+ if data :
265+ ql .mem .write (args [idx ], data )
266+
163267 def _get_host_syscall_nr (self , name : str ) -> int :
164268 """Get the syscall number on the HOST architecture."""
165269 # we are running on Linux — read from the host's syscall table
0 commit comments