非rootユーザーがUbuntuでchrootプロセスを実行することは可能ですか?
Linuxでは、 chroot(2) システムコールは特権を持つプロセスのみが実行できます。プロセスが必要とする機能はCAP_SYS_CHROOTです。
ユーザーとしてchrootできない理由は非常に簡単です。何かを許可されているかどうかを/ etc/sudoersでチェックするSudoなどのsetuidプログラムがあるとします。次に、独自の/ etc/sudoersを使用してchroot chrootに配置します。突然、特権のエスカレーションがすぐに発生します。
プログラムをそれ自体をchrootし、setuidプロセスとして実行するようにプログラムを設計することは可能ですが、これは一般に悪い設計と見なされます。 chrootの追加のセキュリティは、setuidのセキュリティ問題の動機にはなりません。
@ imz--IvanZakharyaschevは、名前空間の導入で可能になる可能性があるというpehrsの回答にコメントしていますが、これはテストされておらず、回答として投稿されていません。はい、それは確かに非rootユーザーがchrootを使用することを可能にします。
静的にリンクされたdash
、静的にリンクされたbusybox
、および非ルートとして実行されている実行中のbash
シェルがあるとします。
$ mkdir root
$ cp /path/to/dash root
$ cp /path/to/busybox root
$ unshare -r bash -c 'chroot root /dash -c "/busybox ls -al /"'
total 2700
drwxr-xr-x 2 0 0 4096 Dec 2 19:16 .
drwxr-xr-x 2 0 0 4096 Dec 2 19:16 ..
drwxr-xr-x 1 0 0 1905240 Dec 2 19:15 busybox
drwxr-xr-x 1 0 0 847704 Dec 2 19:15 dash
そのネームスペース内のルートユーザーIDは、そのネームスペース外の非ルートユーザーIDにマップされ、その逆も同様です。そのため、システムは、現在のユーザーが所有するファイルをユーザーID 0が所有するものとして表示します。通常のls -al root
は、unshare
なしで、現在のユーザーが所有しているものとして表示します。
注:chroot
を使用できるプロセスがchroot
から抜け出すことができることはよく知られています。 unshare -r
は、chroot
権限を通常のユーザーに付与します。これがchroot
環境内で許可されていると、セキュリティ上のリスクになります。実際、これは許可されておらず、次のエラーで失敗します。
unshare:unshare failed:操作は許可されていません
nshare(2) ドキュメントと一致します:
[〜#〜] eperm [〜#〜](Linux 3.9以降)
CLONE_NEWUSERがflagsで指定され、呼び出し元がchroot環境にある(つまり、呼び出し元のルートディレクトリ)が存在するマウント名前空間のルートディレクトリと一致しません)。
最近では、chroot/BSD jailの代わりにLXC(Linux Containers)を見たいと思っています。これはchrootと仮想マシンの間のどこかにあり、多くのセキュリティ制御と一般的な設定機能を提供します。ユーザーとして必要なのは、必要なファイル/デバイスを所有するグループのメンバーになることだけですが、機能/システム権限も関係している可能性があります。いずれにしても、LinuxカーネルにSELinuxなどが追加されてからかなり後のことですが、LXCはごく最近のことなので、それは非常に可能です。
また、スクリプトをrootとして作成するだけで、Sudoを使用してそれらのスクリプトを実行するための安全なアクセス許可をユーザーに与えることもできます(必要に応じて、パスワードなしで、スクリプトが安全であることを確認してください)。
Fakeroot/fakechrootの組み合わせは、ファイルがrootによって所有されているように見えるtarアーカイブを作成するなどの単純なニーズに対して、chrootのシミュレーションを提供します。 Fakechrootのマンページは http://linux.die.net/man/1/fakechroot です。
ただし、新しい権限は取得しませんが、呼び出す前にディレクトリ(fake-distroなど)を所有している場合
fakechroot fakeroot chroot ~/fake-distro some-command
これで、rootで、偽のディストリビューション内のすべてを所有しているようなコマンドを探します。
ユーザー名前空間を使用すると、実際にはrootなしでchrootを実行できるようです。以下は、それが可能であることを示すサンプルプログラムです。私はLinux名前空間がどのように機能するかを調査し始めたばかりなので、このコードがベストプラクティスであるかどうかは完全にはわかりません。
user_chroot.cc
として保存します。 g++ -o user_chroot user_chroot.cc
でコンパイルします。使用法は./user_chroot /path/to/new_rootfs
です。
// references:
// [1]: http://man7.org/linux/man-pages/man7/user_namespaces.7.html
// [2]: http://man7.org/linux/man-pages/man2/unshare.2.html
#include <sched.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
int main(int argc, char** argv) {
if(argc < 2) {
printf("Usage: %s <rootfs>\n", argv[0]);
}
int uid = getuid();
int gid = getgid();
printf("Before unshare, uid=%d, gid=%d\n", uid, gid);
// First, unshare the user namespace and assume admin capability in the
// new namespace
int err = unshare(CLONE_NEWUSER);
if(err) {
printf("Failed to unshare user namespace\n");
return 1;
}
// write a uid/gid map
char file_path_buf[100];
int pid = getpid();
printf("My pid: %d\n", pid);
sprintf(file_path_buf, "/proc/%d/uid_map", pid);
int fd = open(file_path_buf, O_WRONLY);
if(fd == -1) {
printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno,
strerror(errno));
} else {
printf("Writing : %s (fd=%d)\n", file_path_buf, fd);
err = dprintf(fd, "%d %d 1\n", uid, uid);
if(err == -1) {
printf("Failed to write contents [%d]: %s\n", errno,
strerror(errno));
}
close(fd);
}
sprintf(file_path_buf, "/proc/%d/setgroups", pid);
fd = open(file_path_buf, O_WRONLY);
if(fd == -1) {
printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno,
strerror(errno));
} else {
dprintf(fd, "deny\n");
close(fd);
}
sprintf(file_path_buf, "/proc/%d/gid_map", pid);
fd = open(file_path_buf, O_WRONLY);
if(fd == -1) {
printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno,
strerror(errno));
} else {
printf("Writing : %s (fd=%d)\n", file_path_buf, fd);
err = dprintf(fd, "%d %d 1\n", gid, gid);
if(err == -1) {
printf("Failed to write contents [%d]: %s\n", errno,
strerror(errno));
}
close(fd);
}
// Now chroot into the desired directory
err = chroot(argv[1]);
if(err) {
printf("Failed to chroot\n");
return 1;
}
// Now drop admin in our namespace
err = setresuid(uid, uid, uid);
if(err) {
printf("Failed to set uid\n");
}
err = setresgid(gid, gid, gid);
if(err) {
printf("Failed to set gid\n");
}
// and start a Shell
char argv0[] = "bash";
char* new_argv[] = {
argv0,
NULL
};
err = execvp("/bin/bash", new_argv);
if(err) {
perror("Failed to start Shell");
return -1;
}
}
私はこれをマルチストラップ(非ルートとして実行)で生成された最小限のrootfsでテストしました。 /etc/passwd
や/etc/groups
などのシステムファイルは、ホストrootfsからゲストrootfsにコピーされました。
いいえ。私が正しく思い出せば、chrootがカーネルレベルでそれを妨げていることがいくつかあります。そのことを思い出せません。 GentooのCatalyst Buildツールをいじくったとき、私はそれを調査し直しました(そしてgentooのchrootはubuntuのchrootと同じです)。 passwdなしでそれを実行することは可能ですが...そのようなことは、潜在的なセキュリティの脆弱性の領域に任されており、あなたが何をしているかを確実に知っています。