Function shows calling new listns syscall (since 6.19 kernel) from Python.
Created
February 17, 2026 11:01
-
-
Save bacher09/9bc92b98c592a8d94b01c3b3e2753a5b to your computer and use it in GitHub Desktop.
Calling listns syscall
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import array | |
| import ctypes | |
| import enum | |
| import inspect | |
| import mmap | |
| import sys | |
| libc = ctypes.cdll.LoadLibrary("libc.so.6") | |
| libc.syscall.restype = ctypes.c_long | |
| SYSCALL_WRITE = 1 | |
| # on all architectures except alpha (on alpha it's 580) it's 470 | |
| SYSCALL_LISTNS = 470 | |
| """ | |
| mov %rdi, %rax | |
| mov %rsi, %rdi | |
| mov %rdx, %rsi | |
| mov %rcx, %rdx | |
| mov %r8, %r10 | |
| mov %r9, %r8 | |
| syscall | |
| ret | |
| """ | |
| RAW_SYSCALL_CODE = b'H\x89\xf8H\x89\xf7H\x89\xd6H\x89\xcaM\x89\xc2M\x89\xc8\x0f\x05\xc3' | |
| """ | |
| Based on ns_type from kernel: | |
| enum ns_type { | |
| TIME_NS = (1ULL << 7), /* CLONE_NEWTIME */ | |
| MNT_NS = (1ULL << 17), /* CLONE_NEWNS */ | |
| CGROUP_NS = (1ULL << 25), /* CLONE_NEWCGROUP */ | |
| UTS_NS = (1ULL << 26), /* CLONE_NEWUTS */ | |
| IPC_NS = (1ULL << 27), /* CLONE_NEWIPC */ | |
| USER_NS = (1ULL << 28), /* CLONE_NEWUSER */ | |
| PID_NS = (1ULL << 29), /* CLONE_NEWPID */ | |
| NET_NS = (1ULL << 30), /* CLONE_NEWNET */ | |
| }; | |
| """ | |
| class NSTypes(enum.IntEnum): | |
| ALL = 0 | |
| MNT = 1 << 17 | |
| UTS = 1 << 26 | |
| IPS = 1 << 27 | |
| USER = 1 << 28 | |
| PID = 1 << 29 | |
| NET = 1 << 30 | |
| class _CNSIdRequest(ctypes.Structure): | |
| _fields_ = [ | |
| ("size", ctypes.c_uint32), | |
| ("spare", ctypes.c_uint32), | |
| ("ns_id", ctypes.c_uint64), | |
| ("ns_type", ctypes.c_uint32), | |
| ("spare2", ctypes.c_uint32), | |
| ("user_ns_id", ctypes.c_uint64), | |
| ] | |
| class NSIdRequest: | |
| _data_buf: _CNSIdRequest | |
| __slots__ = ['_data_buf'] | |
| def __init__(self, last_ns_id=0, ns_type=NSTypes.ALL, user_ns_id=0): | |
| self._data_buf = _CNSIdRequest() | |
| self._data_buf.size = ctypes.sizeof(_CNSIdRequest) | |
| # TODO: validate values | |
| self._data_buf.ns_id = last_ns_id | |
| self._data_buf.ns_type = ns_type | |
| self._data_buf.user_ns_id = user_ns_id | |
| @property | |
| def last_ns_id(self) -> int: | |
| return self._data_buf.ns_id | |
| @last_ns_id.setter | |
| def last_ns_id(self, new_value: int): | |
| self._data_buf.ns_id = new_value | |
| @property | |
| def ns_type(self) -> int: | |
| return self._data_buf.ns_type | |
| @ns_type.setter | |
| def ns_type(self, new_value: int): | |
| self._data_buf.ns_type = new_value | |
| def __buffer__(self, flags: int): | |
| if flags & inspect.BufferFlags.WRITABLE: | |
| raise BufferError("Writable buffer not supported") | |
| return memoryview(self._data_buf) | |
| def listns(req: NSIdRequest, max_ns: int=512): | |
| output_buf = (ctypes.c_uint64 * max_ns)() | |
| num_ns = libc.syscall(SYSCALL_LISTNS, ctypes.byref(req._data_buf), output_buf, max_ns, 0) | |
| if num_ns < 0: | |
| raise OSError(f"listns returned negative {num_ns} retval") | |
| return output_buf[0:num_ns] | |
| def listns_nolibc(req: NSIdRequest, max_ns: int=512): | |
| reg = mmap.mmap(0, mmap.PAGESIZE, flags=mmap.MAP_ANONYMOUS | mmap.MAP_PRIVATE, | |
| prot=mmap.PROT_READ | mmap.PROT_EXEC | mmap.PROT_WRITE) | |
| reg.write(RAW_SYSCALL_CODE) | |
| fun_addr = ctypes.addressof(ctypes.c_void_p.from_buffer(reg)) | |
| listns_syscall_type = ctypes.CFUNCTYPE( | |
| ctypes.c_long, | |
| ctypes.c_ulong, | |
| ctypes.POINTER(_CNSIdRequest), | |
| ctypes.POINTER(ctypes.c_uint64), | |
| ctypes.c_size_t, | |
| ctypes.c_uint | |
| ) | |
| syscall_fun = listns_syscall_type(fun_addr) | |
| output_buf = (ctypes.c_uint64 * max_ns)() | |
| num_ns = syscall_fun(SYSCALL_LISTNS, req._data_buf, output_buf, max_ns, 0) | |
| if num_ns < 0: | |
| raise OSError(f"listns returned negative {num_ns} retval") | |
| return output_buf[0:num_ns] | |
| def main(): | |
| req = NSIdRequest() | |
| req.ns_type = NSTypes.PID | |
| output = listns(req) | |
| print(output) | |
| print(req.last_ns_id) | |
| output = listns_nolibc(req) | |
| print(output) | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment