I am trying to create a new user namespace using a Python script that utilizes the clone
system call. My goal is to map the user ID of a newly spawned shell to a specific value, similar to how it's done with the unshare
command.
When I run unshare --user sh
, I can successfully update the UID mappings via:
echo '0 502 1' > /proc/[PID]/uid_map
echo '0 502 1' > /proc/[PID]/gid_map
After doing this, running id
in the new shell shows the user as root
.
However, when I attempt to do the same in a Python script using libc.clone
, the shell still reports the user ID as 65534 (nobody
) even after updating the UID and GID mappings. Here is my script:
import signal
import os
import ctypes
import sys
CLONE_NEWUTS = 0x04000000
CLONE_NEWUSER = 0x10000000
def write_file(path, content):
try:
with open(path, "w") as f:
f.write(content)
except Exception as e:
sys.exit(f"[Error] Failed to write mapping {content} to {path}: {e}")
def child_func():
input("...>")
os.execlp("bash", "sh") # Start a new shell
libc = ctypes.CDLL("libc.so.6", use_errno=True)
STACK_SIZE = 1024 * 1024
stack = ctypes.create_string_buffer(STACK_SIZE)
child_stack = ctypes.c_void_p(ctypes.addressof(stack) + STACK_SIZE)
pid = libc.clone(
ctypes.CFUNCTYPE(ctypes.c_int)(child_func),
child_stack,
CLONE_NEWUTS | CLONE_NEWUSER | signal.SIGCHLD,
)
if pid == -1:
sys.exit("Failed to create new namespace")
print(pid)
write_file(f"/proc/{pid}/setgroups", "deny\n")
write_file(f"/proc/{pid}/uid_map", f"0 502 1\n")
write_file(f"/proc/{pid}/gid_map", f"0 502 1\n")
os.waitpid(pid, 0)
What I have checked:
- The script is executed with root privileges.
- The UID and GID mapping files are updated after the
clone
call.
Despite these steps, the user inside the namespace remains nobody
. What might I be missing? Any advice or insights would be appreciated.
I am trying to create a new user namespace using a Python script that utilizes the clone
system call. My goal is to map the user ID of a newly spawned shell to a specific value, similar to how it's done with the unshare
command.
When I run unshare --user sh
, I can successfully update the UID mappings via:
echo '0 502 1' > /proc/[PID]/uid_map
echo '0 502 1' > /proc/[PID]/gid_map
After doing this, running id
in the new shell shows the user as root
.
However, when I attempt to do the same in a Python script using libc.clone
, the shell still reports the user ID as 65534 (nobody
) even after updating the UID and GID mappings. Here is my script:
import signal
import os
import ctypes
import sys
CLONE_NEWUTS = 0x04000000
CLONE_NEWUSER = 0x10000000
def write_file(path, content):
try:
with open(path, "w") as f:
f.write(content)
except Exception as e:
sys.exit(f"[Error] Failed to write mapping {content} to {path}: {e}")
def child_func():
input("...>")
os.execlp("bash", "sh") # Start a new shell
libc = ctypes.CDLL("libc.so.6", use_errno=True)
STACK_SIZE = 1024 * 1024
stack = ctypes.create_string_buffer(STACK_SIZE)
child_stack = ctypes.c_void_p(ctypes.addressof(stack) + STACK_SIZE)
pid = libc.clone(
ctypes.CFUNCTYPE(ctypes.c_int)(child_func),
child_stack,
CLONE_NEWUTS | CLONE_NEWUSER | signal.SIGCHLD,
)
if pid == -1:
sys.exit("Failed to create new namespace")
print(pid)
write_file(f"/proc/{pid}/setgroups", "deny\n")
write_file(f"/proc/{pid}/uid_map", f"0 502 1\n")
write_file(f"/proc/{pid}/gid_map", f"0 502 1\n")
os.waitpid(pid, 0)
What I have checked:
- The script is executed with root privileges.
- The UID and GID mapping files are updated after the
clone
call.
Despite these steps, the user inside the namespace remains nobody
. What might I be missing? Any advice or insights would be appreciated.
1 Answer
Reset to default 0The issue you're encountering lies in the timing of when the uid_map
and gid_map
are written in relation to the child process and its execution within the new user namespace. In your script, the mapping files are written after the clone system call is made, which means the new child process isn't aware of those mappings at the time it starts.
To solve this issue, the uid_map
and gid_map
should be written before the child process is executed inside the new user namespace. This ensures that the mappings are already set up by the time the child process is started.