Skip to content

Commit

Permalink
Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kerne…
Browse files Browse the repository at this point in the history
…l/git/ebiederm/user-namespace

Pull proc updates from Eric Biederman:
 "This has four sets of changes:

   - modernize proc to support multiple private instances

   - ensure we see the exit of each process tid exactly

   - remove has_group_leader_pid

   - use pids not tasks in posix-cpu-timers lookup

  Alexey updated proc so each mount of proc uses a new superblock. This
  allows people to actually use mount options with proc with no fear of
  messing up another mount of proc. Given the kernel's internal mounts
  of proc for things like uml this was a real problem, and resulted in
  Android's hidepid mount options being ignored and introducing security
  issues.

  The rest of the changes are small cleanups and fixes that came out of
  my work to allow this change to proc. In essence it is swapping the
  pids in de_thread during exec which removes a special case the code
  had to handle. Then updating the code to stop handling that special
  case"

* 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace:
  proc: proc_pid_ns takes super_block as an argument
  remove the no longer needed pid_alive() check in __task_pid_nr_ns()
  posix-cpu-timers: Replace __get_task_for_clock with pid_for_clock
  posix-cpu-timers: Replace cpu_timer_pid_type with clock_pid_type
  posix-cpu-timers: Extend rcu_read_lock removing task_struct references
  signal: Remove has_group_leader_pid
  exec: Remove BUG_ON(has_group_leader_pid)
  posix-cpu-timer:  Unify the now redundant code in lookup_task
  posix-cpu-timer: Tidy up group_leader logic in lookup_task
  proc: Ensure we see the exit of each process tid exactly once
  rculist: Add hlists_swap_heads_rcu
  proc: Use PIDTYPE_TGID in next_tgid
  Use proc_pid_ns() to get pid_namespace from the proc superblock
  proc: use named enums for better readability
  proc: use human-readable values for hidepid
  docs: proc: add documentation for "hidepid=4" and "subset=pid" options and new mount behavior
  proc: add option to mount only a pids subset
  proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option
  proc: allow to mount many instances of proc in one pid namespace
  proc: rename struct proc_fs_info to proc_fs_opts
  • Loading branch information
torvalds committed Jun 4, 2020
2 parents 051c355 + 9d78ede commit 9ff7258
Show file tree
Hide file tree
Showing 25 changed files with 492 additions and 206 deletions.
92 changes: 73 additions & 19 deletions Documentation/filesystems/proc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ fixes/update part 1.1 Stefani Seibold <[email protected]> June 9 2009
4 Configuring procfs
4.1 Mount options
5 Filesystem behavior
Preface
=======

Expand Down Expand Up @@ -2143,28 +2145,80 @@ The following mount options are supported:
========= ========================================================
hidepid= Set /proc/<pid>/ access mode.
gid= Set the group authorized to learn processes information.
subset= Show only the specified subset of procfs.
========= ========================================================

hidepid=0 means classic mode - everybody may access all /proc/<pid>/ directories
(default).

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. This makes it impossible to learn whether any user runs
specific program (given the program doesn't reveal itself by its behaviour).
As an additional bonus, as /proc/<pid>/cmdline is unaccessible for other users,
poorly written programs passing sensitive information via program arguments are
now protected against local eavesdroppers.

hidepid=2 means hidepid=1 plus all /proc/<pid>/ will be fully invisible to other
users. It doesn't mean that it hides a fact whether a process with a specific
pid value exists (it can be learned by other means, e.g. by "kill -0 $PID"),
but it hides process' uid and gid, which may be learned by stat()'ing
/proc/<pid>/ otherwise. It greatly complicates an intruder's task of gathering
information about running processes, whether some daemon runs with elevated
privileges, whether other user runs some sensitive program, whether other users
run any program at all, etc.
hidepid=off or hidepid=0 means classic mode - everybody may access all
/proc/<pid>/ directories (default).

hidepid=noaccess or 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. This makes it impossible to learn whether any
user runs specific program (given the program doesn't reveal itself by its
behaviour). As an additional bonus, as /proc/<pid>/cmdline is unaccessible for
other users, poorly written programs passing sensitive information via program
arguments are now protected against local eavesdroppers.

hidepid=invisible or hidepid=2 means hidepid=1 plus all /proc/<pid>/ will be
fully invisible to other users. It doesn't mean that it hides a fact whether a
process with a specific pid value exists (it can be learned by other means, e.g.
by "kill -0 $PID"), but it hides process' uid and gid, which may be learned by
stat()'ing /proc/<pid>/ otherwise. It greatly complicates an intruder's task of
gathering information about running processes, whether some daemon runs with
elevated privileges, whether other user runs some sensitive program, whether
other users run any program at all, etc.

hidepid=ptraceable or hidepid=4 means that procfs should only contain
/proc/<pid>/ directories that the caller can ptrace.

gid= defines a group authorized to learn processes information otherwise
prohibited by hidepid=. If you use some daemon like identd which needs to learn
information about processes information, just add identd to this group.

subset=pid hides all top level files and directories in the procfs that
are not related to tasks.

5 Filesystem behavior
----------------------------

Originally, before the advent of pid namepsace, procfs was a global file
system. It means that there was only one procfs instance in the system.

When pid namespace was added, a separate procfs instance was mounted in
each pid namespace. So, procfs mount options are global among all
mountpoints within the same namespace.

::

# grep ^proc /proc/mounts
proc /proc proc rw,relatime,hidepid=2 0 0

# strace -e mount mount -o hidepid=1 -t proc proc /tmp/proc
mount("proc", "/tmp/proc", "proc", 0, "hidepid=1") = 0
+++ exited with 0 +++

# grep ^proc /proc/mounts
proc /proc proc rw,relatime,hidepid=2 0 0
proc /tmp/proc proc rw,relatime,hidepid=2 0 0

and only after remounting procfs mount options will change at all
mountpoints.

# mount -o remount,hidepid=1 -t proc proc /tmp/proc

# grep ^proc /proc/mounts
proc /proc proc rw,relatime,hidepid=1 0 0
proc /tmp/proc proc rw,relatime,hidepid=1 0 0

This behavior is different from the behavior of other filesystems.

The new procfs behavior is more like other filesystems. Each procfs mount
creates a new procfs instance. Mount options affect own procfs instance.
It means that it became possible to have several procfs instances
displaying tasks with different filtering options in one pid namespace.

# mount -o hidepid=invisible -t proc proc /proc
# mount -o hidepid=noaccess -t proc proc /tmp/proc
# grep ^proc /proc/mounts
proc /proc proc rw,relatime,hidepid=invisible 0 0
proc /tmp/proc proc rw,relatime,hidepid=noaccess 0 0
6 changes: 1 addition & 5 deletions fs/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,6 @@ static int de_thread(struct task_struct *tsk)
tsk->start_boottime = leader->start_boottime;

BUG_ON(!same_thread_group(leader, tsk));
BUG_ON(has_group_leader_pid(tsk));
/*
* An exec() starts a new thread group with the
* TGID of the previous thread group. Rehash the
Expand All @@ -1186,11 +1185,8 @@ static int de_thread(struct task_struct *tsk)

/* Become a process group leader with the old leader's pid.
* The old leader becomes a thread of the this thread group.
* Note: The old leader also uses this pid until release_task
* is called. Odd but simple and correct.
*/
tsk->pid = leader->pid;
change_pid(tsk, PIDTYPE_PID, task_pid(leader));
exchange_tids(tsk, leader);
transfer_pid(leader, tsk, PIDTYPE_TGID);
transfer_pid(leader, tsk, PIDTYPE_PGID);
transfer_pid(leader, tsk, PIDTYPE_SID);
Expand Down
4 changes: 2 additions & 2 deletions fs/locks.c
Original file line number Diff line number Diff line change
Expand Up @@ -2823,7 +2823,7 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
{
struct inode *inode = NULL;
unsigned int fl_pid;
struct pid_namespace *proc_pidns = file_inode(f->file)->i_sb->s_fs_info;
struct pid_namespace *proc_pidns = proc_pid_ns(file_inode(f->file)->i_sb);

fl_pid = locks_translate_pid(fl, proc_pidns);
/*
Expand Down Expand Up @@ -2901,7 +2901,7 @@ static int locks_show(struct seq_file *f, void *v)
{
struct locks_iterator *iter = f->private;
struct file_lock *fl, *bfl;
struct pid_namespace *proc_pidns = file_inode(f->file)->i_sb->s_fs_info;
struct pid_namespace *proc_pidns = proc_pid_ns(file_inode(f->file)->i_sb);

fl = hlist_entry(v, struct file_lock, fl_link);

Expand Down
2 changes: 1 addition & 1 deletion fs/proc/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ static int children_seq_show(struct seq_file *seq, void *v)
{
struct inode *inode = file_inode(seq->file);

seq_printf(seq, "%d ", pid_nr_ns(v, proc_pid_ns(inode)));
seq_printf(seq, "%d ", pid_nr_ns(v, proc_pid_ns(inode->i_sb)));
return 0;
}

Expand Down
74 changes: 41 additions & 33 deletions fs/proc/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -697,32 +697,40 @@ int proc_setattr(struct dentry *dentry, struct iattr *attr)
* 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 pid_namespace *pid,
static bool has_pid_permissions(struct proc_fs_info *fs_info,
struct task_struct *task,
int hide_pid_min)
enum proc_hidepid hide_pid_min)
{
if (pid->hide_pid < 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(pid->pid_gid))
if (in_group_p(fs_info->pid_gid))
return true;
return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
}


static int proc_pid_permission(struct inode *inode, int mask)
{
struct pid_namespace *pid = proc_pid_ns(inode);
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(pid, task, HIDEPID_NO_ACCESS);
has_perms = has_pid_permissions(fs_info, task, HIDEPID_NO_ACCESS);
put_task_struct(task);

if (!has_perms) {
if (pid->hide_pid == HIDEPID_INVISIBLE) {
if (fs_info->hide_pid == HIDEPID_INVISIBLE) {
/*
* Let's make getdents(), stat(), and open()
* consistent with each other. If a process
Expand All @@ -746,7 +754,7 @@ static const struct inode_operations proc_def_inode_operations = {
static int proc_single_show(struct seq_file *m, void *v)
{
struct inode *inode = m->private;
struct pid_namespace *ns = proc_pid_ns(inode);
struct pid_namespace *ns = proc_pid_ns(inode->i_sb);
struct pid *pid = proc_pid(inode);
struct task_struct *task;
int ret;
Expand Down Expand Up @@ -1415,7 +1423,7 @@ static const struct file_operations proc_fail_nth_operations = {
static int sched_show(struct seq_file *m, void *v)
{
struct inode *inode = m->private;
struct pid_namespace *ns = proc_pid_ns(inode);
struct pid_namespace *ns = proc_pid_ns(inode->i_sb);
struct task_struct *p;

p = get_proc_task(inode);
Expand Down Expand Up @@ -1909,7 +1917,7 @@ int pid_getattr(const struct path *path, struct kstat *stat,
u32 request_mask, unsigned int query_flags)
{
struct inode *inode = d_inode(path->dentry);
struct pid_namespace *pid = proc_pid_ns(inode);
struct proc_fs_info *fs_info = proc_sb_info(inode->i_sb);
struct task_struct *task;

generic_fillattr(inode, stat);
Expand All @@ -1919,7 +1927,7 @@ int pid_getattr(const struct path *path, struct kstat *stat,
rcu_read_lock();
task = pid_task(proc_pid(inode), PIDTYPE_PID);
if (task) {
if (!has_pid_permissions(pid, task, HIDEPID_INVISIBLE)) {
if (!has_pid_permissions(fs_info, task, HIDEPID_INVISIBLE)) {
rcu_read_unlock();
/*
* This doesn't prevent learning whether PID exists,
Expand Down Expand Up @@ -2470,7 +2478,7 @@ static int proc_timers_open(struct inode *inode, struct file *file)
return -ENOMEM;

tp->pid = proc_pid(inode);
tp->ns = proc_pid_ns(inode);
tp->ns = proc_pid_ns(inode->i_sb);
return 0;
}

Expand Down Expand Up @@ -3312,14 +3320,16 @@ struct dentry *proc_pid_lookup(struct dentry *dentry, unsigned int flags)
{
struct task_struct *task;
unsigned tgid;
struct proc_fs_info *fs_info;
struct pid_namespace *ns;
struct dentry *result = ERR_PTR(-ENOENT);

tgid = name_to_int(&dentry->d_name);
if (tgid == ~0U)
goto out;

ns = dentry->d_sb->s_fs_info;
fs_info = proc_sb_info(dentry->d_sb);
ns = fs_info->pid_ns;
rcu_read_lock();
task = find_task_by_pid_ns(tgid, ns);
if (task)
Expand All @@ -3328,7 +3338,14 @@ struct dentry *proc_pid_lookup(struct dentry *dentry, unsigned int flags)
if (!task)
goto out;

/* Limit procfs to only ptraceable tasks */
if (fs_info->hide_pid == HIDEPID_NOT_PTRACEABLE) {
if (!has_pid_permissions(fs_info, task, HIDEPID_NO_ACCESS))
goto out_put_task;
}

result = proc_pid_instantiate(dentry, task, NULL);
out_put_task:
put_task_struct(task);
out:
return result;
Expand All @@ -3354,20 +3371,8 @@ static struct tgid_iter next_tgid(struct pid_namespace *ns, struct tgid_iter ite
pid = find_ge_pid(iter.tgid, ns);
if (pid) {
iter.tgid = pid_nr_ns(pid, ns);
iter.task = pid_task(pid, PIDTYPE_PID);
/* What we to know is if the pid we have find is the
* pid of a thread_group_leader. Testing for task
* being a thread_group_leader is the obvious thing
* todo but there is a window when it fails, due to
* the pid transfer logic in de_thread.
*
* So we perform the straight forward test of seeing
* if the pid we have found is the pid of a thread
* group leader, and don't worry if the task we have
* found doesn't happen to be a thread group leader.
* As we don't care in the case of readdir.
*/
if (!iter.task || !has_group_leader_pid(iter.task)) {
iter.task = pid_task(pid, PIDTYPE_TGID);
if (!iter.task) {
iter.tgid += 1;
goto retry;
}
Expand All @@ -3383,20 +3388,21 @@ static struct tgid_iter next_tgid(struct pid_namespace *ns, struct tgid_iter ite
int proc_pid_readdir(struct file *file, struct dir_context *ctx)
{
struct tgid_iter iter;
struct pid_namespace *ns = proc_pid_ns(file_inode(file));
struct proc_fs_info *fs_info = proc_sb_info(file_inode(file)->i_sb);
struct pid_namespace *ns = proc_pid_ns(file_inode(file)->i_sb);
loff_t pos = ctx->pos;

if (pos >= PID_MAX_LIMIT + TGID_OFFSET)
return 0;

if (pos == TGID_OFFSET - 2) {
struct inode *inode = d_inode(ns->proc_self);
struct inode *inode = d_inode(fs_info->proc_self);
if (!dir_emit(ctx, "self", 4, inode->i_ino, DT_LNK))
return 0;
ctx->pos = pos = pos + 1;
}
if (pos == TGID_OFFSET - 1) {
struct inode *inode = d_inode(ns->proc_thread_self);
struct inode *inode = d_inode(fs_info->proc_thread_self);
if (!dir_emit(ctx, "thread-self", 11, inode->i_ino, DT_LNK))
return 0;
ctx->pos = pos = pos + 1;
Expand All @@ -3410,7 +3416,7 @@ int proc_pid_readdir(struct file *file, struct dir_context *ctx)
unsigned int len;

cond_resched();
if (!has_pid_permissions(ns, iter.task, HIDEPID_INVISIBLE))
if (!has_pid_permissions(fs_info, iter.task, HIDEPID_INVISIBLE))
continue;

len = snprintf(name, sizeof(name), "%u", iter.tgid);
Expand Down Expand Up @@ -3610,6 +3616,7 @@ static struct dentry *proc_task_lookup(struct inode *dir, struct dentry * dentry
struct task_struct *task;
struct task_struct *leader = get_proc_task(dir);
unsigned tid;
struct proc_fs_info *fs_info;
struct pid_namespace *ns;
struct dentry *result = ERR_PTR(-ENOENT);

Expand All @@ -3620,7 +3627,8 @@ static struct dentry *proc_task_lookup(struct inode *dir, struct dentry * dentry
if (tid == ~0U)
goto out;

ns = dentry->d_sb->s_fs_info;
fs_info = proc_sb_info(dentry->d_sb);
ns = fs_info->pid_ns;
rcu_read_lock();
task = find_task_by_pid_ns(tid, ns);
if (task)
Expand Down Expand Up @@ -3734,7 +3742,7 @@ static int proc_task_readdir(struct file *file, struct dir_context *ctx)
/* f_version caches the tgid value that the last readdir call couldn't
* return. lseek aka telldir automagically resets f_version to 0.
*/
ns = proc_pid_ns(inode);
ns = proc_pid_ns(inode->i_sb);
tid = (int)file->f_version;
file->f_version = 0;
for (task = first_tid(proc_pid(inode), tid, ctx->pos - 2, ns);
Expand Down
9 changes: 9 additions & 0 deletions fs/proc/generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ struct dentry *proc_lookup_de(struct inode *dir, struct dentry *dentry,
struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);

if (fs_info->pidonly == PROC_PIDONLY_ON)
return ERR_PTR(-ENOENT);

return proc_lookup_de(dir, dentry, PDE(dir));
}

Expand Down Expand Up @@ -325,6 +330,10 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
int proc_readdir(struct file *file, struct dir_context *ctx)
{
struct inode *inode = file_inode(file);
struct proc_fs_info *fs_info = proc_sb_info(inode->i_sb);

if (fs_info->pidonly == PROC_PIDONLY_ON)
return 1;

return proc_readdir_de(file, ctx, PDE(inode));
}
Expand Down
Loading

0 comments on commit 9ff7258

Please sign in to comment.