#!/usr/bin/env python3 import ctypes import ctypes.util import os import subprocess import sys CLONE_NEWUSER = 0x10000000 CLONE_NEWNET = 0x40000000 _lib = ctypes.util.find_library("c") if not _lib: sys.exit("libc not found") libc = ctypes.CDLL(_lib, use_errno=True) libc.unshare.argtypes = (ctypes.c_int,) libc.unshare.restype = ctypes.c_int def unshare(flags): if libc.unshare(ctypes.c_int(flags)) != 0: sys.exit(f"unshare: {os.strerror(ctypes.get_errno())}") def write_proc(path, data): with open(path, "w", encoding="ascii") as f: f.write(data) def parent_maps(pid, uid, gid): p = f"/proc/{pid}" write_proc(f"{p}/uid_map", f"0 {uid} 1\n") write_proc(f"{p}/setgroups", "deny\n") write_proc(f"{p}/gid_map", f"0 {gid} 1\n") def repl(): while True: try: line = input("ns> ") except EOFError: print() break cmd = line.strip() if not cmd: continue if cmd.lower() in ("exit", "quit"): break try: subprocess.run(cmd, shell=True, executable="/bin/sh") except OSError as e: print(e, file=sys.stderr) def main(): if not sys.platform.startswith("linux") or not hasattr(os, "fork"): sys.exit("need Linux + fork") uid, gid = os.getuid(), os.getgid() r1, w1 = os.pipe() r2, w2 = os.pipe() try: pid = os.fork() except OSError as e: for fd in (r1, w1, r2, w2): try: os.close(fd) except OSError: pass sys.exit(f"fork: {e}") if pid == 0: os.close(r1) os.close(w2) unshare(CLONE_NEWUSER) os.write(w1, b"!") os.close(w1) if os.read(r2, 1) != b"!": os._exit(1) os.close(r2) unshare(CLONE_NEWNET) subprocess.run( ["ip", "link", "set", "lo", "up"], capture_output=True, check=False, ) subprocess.run( ["ip", "addr", "add", "127.0.0.1/8", "dev", "lo"], capture_output=True, check=False, ) repl() os._exit(0) os.close(w1) os.close(r2) if os.read(r1, 1) != b"!": os.close(w2) os.waitpid(pid, 0) sys.exit(1) os.close(r1) try: parent_maps(pid, uid, gid) except OSError as e: os.close(w2) os.waitpid(pid, 0) sys.exit(f"maps: {e}") os.write(w2, b"!") os.close(w2) os.waitpid(pid, 0) if __name__ == "__main__": main()