Skip to content

Instantly share code, notes, and snippets.

@bacher09
Created February 17, 2026 11:01
Show Gist options
  • Select an option

  • Save bacher09/9bc92b98c592a8d94b01c3b3e2753a5b to your computer and use it in GitHub Desktop.

Select an option

Save bacher09/9bc92b98c592a8d94b01c3b3e2753a5b to your computer and use it in GitHub Desktop.
Calling listns syscall

Function shows calling new listns syscall (since 6.19 kernel) from Python.

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