web-dev-qa-db-ja.com

現在のプロセスでネットワークの共有を解除する方法

unshare -r -nを使用して、ネットワークにアクセスせずに非ルートとして新しいコマンドを実行することができます。次に例を示します。

$ unshare -r -n ls
a.txt  b.txt

ネットワークアクセスを必要とするコマンドは、予想どおりに失敗します。

$ unshare -r -n curl unix.stackexchange.com
curl: (6) Could not resolve Host: unix.stackexchange.com

/sysなどの魔法のファイルに書き込むことで、現在のプロセスのネットワークアクセスを削除できるかどうか疑問に思っています。

こんなことができるようになりたい

$ /bin/sh -c 'echo 1 > /sys/unsharethis; curl unix.stackexchange.com'

strace- ing unshare -r -n lsからの抜粋は、unshareシステムコールを示しています

open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=4759040, ...}) = 0
mmap(NULL, 4759040, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7ec6968000
close(3)                                = 0
unshare(CLONE_NEWUSER|CLONE_NEWNET)     = 0
open("/proc/self/setgroups", O_WRONLY)  = 3
write(3, "deny", 4)                     = 4

これは、現在のプロセスからネットワークアクセスを共有解除することが、実際には共有解除を実現する唯一の方法であることを示唆しています(つまり、spawnまたはそれに相当するものに引数として渡すことはできません)。また、シェルがunshareのラッパーを公開するように特別に拡張されていない限り、シェルスクリプトからの共有解除は機能しないことも示唆しています。

3
Gregory Nisbet

これは、 gdb デバッガーを使用して、実行中のプロセスをアタッチできるかどうか(ダンプ可能な状態を変更するプログラム、またはsetgidなどを実行できないプログラム)で実行できます。ルートからでない限り、に接続されます)。達成可能な最善の方法でも、プロセスはルートになっていると見なされます(ただし、少なくとも実際のユーザーとして書き込むことができます)。

一部のオプションファイルは、libc6のデバッグシンボルのようなgdbの使用に役立ちます。また、Linux関連のいくつかのインクルードファイルを使用して、後でいくつかのシンボルの実際の値を取得できます(Debianの場合:(おそらく)_libc6-dbg_、_libc6-dev_および_linux-libc-dev_パッケージ)ですが、実際には「レシピ」が作成されると、おそらくもう必要ありません。

まず、 unshare() _unshare -r_以外に何が行われていますか?これがないと、新しいユーザーはnobodyに留まり、最初のユーザーとして書き込むことさえできません。

_$ 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
_

それは後で使用されます。

_$ 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
$
_

他の端末の場合:

_$ 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)
_

それでは、最初の関数を呼び出しましょう。

_(gdb) call unshare(CLONE_NEWNET|CLONE_NEWUSER)
No symbol "CLONE_NEWNET" in current context.
_

わかりました、gdbにそれを知らせる方法があるかもしれませんが、私は教祖ではありません:

_(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
_

変更されたプロセスで、_eth0_インターフェースが消えたことを確認できます。

_$ 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
_

戻ることはできません。新しいユーザー名前空間を最初の名前空間に戻すことはできません。プロセスが十分な特権で実行されている場合(たとえば、機能が失われていないrootやSELinux)、それは可能です(unshare(CLONE_NEWNET)/setns(savedopenedfd)のみを使用)。

もちろん、ファイルにスクリプトを記述して、許可されている実行中のプロセスを変更したり、シェルにgdbサブプロセスから自分自身を変更させたりすることもできます。 _removenetwork.gdb_の内容。ここでは、_pid:gid_ == _1000:1000_でプロセスを変更する場合にのみ有効です。

_call unshare(0x50000000)
call open("/proc/self/setgroups", 1)
call write($2,"deny",4)
call close($2)
call open("/proc/self/uid_map", 1)
call write($5, "0 1000 1", 8)
call close($5)
call open("/proc/self/gid_map", 1)
call write($8, "0 1000 1", 8)
call close($8)
quit

$ 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 [〜#〜]:この質問に表示されるように、rootがまったく必要ない場合は、必要はありません。ルートにマップします。 write($XX, "0 1000 1", 8)の出現をwrite($XX, "1000 1000 1", 11)に置き換えるだけです(_uid:gid_ == _1000:1000_の場合)。補足グループは依然として不可避的に失われますが、uid/gidは変更されません(それ自体にマップされます)。

2
A.B