Skip to content

Commit 0499680

Browse files
segoontorvalds
authored andcommitted
procfs: add hidepid= and gid= mount options
Add support for mount options to restrict access to /proc/PID/ directories. The default backward-compatible "relaxed" behaviour is left untouched. The first mount option is called "hidepid" and its value defines how much info about processes we want to be available for non-owners: hidepid=0 (default) means the old behavior - anybody may read all world-readable /proc/PID/* files. hidepid=1 means users may not access any /proc/<pid>/ directories, but their own. Sensitive files like cmdline, sched*, status are now protected against other users. As permission checking done in proc_pid_permission() and files' permissions are left untouched, programs expecting specific files' modes are not confused. hidepid=2 means hidepid=1 plus all /proc/PID/ will be invisible to other users. It doesn't mean that it hides whether a process exists (it can be learned by other means, e.g. by kill -0 $PID), but it hides process' euid and egid. It compicates intruder's task of gathering info about running processes, whether some daemon runs with elevated privileges, whether another user runs some sensitive program, whether other users run any program at all, etc. gid=XXX defines a group that will be able to gather all processes' info (as in hidepid=0 mode). This group should be used instead of putting nonroot user in sudoers file or something. However, untrusted users (like daemons, etc.) which are not supposed to monitor the tasks in the whole system should not be added to the group. hidepid=1 or higher is designed to restrict access to procfs files, which might reveal some sensitive private information like precise keystrokes timings: http://www.openwall.com/lists/oss-security/2011/11/05/3 hidepid=1/2 doesn't break monitoring userspace tools. ps, top, pgrep, and conky gracefully handle EPERM/ENOENT and behave as if the current user is the only user running processes. pstree shows the process subtree which contains "pstree" process. Note: the patch doesn't deal with setuid/setgid issues of keeping preopened descriptors of procfs files (like https://lkml.org/lkml/2011/2/7/368). We rely on that the leaked information like the scheduling counters of setuid apps doesn't threaten anybody's privacy - only the user started the setuid program may read the counters. Signed-off-by: Vasiliy Kulikov <segoon@openwall.com> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Randy Dunlap <rdunlap@xenotime.net> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Greg KH <greg@kroah.com> Cc: Theodore Tso <tytso@MIT.EDU> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: James Morris <jmorris@namei.org> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Hugh Dickins <hughd@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 9741295 commit 0499680

File tree

5 files changed

+135
-4
lines changed

5 files changed

+135
-4
lines changed

Documentation/filesystems/proc.txt

+39
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Table of Contents
4141
3.5 /proc/<pid>/mountinfo - Information about mounts
4242
3.6 /proc/<pid>/comm & /proc/<pid>/task/<tid>/comm
4343

44+
4 Configuring procfs
45+
4.1 Mount options
4446

4547
------------------------------------------------------------------------------
4648
Preface
@@ -1542,3 +1544,40 @@ a task to set its own or one of its thread siblings comm value. The comm value
15421544
is limited in size compared to the cmdline value, so writing anything longer
15431545
then the kernel's TASK_COMM_LEN (currently 16 chars) will result in a truncated
15441546
comm value.
1547+
1548+
1549+
------------------------------------------------------------------------------
1550+
Configuring procfs
1551+
------------------------------------------------------------------------------
1552+
1553+
4.1 Mount options
1554+
---------------------
1555+
1556+
The following mount options are supported:
1557+
1558+
hidepid= Set /proc/<pid>/ access mode.
1559+
gid= Set the group authorized to learn processes information.
1560+
1561+
hidepid=0 means classic mode - everybody may access all /proc/<pid>/ directories
1562+
(default).
1563+
1564+
hidepid=1 means users may not access any /proc/<pid>/ directories but their
1565+
own. Sensitive files like cmdline, sched*, status are now protected against
1566+
other users. This makes it impossible to learn whether any user runs
1567+
specific program (given the program doesn't reveal itself by its behaviour).
1568+
As an additional bonus, as /proc/<pid>/cmdline is unaccessible for other users,
1569+
poorly written programs passing sensitive information via program arguments are
1570+
now protected against local eavesdroppers.
1571+
1572+
hidepid=2 means hidepid=1 plus all /proc/<pid>/ will be fully invisible to other
1573+
users. It doesn't mean that it hides a fact whether a process with a specific
1574+
pid value exists (it can be learned by other means, e.g. by "kill -0 $PID"),
1575+
but it hides process' uid and gid, which may be learned by stat()'ing
1576+
/proc/<pid>/ otherwise. It greatly complicates an intruder's task of gathering
1577+
information about running processes, whether some daemon runs with elevated
1578+
privileges, whether other user runs some sensitive program, whether other users
1579+
run any program at all, etc.
1580+
1581+
gid= defines a group authorized to learn processes information otherwise
1582+
prohibited by hidepid=. If you use some daemon like identd which needs to learn
1583+
information about processes information, just add identd to this group.

fs/proc/base.c

+68-1
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,50 @@ int proc_setattr(struct dentry *dentry, struct iattr *attr)
631631
return 0;
632632
}
633633

634+
/*
635+
* May current process learn task's sched/cmdline info (for hide_pid_min=1)
636+
* or euid/egid (for hide_pid_min=2)?
637+
*/
638+
static bool has_pid_permissions(struct pid_namespace *pid,
639+
struct task_struct *task,
640+
int hide_pid_min)
641+
{
642+
if (pid->hide_pid < hide_pid_min)
643+
return true;
644+
if (in_group_p(pid->pid_gid))
645+
return true;
646+
return ptrace_may_access(task, PTRACE_MODE_READ);
647+
}
648+
649+
650+
static int proc_pid_permission(struct inode *inode, int mask)
651+
{
652+
struct pid_namespace *pid = inode->i_sb->s_fs_info;
653+
struct task_struct *task;
654+
bool has_perms;
655+
656+
task = get_proc_task(inode);
657+
has_perms = has_pid_permissions(pid, task, 1);
658+
put_task_struct(task);
659+
660+
if (!has_perms) {
661+
if (pid->hide_pid == 2) {
662+
/*
663+
* Let's make getdents(), stat(), and open()
664+
* consistent with each other. If a process
665+
* may not stat() a file, it shouldn't be seen
666+
* in procfs at all.
667+
*/
668+
return -ENOENT;
669+
}
670+
671+
return -EPERM;
672+
}
673+
return generic_permission(inode, mask);
674+
}
675+
676+
677+
634678
static const struct inode_operations proc_def_inode_operations = {
635679
.setattr = proc_setattr,
636680
};
@@ -1615,6 +1659,7 @@ int pid_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
16151659
struct inode *inode = dentry->d_inode;
16161660
struct task_struct *task;
16171661
const struct cred *cred;
1662+
struct pid_namespace *pid = dentry->d_sb->s_fs_info;
16181663

16191664
generic_fillattr(inode, stat);
16201665

@@ -1623,6 +1668,14 @@ int pid_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
16231668
stat->gid = 0;
16241669
task = pid_task(proc_pid(inode), PIDTYPE_PID);
16251670
if (task) {
1671+
if (!has_pid_permissions(pid, task, 2)) {
1672+
rcu_read_unlock();
1673+
/*
1674+
* This doesn't prevent learning whether PID exists,
1675+
* it only makes getattr() consistent with readdir().
1676+
*/
1677+
return -ENOENT;
1678+
}
16261679
if ((inode->i_mode == (S_IFDIR|S_IRUGO|S_IXUGO)) ||
16271680
task_dumpable(task)) {
16281681
cred = __task_cred(task);
@@ -3119,6 +3172,7 @@ static const struct inode_operations proc_tgid_base_inode_operations = {
31193172
.lookup = proc_tgid_base_lookup,
31203173
.getattr = pid_getattr,
31213174
.setattr = proc_setattr,
3175+
.permission = proc_pid_permission,
31223176
};
31233177

31243178
static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
@@ -3322,13 +3376,20 @@ static int proc_pid_fill_cache(struct file *filp, void *dirent, filldir_t filldi
33223376
proc_pid_instantiate, iter.task, NULL);
33233377
}
33243378

3379+
static int fake_filldir(void *buf, const char *name, int namelen,
3380+
loff_t offset, u64 ino, unsigned d_type)
3381+
{
3382+
return 0;
3383+
}
3384+
33253385
/* for the /proc/ directory itself, after non-process stuff has been done */
33263386
int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)
33273387
{
33283388
unsigned int nr;
33293389
struct task_struct *reaper;
33303390
struct tgid_iter iter;
33313391
struct pid_namespace *ns;
3392+
filldir_t __filldir;
33323393

33333394
if (filp->f_pos >= PID_MAX_LIMIT + TGID_OFFSET)
33343395
goto out_no_task;
@@ -3350,8 +3411,13 @@ int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)
33503411
for (iter = next_tgid(ns, iter);
33513412
iter.task;
33523413
iter.tgid += 1, iter = next_tgid(ns, iter)) {
3414+
if (has_pid_permissions(ns, iter.task, 2))
3415+
__filldir = filldir;
3416+
else
3417+
__filldir = fake_filldir;
3418+
33533419
filp->f_pos = iter.tgid + TGID_OFFSET;
3354-
if (proc_pid_fill_cache(filp, dirent, filldir, iter) < 0) {
3420+
if (proc_pid_fill_cache(filp, dirent, __filldir, iter) < 0) {
33553421
put_task_struct(iter.task);
33563422
goto out;
33573423
}
@@ -3686,6 +3752,7 @@ static const struct inode_operations proc_task_inode_operations = {
36863752
.lookup = proc_task_lookup,
36873753
.getattr = proc_task_getattr,
36883754
.setattr = proc_setattr,
3755+
.permission = proc_pid_permission,
36893756
};
36903757

36913758
static const struct file_operations proc_task_operations = {

fs/proc/inode.c

+8
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ void __init proc_init_inodecache(void)
106106

107107
static int proc_show_options(struct seq_file *seq, struct dentry *root)
108108
{
109+
struct super_block *sb = root->d_sb;
110+
struct pid_namespace *pid = sb->s_fs_info;
111+
112+
if (pid->pid_gid)
113+
seq_printf(seq, ",gid=%lu", (unsigned long)pid->pid_gid);
114+
if (pid->hide_pid != 0)
115+
seq_printf(seq, ",hidepid=%u", pid->hide_pid);
116+
109117
return 0;
110118
}
111119

fs/proc/root.c

+18-3
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,20 @@ static int proc_set_super(struct super_block *sb, void *data)
3838
}
3939

4040
enum {
41-
Opt_err,
41+
Opt_gid, Opt_hidepid, Opt_err,
4242
};
4343

4444
static const match_table_t tokens = {
45+
{Opt_hidepid, "hidepid=%u"},
46+
{Opt_gid, "gid=%u"},
4547
{Opt_err, NULL},
4648
};
4749

4850
static int proc_parse_options(char *options, struct pid_namespace *pid)
4951
{
5052
char *p;
5153
substring_t args[MAX_OPT_ARGS];
52-
53-
pr_debug("proc: options = %s\n", options);
54+
int option;
5455

5556
if (!options)
5657
return 1;
@@ -63,6 +64,20 @@ static int proc_parse_options(char *options, struct pid_namespace *pid)
6364
args[0].to = args[0].from = 0;
6465
token = match_token(p, tokens, args);
6566
switch (token) {
67+
case Opt_gid:
68+
if (match_int(&args[0], &option))
69+
return 0;
70+
pid->pid_gid = option;
71+
break;
72+
case Opt_hidepid:
73+
if (match_int(&args[0], &option))
74+
return 0;
75+
if (option < 0 || option > 2) {
76+
pr_err("proc: hidepid value must be between 0 and 2.\n");
77+
return 0;
78+
}
79+
pid->hide_pid = option;
80+
break;
6681
default:
6782
pr_err("proc: unrecognized mount option \"%s\" "
6883
"or missing value\n", p);

include/linux/pid_namespace.h

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ struct pid_namespace {
3030
#ifdef CONFIG_BSD_PROCESS_ACCT
3131
struct bsd_acct_struct *bacct;
3232
#endif
33+
gid_t pid_gid;
34+
int hide_pid;
3335
};
3436

3537
extern struct pid_namespace init_pid_ns;

0 commit comments

Comments
 (0)