@@ -424,8 +424,7 @@ def __init__(
424424 # Deterministic time
425425 self ._time_offset = None # TimeOffset | None
426426 self ._mono_offset_s : int = 0 # monotonic offset for vDSO stubs
427- self ._vdso_patch_fd : int = - 1 # pre-opened /proc/pid/mem
428- self ._vdso_patch_writes : list [tuple [int , bytes ]] = [] # (offset, stub)
427+ self ._vdso_patched_addr : int = 0 # vDSO base address we last patched
429428 self ._virtual_btime : int = 0 # virtual boot time for /proc/stat
430429 if policy .time_start is not None :
431430 import time as _time
@@ -556,6 +555,12 @@ def _handle_one(self) -> None:
556555 if ret < 0 :
557556 return # ENOENT = child died, EINTR = interrupted
558557
558+ # Patch vDSO before dispatching (child is stopped in seccomp
559+ # notification state, so /proc/pid/mem writes are reliable).
560+ # Re-patch when the vDSO address changes (exec replaces the vDSO).
561+ if self ._time_offset is not None :
562+ self ._maybe_patch_vdso (notif .pid )
563+
559564 try :
560565 self ._dispatch (notif )
561566 except Exception :
@@ -566,53 +571,45 @@ def _handle_one(self) -> None:
566571 except Exception :
567572 pass
568573
569- # Post-dispatch: patch vDSO for new PIDs (after exec).
570- # Two-phase approach:
571- # Phase 1 (this notification): pre-compute — open fd, parse
572- # vDSO ELF, compute offsets. This is slow but the child
573- # will be unfrozen when dispatch responds above.
574- # Phase 2 (next notification): minimal lseek+write using the
575- # pre-computed fd and offsets. Fast enough to land while
576- # the child is briefly running after the previous response.
577- if self ._vdso_patch_writes :
578- # Phase 2: fast write (child briefly running after respond)
579- fd = self ._vdso_patch_fd
580- for off , stub in self ._vdso_patch_writes :
581- os .lseek (fd , off , os .SEEK_SET )
582- os .write (fd , stub )
583- os .close (fd )
584- self ._vdso_patch_fd = - 1
585- self ._vdso_patch_writes = []
586- elif self ._time_offset is not None and self ._vdso_patch_fd == - 1 :
587- # Phase 1: pre-compute (first notification from new PID)
588- pid = notif .pid
589- from ._vdso import _find_vdso , _parse_vdso_symbols , _get_stubs
590- info = _find_vdso (pid )
591- stubs = _get_stubs (self ._mono_offset_s )
592- if info and stubs :
593- addr , size = info
594- try :
595- fd = os .open (f"/proc/{ pid } /mem" , os .O_RDWR )
596- os .lseek (fd , addr , os .SEEK_SET )
597- data = os .read (fd , size )
598- writes = []
599- for name , off in _parse_vdso_symbols (data ):
600- stub = stubs .get (name )
601- if stub :
602- writes .append ((addr + off , stub ))
603- if writes :
604- self ._vdso_patch_fd = fd
605- self ._vdso_patch_writes = writes
606- else :
607- os .close (fd )
608- except OSError :
609- pass
610-
611574 @property
612575 def tracked_pids (self ) -> set [int ]:
613576 """All PIDs known to belong to this sandbox."""
614577 return set (self ._proc_pids )
615578
579+ def _maybe_patch_vdso (self , pid : int ) -> None :
580+ """Patch the child's vDSO to force real syscalls, if needed.
581+
582+ Called while the child is stopped in seccomp notification state,
583+ so /proc/pid/mem writes land reliably before the child resumes.
584+ Tracks the vDSO base address to detect exec (which replaces the
585+ vDSO at a new address) and re-patch automatically.
586+ """
587+ from ._vdso import _find_vdso , _parse_vdso_symbols , _get_stubs
588+ info = _find_vdso (pid )
589+ if not info :
590+ return
591+ addr , size = info
592+ if addr == self ._vdso_patched_addr :
593+ return # already patched this vDSO
594+ stubs = _get_stubs (self ._mono_offset_s )
595+ if not stubs :
596+ return
597+ try :
598+ fd = os .open (f"/proc/{ pid } /mem" , os .O_RDWR )
599+ try :
600+ os .lseek (fd , addr , os .SEEK_SET )
601+ data = os .read (fd , size )
602+ for name , off in _parse_vdso_symbols (data ):
603+ stub = stubs .get (name )
604+ if stub :
605+ os .lseek (fd , addr + off , os .SEEK_SET )
606+ os .write (fd , stub )
607+ self ._vdso_patched_addr = addr
608+ finally :
609+ os .close (fd )
610+ except OSError :
611+ pass
612+
616613 def _dispatch (self , notif : SeccompNotif ) -> None :
617614 """Route a notification to the appropriate handler."""
618615 # Lazily track every PID that makes an intercepted syscall
0 commit comments