profs の hidepid
オプションの値
procfs のマウントオプションに hidepid
というオプションがあることを知った。
man
で確認すると 0~3 の値を取るらしい。
hidepid=n (since Linux 3.3)
This option controls who can access the information in /proc/[pid] directories. The argument, n, is one of the following values:
0 Everybody may access all /proc/[pid] directories. This is the traditional behavior, and the default if this mount option is not specified.
1 Users may not access files and subdirectories inside any /proc/[pid] directories but their own (the /proc/[pid] directories themselves remain visible). Sensitive files such as
/proc/[pid]/cmdline and /proc/[pid]/status are now protected against other users. This makes it impossible to learn whether any user is running a specific program (so long as the program
doesn't otherwise reveal itself by its behavior).
2 As for mode 1, but in addition the /proc/[pid] directories belonging to other users become invisible. This means that /proc/[pid] entries can no longer be used to discover the PIDs on the
system. This doesn't hide the fact that a process with a specific PID value exists (it can be learned by other means, for example, by "kill -0 $PID"), but it hides a process's UID and GID,
which could otherwise be learned by employing stat(2) on a /proc/[pid] directory. This greatly complicates an attacker's task of gathering information about running processes (e.g., discover‐
ing whether some daemon is running with elevated privileges, whether another user is running some sensitive program, whether other users are running any program at all, and so on).
/proc/[pid]
ディレクトリへのアクセスを制御するオプション0
の場合は誰でもアクセスできる。これはデフォルトの状態。1
の場合は自分のディレクトリ以外の/proc/[pid]
内のファイルやサブディレクトリにアクセスできない。/proc/[pid]
自体は表示される。つまり、/proc/[pid]/cmdline
などが他のユーザーから確認できなくなる。2
は1
に加えて/proc/[pid]
ディレクトリ自体も見えなくなる。これにより PID を検出することもできなくなる。ただしkill -0 $PID
などで存在を確認することはできるので、完全な隠蔽ではない。
man には書かれていないが実はカーネル v5.8 からは 4
という値があって、ptrace 可能な PID のみを返すようにできるらしい。なぜ3を飛ばして4なのか…(歴史的経緯は追っていない)。
また、subset=pid
オプションも追加されていて、PID以外のディレクトリやファイル(例えば /proc/sysrq-trigger
など)も隠せるようになっている。コンテナ環境に /proc
をそのままマウントしてしまうと様々な Attack Surafaces を生み出してしまうが、これで安全にマウントできて便利っぽい。これについては後述する。
hidepid を設定した場合の挙動の確認
デフォルト(つまり hidepid=0
)では普通に別ユーザーのプロセスが確認できる。
ubuntu@sandbox:~$ ps aux | head -n 3
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 225720 9472 ? Ss Aug19 0:07 /sbin/init
root 2 0.0 0.0 0 0 ? S Aug19 0:00 [kthreadd]
hidepid=1
で procfs をマウントすると別ユーザーのプロセスは確認できない。
ubuntu@sandbox:~$ sudo mount /proc -o remount,hidepid=1
ubuntu@sandbox:~$ ps aux | head -n 3
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 1549 0.0 0.1 76820 7704 ? Ss Aug19 0:00 /lib/systemd/systemd --user
ubuntu 27136 0.0 0.1 23260 5376 pts/1 Ss 16:33 0:00 bash
/proc
を確認すると PID 自体は見えるが、その配下のファイルやディレクトリは確認できない。
ubuntu@sandbox:~$ ls -l /proc/ | head
total 0
dr-xr-xr-x 9 root root 0 Aug 14 14:45 1
dr-xr-xr-x 9 root root 0 Aug 22 17:06 10
dr-xr-xr-x 9 root root 0 Aug 22 17:06 1090
dr-xr-xr-x 9 daemon daemon 0 Aug 22 17:06 1093
dr-xr-xr-x 9 root root 0 Aug 22 17:06 1098
dr-xr-xr-x 9 root root 0 Aug 22 17:06 1099
dr-xr-xr-x 9 root root 0 Aug 22 17:06 11
dr-xr-xr-x 9 root root 0 Aug 22 17:06 1106
dr-xr-xr-x 9 messagebus messagebus 0 Aug 22 17:06 1108
ubuntu@sandbox:~$ ls /proc/1
ls: cannot open directory '/proc/1': Operation not permitted
続いて hidepid=2
でマウントしてみる。man にあるように自分のプロセスの PID の以外は見えなくなっている。
ubuntu@sandbox:~$ sudo mount /proc -o remount,hidepid=2
ubuntu@sandbox:~$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 1549 0.0 0.1 76820 7704 ? Ss Aug19 0:00 /lib/systemd/systemd --user
ubuntu 27136 0.0 0.1 23260 5376 pts/1 Ss 16:33 0:00 bash
ubuntu 27287 0.0 0.0 37796 3236 pts/1 R+ 17:08 0:00 ps aux
ubuntu@sandbox:~$ ls -l /proc/ | head
total 0
dr-xr-xr-x 9 ubuntu ubuntu 0 Aug 14 14:45 1549
dr-xr-xr-x 9 ubuntu ubuntu 0 Aug 22 17:08 27136
dr-xr-xr-x 9 ubuntu ubuntu 0 Aug 22 17:09 27292
dr-xr-xr-x 9 ubuntu ubuntu 0 Aug 22 17:09 27293
dr-xr-xr-x 2 root root 0 Aug 14 19:04 acpi
-r--r--r-- 1 root root 0 Aug 22 17:09 buddyinfo
dr-xr-xr-x 4 root root 0 Aug 14 19:04 bus
-r--r--r-- 1 root root 0 Aug 22 17:09 cgroups
-r--r--r-- 1 root root 0 Aug 22 17:09 cmdline
ただし、kill -0 $PID
でプロセスが存在していることは確認できる。
ubuntu@sandbox:~$ kill -0 1
bash: kill: (1) - Operation not permitted
ubuntu@sandbox:~$ kill -0 2
bash: kill: (2) - Operation not permitted
ubuntu@sandbox:~$ kill -0 123456
bash: kill: (123456) - No such process
subset=pid
で PID 以外のファイルも隠す
hidepid
オプションとは異なるが、確認したので一応メモ。前述した通り、カーネルv5.8以降で subset=pid
というオプションが追加されている。
ssm-user@ip-10-0-1-113:~$ sudo mount /proc -o remount,hidepid=2,subset=pid
ssm-user@ip-10-0-1-113:~$ ls /proc/
1073 960 992 self thread-self
ssm-user@ip-10-0-1-113:~$ cat /proc/uptime
cat: /proc/uptime: No such file or directory
ssm-user@ip-10-0-1-113:~$ echo c > /proc/sysrq-trigger
bash: /proc/sysra-trigger: No such file or directory
コンテナを作るときはセキュリティ上の問題があるので特定のファイルを ReadOnly にしたり bind mount したりする必要があるが、このオプションが代替となっていきそうだ。
実装を見てみる
カーネルのコード的にはどのような実装になっているか確認してみる(5.8.3でみています)。
inode_oprations.permission
のコールバック関数 proc_pid_permission()
という関数でチェックをしている。
static int proc_pid_permission(struct inode *inode, int mask)
{
struct proc_fs_info *fs_info = proc_sb_info(inode->i_sb);
struct task_struct *task;
bool has_perms;
task = get_proc_task(inode);
if (!task)
return -ESRCH; has_perms = has_pid_permissions(fs_info, task, HIDEPID_NO_ACCESS);
put_task_struct(task);
if (!has_perms) {
if (fs_info->hide_pid == HIDEPID_INVISIBLE) {
/*
* Let's make getdents(), stat(), and open()
* consistent with each other. If a process
* may not stat() a file, it shouldn't be seen
* in procfs at all.
*/
return -ENOENT;
}
return -EPERM;
}
return generic_permission(inode, mask);
}
この関数では has_pid_permissions()
を呼び出している。
has_perms = has_pid_permissions(fs_info, task, HIDEPID_NO_ACCESS);
/*
* May current process learn task's sched/cmdline info (for hide_pid_min=1)
* or euid/egid (for hide_pid_min=2)?
*/
static bool has_pid_permissions(struct proc_fs_info *fs_info,
struct task_struct *task,
enum proc_hidepid hide_pid_min)
{
/*
* If 'hidpid' mount option is set force a ptrace check,
* we indicate that we are using a filesystem syscall
* by passing PTRACE_MODE_READ_FSCREDS
*/
if (fs_info->hide_pid == HIDEPID_NOT_PTRACEABLE)
return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
if (fs_info->hide_pid < hide_pid_min)
return true;
if (in_group_p(fs_info->pid_gid))
return true;
return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
}
hidepid
の値の定義はこれ。
/* definitions for hide_pid field */
enum proc_hidepid {
HIDEPID_OFF = 0,
HIDEPID_NO_ACCESS = 1,
HIDEPID_INVISIBLE = 2,
HIDEPID_NOT_PTRACEABLE = 4, /* Limit pids to only ptraceable pids */
};
HIDEPID_NOT_PTRACEABLE
は前述した v5.8 から入っている値。ptrace 可能な PID のみにアクセスできる。
if (fs_info->hide_pid < hide_pid_min)
のところを見てみる。if (fs_info->hide_pid < HIDEPID_NO_ACCESS)
なので1より小さければ(つまり0であるかをみている) true
になる。
has_perms
が false
のときには hidepid
の値によって返ってくるエラーが異なる。
if (!has_perms) {
if (fs_info->hide_pid == HIDEPID_INVISIBLE) {
/*
* Let's make getdents(), stat(), and open()
* consistent with each other. If a process
* may not stat() a file, it shouldn't be seen
* in procfs at all.
*/
return -ENOENT;
}
return -EPERM;
}
このように hidepid=2
のときは -ENOENT
が返るが、hidepid=1
の場合は -EPERM
が返る。
これは hidepid=1
が PID 自体は見えるのに対して hidepid=2
の場合は PID 自体も見えなくなるのと一致している。
ubuntu@sandbox:~$ sudo mount /proc -o remount,hidepid=1
ubuntu@sandbox:~$ stat /proc/1/cmdline
stat: cannot stat '/proc/1/cmdline': Operation not permitted
ubuntu@sandbox:~$ sudo mount /proc -o remount,hidepid=2
ubuntu@sandbox:~$ stat /proc/1/cmdline
stat: cannot stat '/proc/1/cmdline': No such file or directory
ちなみに ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)
で hidepid=1,2
のときのアクセスチェックをしているが、 PTRACE_MODE_READ_FSCREDS
は PTRACE_MODE_READ | PTRACE_MODE_FSCREDS
を指す。
つまり /proc/[pid]/stat
などのファイルへの読み込み権限と正しいUID/GIDを持っているかのチェックかな。ptrace API 便利。
has_pid_permissions()
は proc_pid_readdir()
や pid_getattr()
などでも呼び出されており、これらも file_operations
や inode_oprations
のコールバック関数として使用されているようだ。
References
- https://wiki.bit-hive.com/north/pg/proc%E3%81%AEhidepid%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3
- https://elixir.bootlin.com/linux/v5.8.3/source/fs/proc/base.c
- https://www.kernel.org/doc/html/latest/filesystems/locking.html
- https://man7.org/linux/man-pages/man2/ptrace.2.html
- https://man7.org/linux/man-pages//man5/procfs.5.html