diff options
-rw-r--r-- | Documentation/filesystems/proc.txt | 39 | ||||
-rw-r--r-- | fs/proc/base.c | 69 | ||||
-rw-r--r-- | fs/proc/inode.c | 8 | ||||
-rw-r--r-- | fs/proc/root.c | 21 | ||||
-rw-r--r-- | include/linux/pid_namespace.h | 2 |
5 files changed, 135 insertions, 4 deletions
diff --git a/Documentation/filesystems/proc.txt b/Documentation/filesystems/proc.txt index 0ec91f03422e..12fee132fbe2 100644 --- a/Documentation/filesystems/proc.txt +++ b/Documentation/filesystems/proc.txt | |||
@@ -41,6 +41,8 @@ Table of Contents | |||
41 | 3.5 /proc/<pid>/mountinfo - Information about mounts | 41 | 3.5 /proc/<pid>/mountinfo - Information about mounts |
42 | 3.6 /proc/<pid>/comm & /proc/<pid>/task/<tid>/comm | 42 | 3.6 /proc/<pid>/comm & /proc/<pid>/task/<tid>/comm |
43 | 43 | ||
44 | 4 Configuring procfs | ||
45 | 4.1 Mount options | ||
44 | 46 | ||
45 | ------------------------------------------------------------------------------ | 47 | ------------------------------------------------------------------------------ |
46 | Preface | 48 | Preface |
@@ -1542,3 +1544,40 @@ a task to set its own or one of its thread siblings comm value. The comm value | |||
1542 | is limited in size compared to the cmdline value, so writing anything longer | 1544 | is limited in size compared to the cmdline value, so writing anything longer |
1543 | then the kernel's TASK_COMM_LEN (currently 16 chars) will result in a truncated | 1545 | then the kernel's TASK_COMM_LEN (currently 16 chars) will result in a truncated |
1544 | comm value. | 1546 | 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. | ||
diff --git a/fs/proc/base.c b/fs/proc/base.c index 4d755fed3ecb..8173dfd89cb2 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c | |||
@@ -631,6 +631,50 @@ int proc_setattr(struct dentry *dentry, struct iattr *attr) | |||
631 | return 0; | 631 | return 0; |
632 | } | 632 | } |
633 | 633 | ||
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 | |||
634 | static const struct inode_operations proc_def_inode_operations = { | 678 | static const struct inode_operations proc_def_inode_operations = { |
635 | .setattr = proc_setattr, | 679 | .setattr = proc_setattr, |
636 | }; | 680 | }; |
@@ -1615,6 +1659,7 @@ int pid_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) | |||
1615 | struct inode *inode = dentry->d_inode; | 1659 | struct inode *inode = dentry->d_inode; |
1616 | struct task_struct *task; | 1660 | struct task_struct *task; |
1617 | const struct cred *cred; | 1661 | const struct cred *cred; |
1662 | struct pid_namespace *pid = dentry->d_sb->s_fs_info; | ||
1618 | 1663 | ||
1619 | generic_fillattr(inode, stat); | 1664 | generic_fillattr(inode, stat); |
1620 | 1665 | ||
@@ -1623,6 +1668,14 @@ int pid_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) | |||
1623 | stat->gid = 0; | 1668 | stat->gid = 0; |
1624 | task = pid_task(proc_pid(inode), PIDTYPE_PID); | 1669 | task = pid_task(proc_pid(inode), PIDTYPE_PID); |
1625 | if (task) { | 1670 | 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 | } | ||
1626 | if ((inode->i_mode == (S_IFDIR|S_IRUGO|S_IXUGO)) || | 1679 | if ((inode->i_mode == (S_IFDIR|S_IRUGO|S_IXUGO)) || |
1627 | task_dumpable(task)) { | 1680 | task_dumpable(task)) { |
1628 | cred = __task_cred(task); | 1681 | cred = __task_cred(task); |
@@ -3119,6 +3172,7 @@ static const struct inode_operations proc_tgid_base_inode_operations = { | |||
3119 | .lookup = proc_tgid_base_lookup, | 3172 | .lookup = proc_tgid_base_lookup, |
3120 | .getattr = pid_getattr, | 3173 | .getattr = pid_getattr, |
3121 | .setattr = proc_setattr, | 3174 | .setattr = proc_setattr, |
3175 | .permission = proc_pid_permission, | ||
3122 | }; | 3176 | }; |
3123 | 3177 | ||
3124 | static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid) | 3178 | static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid) |
@@ -3322,6 +3376,12 @@ static int proc_pid_fill_cache(struct file *filp, void *dirent, filldir_t filldi | |||
3322 | proc_pid_instantiate, iter.task, NULL); | 3376 | proc_pid_instantiate, iter.task, NULL); |
3323 | } | 3377 | } |
3324 | 3378 | ||
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 | |||
3325 | /* for the /proc/ directory itself, after non-process stuff has been done */ | 3385 | /* for the /proc/ directory itself, after non-process stuff has been done */ |
3326 | int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) | 3386 | int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) |
3327 | { | 3387 | { |
@@ -3329,6 +3389,7 @@ int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) | |||
3329 | struct task_struct *reaper; | 3389 | struct task_struct *reaper; |
3330 | struct tgid_iter iter; | 3390 | struct tgid_iter iter; |
3331 | struct pid_namespace *ns; | 3391 | struct pid_namespace *ns; |
3392 | filldir_t __filldir; | ||
3332 | 3393 | ||
3333 | if (filp->f_pos >= PID_MAX_LIMIT + TGID_OFFSET) | 3394 | if (filp->f_pos >= PID_MAX_LIMIT + TGID_OFFSET) |
3334 | goto out_no_task; | 3395 | goto out_no_task; |
@@ -3350,8 +3411,13 @@ int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) | |||
3350 | for (iter = next_tgid(ns, iter); | 3411 | for (iter = next_tgid(ns, iter); |
3351 | iter.task; | 3412 | iter.task; |
3352 | iter.tgid += 1, iter = next_tgid(ns, iter)) { | 3413 | 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 | |||
3353 | filp->f_pos = iter.tgid + TGID_OFFSET; | 3419 | 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) { |
3355 | put_task_struct(iter.task); | 3421 | put_task_struct(iter.task); |
3356 | goto out; | 3422 | goto out; |
3357 | } | 3423 | } |
@@ -3686,6 +3752,7 @@ static const struct inode_operations proc_task_inode_operations = { | |||
3686 | .lookup = proc_task_lookup, | 3752 | .lookup = proc_task_lookup, |
3687 | .getattr = proc_task_getattr, | 3753 | .getattr = proc_task_getattr, |
3688 | .setattr = proc_setattr, | 3754 | .setattr = proc_setattr, |
3755 | .permission = proc_pid_permission, | ||
3689 | }; | 3756 | }; |
3690 | 3757 | ||
3691 | static const struct file_operations proc_task_operations = { | 3758 | static const struct file_operations proc_task_operations = { |
diff --git a/fs/proc/inode.c b/fs/proc/inode.c index 27c762f34870..84fd3235a590 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c | |||
@@ -106,6 +106,14 @@ void __init proc_init_inodecache(void) | |||
106 | 106 | ||
107 | static int proc_show_options(struct seq_file *seq, struct dentry *root) | 107 | static int proc_show_options(struct seq_file *seq, struct dentry *root) |
108 | { | 108 | { |
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 | |||
109 | return 0; | 117 | return 0; |
110 | } | 118 | } |
111 | 119 | ||
diff --git a/fs/proc/root.c b/fs/proc/root.c index 6a8ac1d361a9..46a15d8a29ca 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c | |||
@@ -38,10 +38,12 @@ static int proc_set_super(struct super_block *sb, void *data) | |||
38 | } | 38 | } |
39 | 39 | ||
40 | enum { | 40 | enum { |
41 | Opt_err, | 41 | Opt_gid, Opt_hidepid, Opt_err, |
42 | }; | 42 | }; |
43 | 43 | ||
44 | static const match_table_t tokens = { | 44 | static const match_table_t tokens = { |
45 | {Opt_hidepid, "hidepid=%u"}, | ||
46 | {Opt_gid, "gid=%u"}, | ||
45 | {Opt_err, NULL}, | 47 | {Opt_err, NULL}, |
46 | }; | 48 | }; |
47 | 49 | ||
@@ -49,8 +51,7 @@ static int proc_parse_options(char *options, struct pid_namespace *pid) | |||
49 | { | 51 | { |
50 | char *p; | 52 | char *p; |
51 | substring_t args[MAX_OPT_ARGS]; | 53 | substring_t args[MAX_OPT_ARGS]; |
52 | 54 | int option; | |
53 | pr_debug("proc: options = %s\n", options); | ||
54 | 55 | ||
55 | if (!options) | 56 | if (!options) |
56 | return 1; | 57 | return 1; |
@@ -63,6 +64,20 @@ static int proc_parse_options(char *options, struct pid_namespace *pid) | |||
63 | args[0].to = args[0].from = 0; | 64 | args[0].to = args[0].from = 0; |
64 | token = match_token(p, tokens, args); | 65 | token = match_token(p, tokens, args); |
65 | switch (token) { | 66 | 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; | ||
66 | default: | 81 | default: |
67 | pr_err("proc: unrecognized mount option \"%s\" " | 82 | pr_err("proc: unrecognized mount option \"%s\" " |
68 | "or missing value\n", p); | 83 | "or missing value\n", p); |
diff --git a/include/linux/pid_namespace.h b/include/linux/pid_namespace.h index 38d10326246a..e7cf6669ac34 100644 --- a/include/linux/pid_namespace.h +++ b/include/linux/pid_namespace.h | |||
@@ -30,6 +30,8 @@ struct pid_namespace { | |||
30 | #ifdef CONFIG_BSD_PROCESS_ACCT | 30 | #ifdef CONFIG_BSD_PROCESS_ACCT |
31 | struct bsd_acct_struct *bacct; | 31 | struct bsd_acct_struct *bacct; |
32 | #endif | 32 | #endif |
33 | gid_t pid_gid; | ||
34 | int hide_pid; | ||
33 | }; | 35 | }; |
34 | 36 | ||
35 | extern struct pid_namespace init_pid_ns; | 37 | extern struct pid_namespace init_pid_ns; |