This can be done, sort of, with the gdb
debugger, and if the running process can be attached to (programs that alter their dumpable state, or are setgid etc. can't be attached to, unless from root).
Some optional files can help to use gdb like debug symbols for libc6, and a few Linux related include files to get the actual values of a few symbols later (eg on Debian: (possibly) libc6-dbg
, libc6-dev
and linux-libc-dev
packages), but actually once the "recipe" is made, they're probably not needed anymore.
First what more than unshare()
unshare -r
is doing? Without this, the new user stays at nobody
and can't even write as its initial user:
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ strace unshare -r -n /bin/sleep 1 2>&1 |sed -n '/^unshare/,/^execve/p'
unshare(CLONE_NEWNET|CLONE_NEWUSER) = 0
open("/proc/self/setgroups", O_WRONLY) = 3
write(3, "deny", 4) = 4
close(3) = 0
open("/proc/self/uid_map", O_WRONLY) = 3
write(3, "0 1000 1", 8) = 8
close(3) = 0
open("/proc/self/gid_map", O_WRONLY) = 3
write(3, "0 1000 1", 8) = 8
close(3) = 0
execve("/bin/sleep", ["/bin/sleep", "1"], [/* 18 vars */]) = 0
That will be used later.
$ ip -4 -br a
lo UNKNOWN 127.0.0.1/8
eth0@if19 UP 10.0.3.66/24
$ ping -c1 10.0.3.1
PING 10.0.3.1 (10.0.3.1) 56(84) bytes of data.
64 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=0.167 ms
--- 10.0.3.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.167/0.167/0.167/0.000 ms
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ echo $$
338
$
On an other terminal:
$ gdb --pid=338
Reading symbols from /bin/bash...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libtinfo.so.5...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libdl.so.2...Reading symbols from /usr/lib/debug/.build-id/b8/95f0831f623c5f23603401d4069f9f94c24761.debug...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug/.build-id/aa/889e26a70f98fa8d230d088f7cc5bf43573163.debug...done.
done.
[...]
(gdb)
Now let's call the first function:
(gdb) call unshare(CLONE_NEWNET|CLONE_NEWUSER)
No symbol "CLONE_NEWNET" in current context.
Ok, there might be a method to have gdb know it, but I'm not a guru:
(gdb) !
$ grep CLONE_NEW /usr/include/linux/sched.h # man 2 unshare
#define CLONE_NEWNS 0x00020000 /* New mount namespace group */
#define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */
#define CLONE_NEWUTS 0x04000000 /* New utsname namespace */
#define CLONE_NEWIPC 0x08000000 /* New ipc namespace */
#define CLONE_NEWUSER 0x10000000 /* New user namespace */
#define CLONE_NEWPID 0x20000000 /* New pid namespace */
#define CLONE_NEWNET 0x40000000 /* New network namespace */
$ find /usr/include/ -name fcntl.h |xargs grep O_WRONLY # man 2 open
/usr/include/asm-generic/fcntl.h:#define O_WRONLY 00000001
$ exit
exit
(gdb) call unshare(0x50000000)
$1 = 0
(gdb) call open("/proc/self/setgroups", 1)
$2 = 3
(gdb) call write($2,"deny",4)
$3 = 4
(gdb) call close($2)
$4 = 0
(gdb) call open("/proc/self/uid_map", 1)
$5 = 3
(gdb) call write($5, "0 1000 1", 8)
$6 = 8
(gdb) call close($5)
$7 = 0
(gdb) call open("/proc/self/gid_map", 1)
$8 = 3
(gdb) call write($8, "0 1000 1", 8)
$9 = 8
(gdb) call close($8)
$10 = 0
(gdb) quit
A debugging session is active.
Inferior 1 [process 338] will be detached.
Quit anyway? (y or n) y
Detaching from program: /bin/bash, process 338
On the altered process, one can verify the eth0
interface disappeared:
$ ip -br a
lo DOWN 127.0.0.1/8
$ echo $$
338
$ id
uid=0(root) gid=0(root) groupes=0(root)
$ touch /
touch: setting times of '/': Permission denied
$ touch ~/test1
$ ls ~/test1
/home/user/test1
$ ping 10.0.3.1
connect: Network is unreachable
There's no going back: the new user namespace can't change back to its initial namespace. If the process is running with enough privileges (eg root without lost capabilities nor SELinux) then it would be possible (using only unshare(CLONE_NEWNET)
/ setns(savedopenedfd)
).
Of course it's possible to script it in a file, and alter any allowed running process, or have the shell alter itself from a gdb subprocess. Contents of removenetwork.gdb
, valid here only to alter a process with pid:gid
== 1000:1000
:
UPDATE: added the (approximate) return type for the syscalls below, this should avoid some versions of gdb to complain in non-dev environments:
call (int)unshare(0x50000000)
call (int)open("/proc/self/setgroups", 1)
call (long)write($2,"deny",4)
call (int)close($2)
call (int)open("/proc/self/uid_map", 1)
call (long)write($5, "0 1000 1", 8)
call (int)close($5)
call (int)open("/proc/self/gid_map", 1)
call (long)write($8, "0 1000 1", 8)
call (int)close($8)
quit
Example:
$ sh -c 'id; gdb --pid=$$ < removenetwork.gdb >/dev/null 2>&1; id; curl unix.stackexchange.com'
uid=1000(user) gid=1000(user) groups=1000(user)
uid=0(root) gid=0(root) groups=0(root)
curl: (6) Could not resolve host: unix.stackexchange.com
UPDATE: if root is not needed at all, as it appears for this Question, then there's no need to map to root at all. Simply replace occurences of write($XX, "0 1000 1", 8)
with write($XX, "1000 1000 1", 11)
(for the uid:gid
== 1000:1000
case). Supplementary groups are still unavoidably lost, but the uid/gid doesn't change (is mapped to itself).
gdb
to insert a system call. – Greg Nisbet Jul 20 '18 at 01:54unshare -r -m
I didn't see that for this specific answer, there's no root needed at all. So the user can stay the same actually (but still loses supplementary groups). Will edit answer – A.B Jul 20 '18 at 12:08