Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/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
This commit is contained in:
Linus Torvalds 2020-06-04 13:54:34 -07:00
commit 9ff7258575
25 changed files with 493 additions and 207 deletions

View File

@ -51,6 +51,8 @@ fixes/update part 1.1 Stefani Seibold <stefani@seibold.net> June 9 2009
4 Configuring procfs
4.1 Mount options
5 Filesystem behavior
Preface
=======
@ -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=off or 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=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=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=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

View File

@ -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
@ -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);

View File

@ -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);
/*
@ -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);

View File

@ -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;
}

View File

@ -697,13 +697,21 @@ 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);
}
@ -711,18 +719,18 @@ static bool has_pid_permissions(struct pid_namespace *pid,
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
@ -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;
@ -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);
@ -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);
@ -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,
@ -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;
}
@ -3312,6 +3320,7 @@ 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);
@ -3319,7 +3328,8 @@ struct dentry *proc_pid_lookup(struct dentry *dentry, unsigned int flags)
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)
@ -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;
@ -3354,20 +3371,8 @@ retry:
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;
}
@ -3383,20 +3388,21 @@ retry:
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;
@ -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);
@ -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);
@ -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)
@ -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);

View File

@ -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));
}
@ -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));
}

View File

@ -24,6 +24,7 @@
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/mount.h>
#include <linux/bug.h>
#include <linux/uaccess.h>
@ -165,15 +166,28 @@ void proc_invalidate_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock
deactivate_super(old_sb);
}
static inline const char *hidepid2str(enum proc_hidepid v)
{
switch (v) {
case HIDEPID_OFF: return "off";
case HIDEPID_NO_ACCESS: return "noaccess";
case HIDEPID_INVISIBLE: return "invisible";
case HIDEPID_NOT_PTRACEABLE: return "ptraceable";
}
WARN_ONCE(1, "bad hide_pid value: %d\n", v);
return "unknown";
}
static int proc_show_options(struct seq_file *seq, struct dentry *root)
{
struct super_block *sb = root->d_sb;
struct pid_namespace *pid = sb->s_fs_info;
struct proc_fs_info *fs_info = proc_sb_info(root->d_sb);
if (!gid_eq(pid->pid_gid, GLOBAL_ROOT_GID))
seq_printf(seq, ",gid=%u", from_kgid_munged(&init_user_ns, pid->pid_gid));
if (pid->hide_pid != HIDEPID_OFF)
seq_printf(seq, ",hidepid=%u", pid->hide_pid);
if (!gid_eq(fs_info->pid_gid, GLOBAL_ROOT_GID))
seq_printf(seq, ",gid=%u", from_kgid_munged(&init_user_ns, fs_info->pid_gid));
if (fs_info->hide_pid != HIDEPID_OFF)
seq_printf(seq, ",hidepid=%s", hidepid2str(fs_info->hide_pid));
if (fs_info->pidonly != PROC_PIDONLY_OFF)
seq_printf(seq, ",subset=pid");
return 0;
}
@ -464,6 +478,7 @@ proc_reg_get_unmapped_area(struct file *file, unsigned long orig_addr,
static int proc_reg_open(struct inode *inode, struct file *file)
{
struct proc_fs_info *fs_info = proc_sb_info(inode->i_sb);
struct proc_dir_entry *pde = PDE(inode);
int rv = 0;
typeof_member(struct proc_ops, proc_open) open;
@ -477,6 +492,9 @@ static int proc_reg_open(struct inode *inode, struct file *file)
return rv;
}
if (fs_info->pidonly == PROC_PIDONLY_ON)
return -ENOENT;
/*
* Ensure that
* 1) PDE's ->release hook will be called no matter what

View File

@ -32,21 +32,86 @@
struct proc_fs_context {
struct pid_namespace *pid_ns;
unsigned int mask;
int hidepid;
enum proc_hidepid hidepid;
int gid;
enum proc_pidonly pidonly;
};
enum proc_param {
Opt_gid,
Opt_hidepid,
Opt_subset,
};
static const struct fs_parameter_spec proc_fs_parameters[] = {
fsparam_u32("gid", Opt_gid),
fsparam_u32("hidepid", Opt_hidepid),
fsparam_string("hidepid", Opt_hidepid),
fsparam_string("subset", Opt_subset),
{}
};
static inline int valid_hidepid(unsigned int value)
{
return (value == HIDEPID_OFF ||
value == HIDEPID_NO_ACCESS ||
value == HIDEPID_INVISIBLE ||
value == HIDEPID_NOT_PTRACEABLE);
}
static int proc_parse_hidepid_param(struct fs_context *fc, struct fs_parameter *param)
{
struct proc_fs_context *ctx = fc->fs_private;
struct fs_parameter_spec hidepid_u32_spec = fsparam_u32("hidepid", Opt_hidepid);
struct fs_parse_result result;
int base = (unsigned long)hidepid_u32_spec.data;
if (param->type != fs_value_is_string)
return invalf(fc, "proc: unexpected type of hidepid value\n");
if (!kstrtouint(param->string, base, &result.uint_32)) {
if (!valid_hidepid(result.uint_32))
return invalf(fc, "proc: unknown value of hidepid - %s\n", param->string);
ctx->hidepid = result.uint_32;
return 0;
}
if (!strcmp(param->string, "off"))
ctx->hidepid = HIDEPID_OFF;
else if (!strcmp(param->string, "noaccess"))
ctx->hidepid = HIDEPID_NO_ACCESS;
else if (!strcmp(param->string, "invisible"))
ctx->hidepid = HIDEPID_INVISIBLE;
else if (!strcmp(param->string, "ptraceable"))
ctx->hidepid = HIDEPID_NOT_PTRACEABLE;
else
return invalf(fc, "proc: unknown value of hidepid - %s\n", param->string);
return 0;
}
static int proc_parse_subset_param(struct fs_context *fc, char *value)
{
struct proc_fs_context *ctx = fc->fs_private;
while (value) {
char *ptr = strchr(value, ',');
if (ptr != NULL)
*ptr++ = '\0';
if (*value != '\0') {
if (!strcmp(value, "pid")) {
ctx->pidonly = PROC_PIDONLY_ON;
} else {
return invalf(fc, "proc: unsupported subset option - %s\n", value);
}
}
value = ptr;
}
return 0;
}
static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct proc_fs_context *ctx = fc->fs_private;
@ -63,10 +128,13 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
break;
case Opt_hidepid:
ctx->hidepid = result.uint_32;
if (ctx->hidepid < HIDEPID_OFF ||
ctx->hidepid > HIDEPID_INVISIBLE)
return invalfc(fc, "hidepid value must be between 0 and 2.\n");
if (proc_parse_hidepid_param(fc, param))
return -EINVAL;
break;
case Opt_subset:
if (proc_parse_subset_param(fc, param->string) < 0)
return -EINVAL;
break;
default:
@ -77,26 +145,33 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
return 0;
}
static void proc_apply_options(struct super_block *s,
static void proc_apply_options(struct proc_fs_info *fs_info,
struct fs_context *fc,
struct pid_namespace *pid_ns,
struct user_namespace *user_ns)
{
struct proc_fs_context *ctx = fc->fs_private;
if (ctx->mask & (1 << Opt_gid))
pid_ns->pid_gid = make_kgid(user_ns, ctx->gid);
fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
if (ctx->mask & (1 << Opt_hidepid))
pid_ns->hide_pid = ctx->hidepid;
fs_info->hide_pid = ctx->hidepid;
if (ctx->mask & (1 << Opt_subset))
fs_info->pidonly = ctx->pidonly;
}
static int proc_fill_super(struct super_block *s, struct fs_context *fc)
{
struct pid_namespace *pid_ns = get_pid_ns(s->s_fs_info);
struct proc_fs_context *ctx = fc->fs_private;
struct inode *root_inode;
struct proc_fs_info *fs_info;
int ret;
proc_apply_options(s, fc, pid_ns, current_user_ns());
fs_info = kzalloc(sizeof(*fs_info), GFP_KERNEL);
if (!fs_info)
return -ENOMEM;
fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
proc_apply_options(fs_info, fc, current_user_ns());
/* User space would break if executables or devices appear on proc */
s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@ -106,6 +181,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
s->s_magic = PROC_SUPER_MAGIC;
s->s_op = &proc_sops;
s->s_time_gran = 1;
s->s_fs_info = fs_info;
/*
* procfs isn't actually a stacking filesystem; however, there is
@ -113,7 +189,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
* top of it
*/
s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
/* procfs dentries and inodes don't require IO to create */
s->s_shrink.seeks = 0;
@ -140,19 +216,17 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
static int proc_reconfigure(struct fs_context *fc)
{
struct super_block *sb = fc->root->d_sb;
struct pid_namespace *pid = sb->s_fs_info;
struct proc_fs_info *fs_info = proc_sb_info(sb);
sync_filesystem(sb);
proc_apply_options(sb, fc, pid, current_user_ns());
proc_apply_options(fs_info, fc, current_user_ns());
return 0;
}
static int proc_get_tree(struct fs_context *fc)
{
struct proc_fs_context *ctx = fc->fs_private;
return get_tree_keyed(fc, proc_fill_super, ctx->pid_ns);
return get_tree_nodev(fc, proc_fill_super);
}
static void proc_fs_context_free(struct fs_context *fc)
@ -188,22 +262,17 @@ static int proc_init_fs_context(struct fs_context *fc)
static void proc_kill_sb(struct super_block *sb)
{
struct pid_namespace *ns;
struct proc_fs_info *fs_info = proc_sb_info(sb);
if (fs_info->proc_self)
dput(fs_info->proc_self);
if (fs_info->proc_thread_self)
dput(fs_info->proc_thread_self);
ns = (struct pid_namespace *)sb->s_fs_info;
if (ns->proc_self)
dput(ns->proc_self);
if (ns->proc_thread_self)
dput(ns->proc_thread_self);
kill_anon_super(sb);
/* Make the pid namespace safe for the next mount of proc */
ns->proc_self = NULL;
ns->proc_thread_self = NULL;
ns->pid_gid = GLOBAL_ROOT_GID;
ns->hide_pid = 0;
put_pid_ns(ns);
put_pid_ns(fs_info->pid_ns);
kfree(fs_info);
}
static struct file_system_type proc_fs_type = {

View File

@ -12,7 +12,7 @@ static const char *proc_self_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
struct pid_namespace *ns = proc_pid_ns(inode);
struct pid_namespace *ns = proc_pid_ns(inode->i_sb);
pid_t tgid = task_tgid_nr_ns(current, ns);
char *name;
@ -36,10 +36,10 @@ static unsigned self_inum __ro_after_init;
int proc_setup_self(struct super_block *s)
{
struct inode *root_inode = d_inode(s->s_root);
struct pid_namespace *ns = proc_pid_ns(root_inode);
struct proc_fs_info *fs_info = proc_sb_info(s);
struct dentry *self;
int ret = -ENOMEM;
inode_lock(root_inode);
self = d_alloc_name(s->s_root, "self");
if (self) {
@ -62,7 +62,7 @@ int proc_setup_self(struct super_block *s)
if (ret)
pr_err("proc_fill_super: can't allocate /proc/self\n");
else
ns->proc_self = self;
fs_info->proc_self = self;
return ret;
}

View File

@ -12,7 +12,7 @@ static const char *proc_thread_self_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
struct pid_namespace *ns = proc_pid_ns(inode);
struct pid_namespace *ns = proc_pid_ns(inode->i_sb);
pid_t tgid = task_tgid_nr_ns(current, ns);
pid_t pid = task_pid_nr_ns(current, ns);
char *name;
@ -36,7 +36,7 @@ static unsigned thread_self_inum __ro_after_init;
int proc_setup_thread_self(struct super_block *s)
{
struct inode *root_inode = d_inode(s->s_root);
struct pid_namespace *ns = proc_pid_ns(root_inode);
struct proc_fs_info *fs_info = proc_sb_info(s);
struct dentry *thread_self;
int ret = -ENOMEM;
@ -60,9 +60,9 @@ int proc_setup_thread_self(struct super_block *s)
inode_unlock(root_inode);
if (ret)
pr_err("proc_fill_super: can't allocate /proc/thread_self\n");
pr_err("proc_fill_super: can't allocate /proc/thread-self\n");
else
ns->proc_thread_self = thread_self;
fs_info->proc_thread_self = thread_self;
return ret;
}

View File

@ -37,23 +37,23 @@ static __poll_t mounts_poll(struct file *file, poll_table *wait)
return res;
}
struct proc_fs_info {
struct proc_fs_opts {
int flag;
const char *str;
};
static int show_sb_opts(struct seq_file *m, struct super_block *sb)
{
static const struct proc_fs_info fs_info[] = {
static const struct proc_fs_opts fs_opts[] = {
{ SB_SYNCHRONOUS, ",sync" },
{ SB_DIRSYNC, ",dirsync" },
{ SB_MANDLOCK, ",mand" },
{ SB_LAZYTIME, ",lazytime" },
{ 0, NULL }
};
const struct proc_fs_info *fs_infop;
const struct proc_fs_opts *fs_infop;
for (fs_infop = fs_info; fs_infop->flag; fs_infop++) {
for (fs_infop = fs_opts; fs_infop->flag; fs_infop++) {
if (sb->s_flags & fs_infop->flag)
seq_puts(m, fs_infop->str);
}
@ -63,7 +63,7 @@ static int show_sb_opts(struct seq_file *m, struct super_block *sb)
static void show_mnt_opts(struct seq_file *m, struct vfsmount *mnt)
{
static const struct proc_fs_info mnt_info[] = {
static const struct proc_fs_opts mnt_opts[] = {
{ MNT_NOSUID, ",nosuid" },
{ MNT_NODEV, ",nodev" },
{ MNT_NOEXEC, ",noexec" },
@ -72,9 +72,9 @@ static void show_mnt_opts(struct seq_file *m, struct vfsmount *mnt)
{ MNT_RELATIME, ",relatime" },
{ 0, NULL }
};
const struct proc_fs_info *fs_infop;
const struct proc_fs_opts *fs_infop;
for (fs_infop = mnt_info; fs_infop->flag; fs_infop++) {
for (fs_infop = mnt_opts; fs_infop->flag; fs_infop++) {
if (mnt->mnt_flags & fs_infop->flag)
seq_puts(m, fs_infop->str);
}

View File

@ -102,6 +102,7 @@ extern void attach_pid(struct task_struct *task, enum pid_type);
extern void detach_pid(struct task_struct *task, enum pid_type);
extern void change_pid(struct task_struct *task, enum pid_type,
struct pid *pid);
extern void exchange_tids(struct task_struct *task, struct task_struct *old);
extern void transfer_pid(struct task_struct *old, struct task_struct *new,
enum pid_type);

View File

@ -17,12 +17,6 @@
struct fs_pin;
enum { /* definitions for pid_namespace's hide_pid field */
HIDEPID_OFF = 0,
HIDEPID_NO_ACCESS = 1,
HIDEPID_INVISIBLE = 2,
};
struct pid_namespace {
struct kref kref;
struct idr idr;
@ -32,17 +26,11 @@ struct pid_namespace {
struct kmem_cache *pid_cachep;
unsigned int level;
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
struct dentry *proc_self;
struct dentry *proc_thread_self;
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
struct fs_pin *bacct;
#endif
struct user_namespace *user_ns;
struct ucounts *ucounts;
kgid_t pid_gid;
int hide_pid;
int reboot; /* group exit code if this pidns was rebooted */
struct ns_common ns;
} __randomize_layout;

View File

@ -42,6 +42,34 @@ struct proc_ops {
unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
} __randomize_layout;
/* 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 */
};
/* definitions for proc mount option pidonly */
enum proc_pidonly {
PROC_PIDONLY_OFF = 0,
PROC_PIDONLY_ON = 1,
};
struct proc_fs_info {
struct pid_namespace *pid_ns;
struct dentry *proc_self; /* For /proc/self */
struct dentry *proc_thread_self; /* For /proc/thread-self */
kgid_t pid_gid;
enum proc_hidepid hide_pid;
enum proc_pidonly pidonly;
};
static inline struct proc_fs_info *proc_sb_info(struct super_block *sb)
{
return sb->s_fs_info;
}
#ifdef CONFIG_PROC_FS
typedef int (*proc_write_t)(struct file *, char *, size_t);
@ -177,9 +205,9 @@ int open_related_ns(struct ns_common *ns,
struct ns_common *(*get_ns)(struct ns_common *ns));
/* get the associated pid namespace for a file in procfs */
static inline struct pid_namespace *proc_pid_ns(const struct inode *inode)
static inline struct pid_namespace *proc_pid_ns(struct super_block *sb)
{
return inode->i_sb->s_fs_info;
return proc_sb_info(sb)->pid_ns;
}
bool proc_ns_file(const struct file *file);

View File

@ -506,6 +506,27 @@ static inline void hlist_replace_rcu(struct hlist_node *old,
WRITE_ONCE(old->pprev, LIST_POISON2);
}
/**
* hlists_swap_heads_rcu - swap the lists the hlist heads point to
* @left: The hlist head on the left
* @right: The hlist head on the right
*
* The lists start out as [@left ][node1 ... ] and
[@right ][node2 ... ]
* The lists end up as [@left ][node2 ... ]
* [@right ][node1 ... ]
*/
static inline void hlists_swap_heads_rcu(struct hlist_head *left, struct hlist_head *right)
{
struct hlist_node *node1 = left->first;
struct hlist_node *node2 = right->first;
rcu_assign_pointer(left->first, node2);
rcu_assign_pointer(right->first, node1);
WRITE_ONCE(node2->pprev, &left->first);
WRITE_ONCE(node1->pprev, &right->first);
}
/*
* return the first or the next element in an RCU protected hlist
*/

View File

@ -654,17 +654,6 @@ static inline bool thread_group_leader(struct task_struct *p)
return p->exit_signal >= 0;
}
/* Do to the insanities of de_thread it is possible for a process
* to have the pid of the thread group leader without actually being
* the thread group leader. For iteration through the pids in proc
* all we care about is that we have a task with the appropriate
* pid, we don't actually care if we have the right task.
*/
static inline bool has_group_leader_pid(struct task_struct *p)
{
return task_pid(p) == task_tgid(p);
}
static inline
bool same_thread_group(struct task_struct *p1, struct task_struct *p2)
{

View File

@ -1759,7 +1759,7 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
pid_t nr = -1;
if (likely(pid_has_task(pid, PIDTYPE_PID))) {
ns = proc_pid_ns(file_inode(m->file));
ns = proc_pid_ns(file_inode(m->file)->i_sb);
nr = pid_nr_ns(pid, ns);
}

View File

@ -363,6 +363,25 @@ void change_pid(struct task_struct *task, enum pid_type type,
attach_pid(task, type);
}
void exchange_tids(struct task_struct *left, struct task_struct *right)
{
struct pid *pid1 = left->thread_pid;
struct pid *pid2 = right->thread_pid;
struct hlist_head *head1 = &pid1->tasks[PIDTYPE_PID];
struct hlist_head *head2 = &pid2->tasks[PIDTYPE_PID];
/* Swap the single entry tid lists */
hlists_swap_heads_rcu(head1, head2);
/* Swap the per task_struct pid */
rcu_assign_pointer(left->thread_pid, pid2);
rcu_assign_pointer(right->thread_pid, pid1);
/* Swap the cached value */
WRITE_ONCE(left->pid, pid_nr(pid2));
WRITE_ONCE(right->pid, pid_nr(pid1));
}
/* transfer_pid is an optimization of attach_pid(new), detach_pid(old) */
void transfer_pid(struct task_struct *old, struct task_struct *new,
enum pid_type type)
@ -476,8 +495,7 @@ pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
rcu_read_lock();
if (!ns)
ns = task_active_pid_ns(current);
if (likely(pid_alive(task)))
nr = pid_nr_ns(rcu_dereference(*task_pid_ptr(task, type)), ns);
nr = pid_nr_ns(rcu_dereference(*task_pid_ptr(task, type)), ns);
rcu_read_unlock();
return nr;

View File

@ -47,85 +47,65 @@ void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new)
/*
* Functions for validating access to tasks.
*/
static struct task_struct *lookup_task(const pid_t pid, bool thread,
bool gettime)
static struct pid *pid_for_clock(const clockid_t clock, bool gettime)
{
struct task_struct *p;
const bool thread = !!CPUCLOCK_PERTHREAD(clock);
const pid_t upid = CPUCLOCK_PID(clock);
struct pid *pid;
if (CPUCLOCK_WHICH(clock) >= CPUCLOCK_MAX)
return NULL;
/*
* If the encoded PID is 0, then the timer is targeted at current
* or the process to which current belongs.
*/
if (upid == 0)
return thread ? task_pid(current) : task_tgid(current);
pid = find_vpid(upid);
if (!pid)
return thread ? current : current->group_leader;
return NULL;
p = find_task_by_vpid(pid);
if (!p)
return p;
if (thread)
return same_thread_group(p, current) ? p : NULL;
if (gettime) {
/*
* For clock_gettime(PROCESS) the task does not need to be
* the actual group leader. tsk->sighand gives
* access to the group's clock.
*
* Timers need the group leader because they take a
* reference on it and store the task pointer until the
* timer is destroyed.
*/
return (p == current || thread_group_leader(p)) ? p : NULL;
if (thread) {
struct task_struct *tsk = pid_task(pid, PIDTYPE_PID);
return (tsk && same_thread_group(tsk, current)) ? pid : NULL;
}
/*
* For processes require that p is group leader.
* For clock_gettime(PROCESS) allow finding the process by
* with the pid of the current task. The code needs the tgid
* of the process so that pid_task(pid, PIDTYPE_TGID) can be
* used to find the process.
*/
return has_group_leader_pid(p) ? p : NULL;
}
if (gettime && (pid == task_pid(current)))
return task_tgid(current);
static struct task_struct *__get_task_for_clock(const clockid_t clock,
bool getref, bool gettime)
{
const bool thread = !!CPUCLOCK_PERTHREAD(clock);
const pid_t pid = CPUCLOCK_PID(clock);
struct task_struct *p;
if (CPUCLOCK_WHICH(clock) >= CPUCLOCK_MAX)
return NULL;
rcu_read_lock();
p = lookup_task(pid, thread, gettime);
if (p && getref)
get_task_struct(p);
rcu_read_unlock();
return p;
}
static inline struct task_struct *get_task_for_clock(const clockid_t clock)
{
return __get_task_for_clock(clock, true, false);
}
static inline struct task_struct *get_task_for_clock_get(const clockid_t clock)
{
return __get_task_for_clock(clock, true, true);
/*
* For processes require that pid identifies a process.
*/
return pid_has_task(pid, PIDTYPE_TGID) ? pid : NULL;
}
static inline int validate_clock_permissions(const clockid_t clock)
{
return __get_task_for_clock(clock, false, false) ? 0 : -EINVAL;
int ret;
rcu_read_lock();
ret = pid_for_clock(clock, false) ? 0 : -EINVAL;
rcu_read_unlock();
return ret;
}
static inline enum pid_type cpu_timer_pid_type(struct k_itimer *timer)
static inline enum pid_type clock_pid_type(const clockid_t clock)
{
return CPUCLOCK_PERTHREAD(timer->it_clock) ? PIDTYPE_PID : PIDTYPE_TGID;
return CPUCLOCK_PERTHREAD(clock) ? PIDTYPE_PID : PIDTYPE_TGID;
}
static inline struct task_struct *cpu_timer_task_rcu(struct k_itimer *timer)
{
return pid_task(timer->it.cpu.pid, cpu_timer_pid_type(timer));
return pid_task(timer->it.cpu.pid, clock_pid_type(timer->it_clock));
}
/*
@ -373,15 +353,18 @@ static int posix_cpu_clock_get(const clockid_t clock, struct timespec64 *tp)
struct task_struct *tsk;
u64 t;
tsk = get_task_for_clock_get(clock);
if (!tsk)
rcu_read_lock();
tsk = pid_task(pid_for_clock(clock, true), clock_pid_type(clock));
if (!tsk) {
rcu_read_unlock();
return -EINVAL;
}
if (CPUCLOCK_PERTHREAD(clock))
t = cpu_clock_sample(clkid, tsk);
else
t = cpu_clock_sample_group(clkid, tsk, false);
put_task_struct(tsk);
rcu_read_unlock();
*tp = ns_to_timespec64(t);
return 0;
@ -394,19 +377,19 @@ static int posix_cpu_clock_get(const clockid_t clock, struct timespec64 *tp)
*/
static int posix_cpu_timer_create(struct k_itimer *new_timer)
{
struct task_struct *p = get_task_for_clock(new_timer->it_clock);
struct pid *pid;
if (!p)
rcu_read_lock();
pid = pid_for_clock(new_timer->it_clock, false);
if (!pid) {
rcu_read_unlock();
return -EINVAL;
}
new_timer->kclock = &clock_posix_cpu;
timerqueue_init(&new_timer->it.cpu.node);
new_timer->it.cpu.pid = get_task_pid(p, cpu_timer_pid_type(new_timer));
/*
* get_task_for_clock() took a reference on @p. Drop it as the timer
* holds a reference on the pid of @p.
*/
put_task_struct(p);
new_timer->it.cpu.pid = get_pid(pid);
rcu_read_unlock();
return 0;
}

View File

@ -779,7 +779,7 @@ static void *ip6fl_seq_start(struct seq_file *seq, loff_t *pos)
{
struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
state->pid_ns = proc_pid_ns(file_inode(seq->file));
state->pid_ns = proc_pid_ns(file_inode(seq->file)->i_sb);
rcu_read_lock_bh();
return *pos ? ip6fl_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;

View File

@ -7,6 +7,7 @@
#include "common.h"
#include <linux/magic.h>
#include <linux/proc_fs.h>
/**
* tomoyo_encode2 - Encode binary string to ascii string.
@ -161,9 +162,10 @@ static char *tomoyo_get_local_path(struct dentry *dentry, char * const buffer,
if (sb->s_magic == PROC_SUPER_MAGIC && *pos == '/') {
char *ep;
const pid_t pid = (pid_t) simple_strtoul(pos + 1, &ep, 10);
struct pid_namespace *proc_pidns = proc_pid_ns(sb);
if (*ep == '/' && pid && pid ==
task_tgid_nr_ns(current, sb->s_fs_info)) {
task_tgid_nr_ns(current, proc_pidns)) {
pos = ep - 5;
if (pos < buffer)
goto out;

View File

@ -2,7 +2,9 @@
/fd-001-lookup
/fd-002-posix-eq
/fd-003-kthread
/proc-fsconfig-hidepid
/proc-loadavg-001
/proc-multiple-procfs
/proc-pid-vm
/proc-self-map-files-001
/proc-self-map-files-002

View File

@ -19,5 +19,7 @@ TEST_GEN_PROGS += self
TEST_GEN_PROGS += setns-dcache
TEST_GEN_PROGS += setns-sysvipc
TEST_GEN_PROGS += thread-self
TEST_GEN_PROGS += proc-multiple-procfs
TEST_GEN_PROGS += proc-fsconfig-hidepid
include ../lib.mk

View File

@ -0,0 +1,50 @@
/*
* Copyright © 2020 Alexey Gladkov <gladkov.alexey@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/mount.h>
#include <linux/unistd.h>
static inline int fsopen(const char *fsname, unsigned int flags)
{
return syscall(__NR_fsopen, fsname, flags);
}
static inline int fsconfig(int fd, unsigned int cmd, const char *key, const void *val, int aux)
{
return syscall(__NR_fsconfig, fd, cmd, key, val, aux);
}
int main(void)
{
int fsfd, ret;
int hidepid = 2;
assert((fsfd = fsopen("proc", 0)) != -1);
ret = fsconfig(fsfd, FSCONFIG_SET_BINARY, "hidepid", &hidepid, 0);
assert(ret == -1);
assert(errno == EINVAL);
assert(!fsconfig(fsfd, FSCONFIG_SET_STRING, "hidepid", "2", 0));
assert(!fsconfig(fsfd, FSCONFIG_SET_STRING, "hidepid", "invisible", 0));
assert(!close(fsfd));
return 0;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright © 2020 Alexey Gladkov <gladkov.alexey@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
struct stat proc_st1, proc_st2;
char procbuff[] = "/tmp/proc.XXXXXX/meminfo";
char procdir1[] = "/tmp/proc.XXXXXX";
char procdir2[] = "/tmp/proc.XXXXXX";
assert(mkdtemp(procdir1) != NULL);
assert(mkdtemp(procdir2) != NULL);
assert(!mount("proc", procdir1, "proc", 0, "hidepid=1"));
assert(!mount("proc", procdir2, "proc", 0, "hidepid=2"));
snprintf(procbuff, sizeof(procbuff), "%s/meminfo", procdir1);
assert(!stat(procbuff, &proc_st1));
snprintf(procbuff, sizeof(procbuff), "%s/meminfo", procdir2);
assert(!stat(procbuff, &proc_st2));
umount(procdir1);
umount(procdir2);
assert(proc_st1.st_dev != proc_st2.st_dev);
return 0;
}