From 901aafa5dcf30e64b0f6000e9af729e5c507e16f Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Mon, 8 Dec 2025 09:52:25 -0500 Subject: [PATCH 1/6] syscalls: unregistering hooks/remove _name_to_hook_ptr --- pyplugins/apis/syscalls.py | 40 ++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index bbf0edcf..2cacdc79 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -5,7 +5,7 @@ from penguin import plugins, Plugin import json -from typing import Dict, List, Any, Callable, Optional, Iterator +from typing import Dict, List, Any, Callable, Optional, Iterator, Generator from hyper.consts import value_filter_type as vft from hyper.consts import igloo_hypercall_constants as iconsts from hyper.consts import igloo_base_hypercalls as bconsts @@ -334,7 +334,6 @@ def __init__(self) -> None: # Track function -> hook_ptr and name -> hook_ptr for easier lookup self._func_to_hook_ptr = {} # Maps functions to hook pointers - self._name_to_hook_ptr = {} # Maps function names to hook pointers # Syscall type information - using dictionary for fast name-based # lookups @@ -473,10 +472,6 @@ def _syscall_interrupt_handler(self) -> bool: # Track function to hook pointer mappings self._func_to_hook_ptr[func] = hook_ptr - # Store by function name if available - func_name = getattr(func, "__name__", None) - if func_name: - self._name_to_hook_ptr[func_name] = hook_ptr ''' On repeated calls to the same syscall in portal we produce new @@ -993,3 +988,36 @@ def decorator(func): return func return decorator + + def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None]: + """ + Unregister a syscall hook. + Parameters + ---------- + func : Callable + The handle for the syscall hook to unregister. + + Returns + ------- + bool + True if unregistered successfully, False otherwise. + """ + if func not in self._func_to_hook_ptr: + self.logger.error("Function not registered as a syscall hook") + return False + hook_ptr = self._func_to_hook_ptr[func] + retval = yield from plugins.kffi.call_kernel_function("unregister_syscall_hook", hook_ptr) + if retval != 0: + self.logger.error(f"Failed to unregister syscall hook 0x{hook_ptr:x} ({func}) with kffi") + # Even though this failed we will attempt to clean up internal state instead of bailing + + try: + self._hooks.pop(hook_ptr, None) + self._func_to_hook_ptr.pop(func, None) + self._hook_info.pop(hook_ptr, None) + self._hook_proto_cache.pop(hook_ptr, None) + except Exception as e: + self.logger.error(f"Error cleaning up internal state for hook 0x{hook_ptr:x} ({func}): {e}") + return False + + return retval == 0 From 4449805b00f6cb12662b818537f810f6b965dd66 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Mon, 8 Dec 2025 17:43:33 -0500 Subject: [PATCH 2/6] bump igloo_driver; add unit test for unregistering syscall --- Dockerfile | 4 ++-- pyplugins/apis/syscalls.py | 10 +++++++++- pyplugins/testing/syscall_test.py | 12 +++++++++++- .../test_target/patches/tests/syscall.yaml | 9 +++++++-- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 39b60442..0bd9260c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG BASE_IMAGE="${REGISTRY}/ubuntu:22.04" ARG VPN_VERSION="1.0.25" ARG BUSYBOX_VERSION="0.0.15" ARG LINUX_VERSION="3.5.16-beta" -ARG IGLOO_DRIVER_VERSION="0.0.29" +ARG IGLOO_DRIVER_VERSION="0.0.30" ARG LIBNVRAM_VERSION="0.0.23" ARG CONSOLE_VERSION="1.0.7" ARG GUESTHOPPER_VERSION="1.0.20" @@ -600,4 +600,4 @@ RUN cd /igloo_static && \ done \ done RUN date +%s%N > /igloo_static/container_timestamp.txt -RUN pip install py-spy \ No newline at end of file +RUN pip install py-spy diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index 2cacdc79..43272fbc 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -472,7 +472,6 @@ def _syscall_interrupt_handler(self) -> bool: # Track function to hook pointer mappings self._func_to_hook_ptr[func] = hook_ptr - ''' On repeated calls to the same syscall in portal we produce new syscall_event objects. However, it doesn't update the version of the object @@ -1006,6 +1005,15 @@ def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None] self.logger.error("Function not registered as a syscall hook") return False hook_ptr = self._func_to_hook_ptr[func] + + # This is meant to mark the hook as disabled + if hook_ptr in self._hooks: + self._hooks[hook_ptr] = None + else: + self.logger.error(f"Hook pointer 0x{hook_ptr:x} not found in internal hooks when attempting to disable") + return False + + # Note that if called from within a syscall handler this will actually defer unregistration retval = yield from plugins.kffi.call_kernel_function("unregister_syscall_hook", hook_ptr) if retval != 0: self.logger.error(f"Failed to unregister syscall hook 0x{hook_ptr:x} ({func}) with kffi") diff --git a/pyplugins/testing/syscall_test.py b/pyplugins/testing/syscall_test.py index 2269c8a2..ab2eca62 100644 --- a/pyplugins/testing/syscall_test.py +++ b/pyplugins/testing/syscall_test.py @@ -25,8 +25,10 @@ def __init__(self): self.ioctl_ret_num = 0 self.ioctl_ret2_num = 0 self.ioctl_ret3_num = 0 + self.getpids = 0 # For unregister test syscalls.syscall("on_sys_ioctl_enter", comm_filter="send_syscall", arg_filters=[None, 0xabcd])(self.test_skip_retval) + self.getpid_hook = syscalls.syscall("on_sys_getpid_return")(self.getpid) def test_skip_retval(self, regs, proto, syscall, fd, op, arg): assert fd == 9, f"Expected fd 9, got {fd:#x}" @@ -72,7 +74,6 @@ def ioctl_noret(self, regs, proto, syscall, fd, op, arg): with open(join(self.outdir, "syscall_test.txt"), "a") as f: f.write("Syscall ioctl_noret: failure\n") - @syscalls.syscall("on_sys_getpid_return") def getpid(self, regs, proto, syscall, *args): # NOTE: We've removed this check because it was causing issues # It doesn't seem to indicate anything negative so we're skipping it @@ -86,6 +87,8 @@ def getpid(self, regs, proto, syscall, *args): if "send_syscall" in self.panda.get_process_name(self.panda.get_cpu()): self.success_getpid = True self.report_getpid() + yield from self.plugins.syscalls.unregister_syscall_hook(self.getpid_hook) + self.getpids += 1 @syscalls.syscall("on_sys_clone_enter") def syscall_test(self, regs, proto, syscall, *args): @@ -150,6 +153,13 @@ def report_getpid(self): self.logger.info(f"Syscall getpid test: {result}") f.write(f"Syscall getpid test: {result}\n") + def report_unregister(self): + with open(join(self.outdir, "syscall_test.txt"), "a") as f: + result = "passed" if self.getpids == 1 else "failed" + self.logger.info(f"Syscall unregister test: {result} (getpid hook called {self.getpids} times)") + f.write(f"Syscall unregister test: {result}\n") + def uninit(self): self.report_clone() self.report_getpid() + self.report_unregister() diff --git a/tests/unit_tests/test_target/patches/tests/syscall.yaml b/tests/unit_tests/test_target/patches/tests/syscall.yaml index 1b0e7972..e1e6e144 100644 --- a/tests/unit_tests/test_target/patches/tests/syscall.yaml +++ b/tests/unit_tests/test_target/patches/tests/syscall.yaml @@ -34,6 +34,10 @@ plugins: type: file_contains file: syscall_test.txt string: "Syscall ioctl_reg3: success 1" + syscall_hypercall+unregister: + type: file_contains + file: syscall_test.txt + string: "Syscall unregister test: passed" static_files: /tests/syscall.sh: @@ -46,8 +50,8 @@ static_files: /igloo/utils/send_syscall ioctl 0x13 0x0 0x1 /igloo/utils/send_syscall ioctl 0x13 0x1234 0xabce /igloo/utils/send_syscall ioctl 0x13 0x1234 0xabcd - - /igloo/utils/send_syscall ioctl 0x9 0xabcd + + /igloo/utils/send_syscall ioctl 0x9 0xabcd if [ $? -ne 43 ]; then echo "Error: send_syscall retval enter failed" @@ -60,6 +64,7 @@ static_files: exit 1 fi /igloo/utils/send_syscall getpid + /igloo/utils/send_syscall getpid # This one should not get through because we'll unregister echo "Syscall test: passed" exit 0 fi From ac190a27b5aa7ece547b5d5670ccc3a00f2a8697 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Mon, 8 Dec 2025 18:05:50 -0500 Subject: [PATCH 3/6] copilot fixes --- pyplugins/apis/syscalls.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index 43272fbc..a458970b 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -693,7 +693,7 @@ def _syscall_event(self, cpu: int, is_enter: Optional[bool] = None) -> Any: # 1. Get Event Object (No bytes serialization yet) sce = self._get_syscall_event(cpu, arg) hook_ptr = sce.hook.address - if hook_ptr not in self._hooks: + if hook_ptr not in self._hooks or self._hooks[hook_ptr] is None: return # 2. Unpack Hook Data including read_only flag @@ -1012,6 +1012,7 @@ def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None] else: self.logger.error(f"Hook pointer 0x{hook_ptr:x} not found in internal hooks when attempting to disable") return False + yield # Note that if called from within a syscall handler this will actually defer unregistration retval = yield from plugins.kffi.call_kernel_function("unregister_syscall_hook", hook_ptr) @@ -1027,5 +1028,6 @@ def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None] except Exception as e: self.logger.error(f"Error cleaning up internal state for hook 0x{hook_ptr:x} ({func}): {e}") return False + yield return retval == 0 From 159fcd9366c714f5581c7efa8c77340e9d58feaf Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Wed, 10 Dec 2025 10:59:27 -0500 Subject: [PATCH 4/6] portalcall for unregister_syscall --- Dockerfile | 2 +- pyplugins/apis/syscalls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0bd9260c..430e61cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG BASE_IMAGE="${REGISTRY}/ubuntu:22.04" ARG VPN_VERSION="1.0.25" ARG BUSYBOX_VERSION="0.0.15" ARG LINUX_VERSION="3.5.16-beta" -ARG IGLOO_DRIVER_VERSION="0.0.30" +ARG IGLOO_DRIVER_VERSION="v0.0.31-pre.7340f4300" ARG LIBNVRAM_VERSION="0.0.23" ARG CONSOLE_VERSION="1.0.7" ARG GUESTHOPPER_VERSION="1.0.20" diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index a458970b..572e7d24 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -1015,7 +1015,7 @@ def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None] yield # Note that if called from within a syscall handler this will actually defer unregistration - retval = yield from plugins.kffi.call_kernel_function("unregister_syscall_hook", hook_ptr) + retval = yield PortalCmd("unregister_syscall_hook", hook_ptr) if retval != 0: self.logger.error(f"Failed to unregister syscall hook 0x{hook_ptr:x} ({func}) with kffi") # Even though this failed we will attempt to clean up internal state instead of bailing From ea472fa94438c17fb11afa9392ec106cd8c8e36f Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Wed, 10 Dec 2025 14:54:14 -0500 Subject: [PATCH 5/6] return 1 on success --- pyplugins/apis/syscalls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index 572e7d24..8bb9cdd1 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -1016,7 +1016,7 @@ def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None] # Note that if called from within a syscall handler this will actually defer unregistration retval = yield PortalCmd("unregister_syscall_hook", hook_ptr) - if retval != 0: + if retval != 1: self.logger.error(f"Failed to unregister syscall hook 0x{hook_ptr:x} ({func}) with kffi") # Even though this failed we will attempt to clean up internal state instead of bailing From cd5f3cb62453b50fc128a1f36a2471d397274e0b Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Thu, 11 Dec 2025 15:50:15 -0500 Subject: [PATCH 6/6] still fighting portalcmd --- pyplugins/apis/syscalls.py | 50 +++++++++++++------------------ pyplugins/testing/syscall_test.py | 19 ++++++------ 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index 8bb9cdd1..5098e6e8 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -365,6 +365,9 @@ def __init__(self) -> None: self._pending_hooks = [] self._syscall_event = plugins.portal.wrap(self._syscall_event) + # Wrap the unregister function + self.unregister_syscall_hook = plugins.portal.wrap(self._do_unregister_syscall_hook) + def _setup_syscall_handler(self, cpu: int) -> None: """ Handler for setting up syscall definitions. @@ -988,46 +991,35 @@ def decorator(func): return decorator - def unregister_syscall_hook(self, func: Callable) -> Generator[bool, None, None]: - """ - Unregister a syscall hook. - Parameters - ---------- - func : Callable - The handle for the syscall hook to unregister. + def _do_unregister_syscall_hook(self, func: Callable) -> None: + """Internal implementation of unregister_syscall_hook.""" - Returns - ------- - bool - True if unregistered successfully, False otherwise. - """ if func not in self._func_to_hook_ptr: self.logger.error("Function not registered as a syscall hook") - return False + return + hook_ptr = self._func_to_hook_ptr[func] - # This is meant to mark the hook as disabled - if hook_ptr in self._hooks: - self._hooks[hook_ptr] = None - else: + if hook_ptr not in self._hooks: self.logger.error(f"Hook pointer 0x{hook_ptr:x} not found in internal hooks when attempting to disable") - return False - yield - - # Note that if called from within a syscall handler this will actually defer unregistration - retval = yield PortalCmd("unregister_syscall_hook", hook_ptr) - if retval != 1: - self.logger.error(f"Failed to unregister syscall hook 0x{hook_ptr:x} ({func}) with kffi") - # Even though this failed we will attempt to clean up internal state instead of bailing + return try: + self.logger.info("yielding unregister_syscall_hook") + retval = yield PortalCmd("unregister_syscall_hook", hook_ptr) + self.logger.info("yielded unregister_syscall_hook") + self._hooks.pop(hook_ptr, None) self._func_to_hook_ptr.pop(func, None) self._hook_info.pop(hook_ptr, None) self._hook_proto_cache.pop(hook_ptr, None) + + if retval != 0: + self.logger.warning(f"Kernel returned non-zero ({retval}) for unregister_syscall_hook, but proceeding with cleanup") + except Exception as e: - self.logger.error(f"Error cleaning up internal state for hook 0x{hook_ptr:x} ({func}): {e}") - return False - yield + self.logger.error(f"Error unregistering syscall hook 0x{hook_ptr:x} ({func}): {e}") + import traceback + self.logger.error(f"Traceback: {traceback.format_exc()}") - return retval == 0 + self.logger.info("_do_unregister_syscall_hook: END") diff --git a/pyplugins/testing/syscall_test.py b/pyplugins/testing/syscall_test.py index ab2eca62..25550ffa 100644 --- a/pyplugins/testing/syscall_test.py +++ b/pyplugins/testing/syscall_test.py @@ -26,6 +26,7 @@ def __init__(self): self.ioctl_ret2_num = 0 self.ioctl_ret3_num = 0 self.getpids = 0 # For unregister test + self.hook_unregistered = False syscalls.syscall("on_sys_ioctl_enter", comm_filter="send_syscall", arg_filters=[None, 0xabcd])(self.test_skip_retval) self.getpid_hook = syscalls.syscall("on_sys_getpid_return")(self.getpid) @@ -75,19 +76,17 @@ def ioctl_noret(self, regs, proto, syscall, fd, op, arg): f.write("Syscall ioctl_noret: failure\n") def getpid(self, regs, proto, syscall, *args): - # NOTE: We've removed this check because it was causing issues - # It doesn't seem to indicate anything negative so we're skipping it - # proc = self.panda.plugins['osi'].get_current_process(cpu) - # if syscall.retval != proc.pid and syscall.retval != 0: - # self.logger.error( - # f"Syscall test failed: getpid returned {syscall.retval:#x}, expected {proc.pid:#x}") - # self.success_getpid = False - # self.report_getpid() - # return if "send_syscall" in self.panda.get_process_name(self.panda.get_cpu()): self.success_getpid = True self.report_getpid() - yield from self.plugins.syscalls.unregister_syscall_hook(self.getpid_hook) + + if not self.hook_unregistered: + result = plugins.syscalls.unregister_syscall_hook(self.getpid) + + if not result: + self.logger.error("Failed to unregister getpid hook!") + + self.hook_unregistered = True self.getpids += 1 @syscalls.syscall("on_sys_clone_enter")