proc: modernize proc to support multiple private instances
Alexey Gladkov <gladkov.alexey@gmail.com> writes: Procfs modernization: --------------------- Historically procfs was always tied to pid namespaces, during pid namespace creation we internally create a procfs mount for it. However, this has the effect that all new procfs mounts are just a mirror of the internal one, any change, any mount option update, any new future introduction will propagate to all other procfs mounts that are in the same pid namespace. This may have solved several use cases in that time. However today we face new requirements, and making procfs able to support new private instances inside same pid namespace seems a major point. If we want to to introduce new features and security mechanisms we have to make sure first that we do not break existing usecases. Supporting private procfs instances will allow to support new features and behaviour without propagating it to all other procfs mounts. Today procfs is more of a burden especially to some Embedded, IoT, sandbox, container use cases. In user space we are over-mounting null or inaccessible files on top to hide files and information. If we want to hide pids we have to create PID namespaces otherwise mount options propagate to all other proc mounts, changing a mount option value in one mount will propagate to all other proc mounts. If we want to introduce new features, then they will propagate to all other mounts too, resulting either maybe new useful functionality or maybe breaking stuff. We have also to note that userspace should not workaround procfs, the kernel should just provide a sane simple interface. In this regard several developers and maintainers pointed out that there are problems with procfs and it has to be modernized: "Here's another one: split up and modernize /proc." by Andy Lutomirski [1] Discussion about kernel pointer leaks: "And yes, as Kees and Daniel mentioned, it's definitely not just dmesg. In fact, the primary things tend to be /proc and /sys, not dmesg itself." By Linus Torvalds [2] Lot of other areas in the kernel and filesystems have been updated to be able to support private instances, devpts is one major example [3]. Which will be used for: 1) Embedded systems and IoT: usually we have one supervisor for apps, we have some lightweight sandbox support, however if we create pid namespaces we have to manage all the processes inside too, where our goal is to be able to run a bunch of apps each one inside its own mount namespace, maybe use network namespaces for vlans setups, but right now we only want mount namespaces, without all the other complexity. We want procfs to behave more like a real file system, and block access to inodes that belong to other users. The 'hidepid=' will not work since it is a shared mount option. 2) Containers, sandboxes and Private instances of file systems - devpts case Historically, lot of file systems inside Linux kernel view when instantiated were just a mirror of an already created and mounted filesystem. This was the case of devpts filesystem, it seems at that time the requirements were to optimize things and reuse the same memory, etc. This design used to work but not anymore with today's containers, IoT, hostile environments and all the privacy challenges that Linux faces. In that regards, devpts was updated so that each new mounts is a total independent file system by the following patches: "devpts: Make each mount of devpts an independent filesystem" by Eric W. Biederman [3] [4] 3) Linux Security Modules have multiple ptrace paths inside some subsystems, however inside procfs, the implementation does not guarantee that the ptrace() check which triggers the security_ptrace_check() hook will always run. We have the 'hidepid' mount option that can be used to force the ptrace_may_access() check inside has_pid_permissions() to run. The problem is that 'hidepid' is per pid namespace and not attached to the mount point, any remount or modification of 'hidepid' will propagate to all other procfs mounts. This also does not allow to support Yama LSM easily in desktop and user sessions. Yama ptrace scope which restricts ptrace and some other syscalls to be allowed only on inferiors, can be updated to have a per-task context, where the context will be inherited during fork(), clone() and preserved across execve(). If we support multiple private procfs instances, then we may force the ptrace_may_access() on /proc/<pids>/ to always run inside that new procfs instances. This will allow to specifiy on user sessions if we should populate procfs with pids that the user can ptrace or not. By using Yama ptrace scope, some restricted users will only be able to see inferiors inside /proc, they won't even be able to see their other processes. Some software like Chromium, Firefox's crash handler, Wine and others are already using Yama to restrict which processes can be ptracable. With this change this will give the possibility to restrict /proc/<pids>/ but more importantly this will give desktop users a generic and usuable way to specifiy which users should see all processes and which user can not. Side notes: * This covers the lack of seccomp where it is not able to parse arguments, it is easy to install a seccomp filter on direct syscalls that operate on pids, however /proc/<pid>/ is a Linux ABI using filesystem syscalls. With this change all LSMs should be able to analyze open/read/write/close... on /proc/<pid>/ 4) This will allow to implement new features either in kernel or userspace without having to worry about procfs. In containers, sandboxes, etc we have workarounds to hide some /proc inodes, this should be supported natively without doing extra complex work, the kernel should be able to support sane options that work with today and future Linux use cases. 5) Creation of new superblock with all procfs options for each procfs mount will fix the ignoring of mount options. The problem is that the second mount of procfs in the same pid namespace ignores the mount options. The mount options are ignored without error until procfs is remounted. Before: proc /proc proc rw,relatime,hidepid=2 0 0 mount("proc", "/tmp/proc", "proc", 0, "hidepid=1") = 0 +++ exited with 0 +++ proc /proc proc rw,relatime,hidepid=2 0 0 proc /tmp/proc proc rw,relatime,hidepid=2 0 0 proc /proc proc rw,relatime,hidepid=1 0 0 proc /tmp/proc proc rw,relatime,hidepid=1 0 0 After: proc /proc proc rw,relatime,hidepid=ptraceable 0 0 proc /proc proc rw,relatime,hidepid=ptraceable 0 0 proc /tmp/proc proc rw,relatime,hidepid=invisible 0 0 Introduced changes: ------------------- Each mount of procfs creates a separate procfs instance with its own mount options. This series adds few new mount options: * New 'hidepid=ptraceable' or 'hidepid=4' mount option to show only ptraceable processes in the procfs. This allows to support lightweight sandboxes in Embedded Linux, also solves the case for LSM where now with this mount option, we make sure that they have a ptrace path in procfs. * 'subset=pid' that allows to hide non-pid inodes from procfs. It can be used in containers and sandboxes, as these are already trying to hide and block access to procfs inodes anyway. ChangeLog: ---------- * Rebase on top of v5.7-rc1. * Fix a resource leak if proc is not mounted or if proc is simply reconfigured. * Add few selftests. * After a discussion with Eric W. Biederman, the numerical values for hidepid parameter have been removed from uapi. * Remove proc_self and proc_thread_self from the pid_namespace struct. * I took into account the comment of Kees Cook. * Update Reviewed-by tags. * 'subset=pidfs' renamed to 'subset=pid' as suggested by Alexey Dobriyan. * Include Reviewed-by tags. * Rebase on top of Eric W. Biederman's procfs changes. * Add human readable values of 'hidepid' as suggested by Andy Lutomirski. * Started using RCU lock to clean dcache entries as suggested by Linus Torvalds. * 'pidonly=1' renamed to 'subset=pidfs' as suggested by Alexey Dobriyan. * HIDEPID_* moved to uapi/ as they are user interface to mount(). Suggested-by Alexey Dobriyan <adobriyan@gmail.com> * 'hidepid=' and 'gid=' mount options are moved from pid namespace to superblock. * 'newinstance' mount option removed as suggested by Eric W. Biederman. Mount of procfs always creates a new instance. * 'limit_pids' renamed to 'hidepid=3'. * I took into account the comment of Linus Torvalds [7]. * Documentation added. * Fixed a bug that caused a problem with the Fedora boot. * The 'pidonly' option is visible among the mount options. * Renamed mount options to 'newinstance' and 'pids=' Suggested-by: Andy Lutomirski <luto@kernel.org> * Fixed order of commit, Suggested-by: Andy Lutomirski <luto@kernel.org> * Many bug fixes. * Removed 'unshared' mount option and replaced it with 'limit_pids' which is attached to the current procfs mount. Suggested-by Andy Lutomirski <luto@kernel.org> * Do not fill dcache with pid entries that we can not ptrace. * Many bug fixes. References: ----------- [1] https://lists.linuxfoundation.org/pipermail/ksummit-discuss/2017-January/004215.html [2] http://www.openwall.com/lists/kernel-hardening/2017/10/05/5 [3] https://lwn.net/Articles/689539/ [4] http://lxr.free-electrons.com/source/Documentation/filesystems/devpts.txt?v=3.14 [5] https://lkml.org/lkml/2017/5/2/407 [6] https://lkml.org/lkml/2017/5/3/357 [7] https://lkml.org/lkml/2018/5/11/505 Alexey Gladkov (7): proc: rename struct proc_fs_info to proc_fs_opts proc: allow to mount many instances of proc in one pid namespace proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option proc: add option to mount only a pids subset docs: proc: add documentation for "hidepid=4" and "subset=pid" options and new mount behavior proc: use human-readable values for hidepid proc: use named enums for better readability Documentation/filesystems/proc.rst | 92 +++++++++--- fs/proc/base.c | 48 +++++-- fs/proc/generic.c | 9 ++ fs/proc/inode.c | 30 +++- fs/proc/root.c | 131 +++++++++++++----- fs/proc/self.c | 6 +- fs/proc/thread_self.c | 6 +- fs/proc_namespace.c | 14 +- include/linux/pid_namespace.h | 12 -- include/linux/proc_fs.h | 30 +++- tools/testing/selftests/proc/.gitignore | 2 + tools/testing/selftests/proc/Makefile | 2 + .../selftests/proc/proc-fsconfig-hidepid.c | 50 +++++++ .../selftests/proc/proc-multiple-procfs.c | 48 +++++++ 14 files changed, 384 insertions(+), 96 deletions(-) create mode 100644 tools/testing/selftests/proc/proc-fsconfig-hidepid.c create mode 100644 tools/testing/selftests/proc/proc-multiple-procfs.c Link: https://lore.kernel.org/lkml/20200419141057.621356-1-gladkov.alexey@gmail.com/ Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
This commit is contained in:
commit
0fb5ce62c5
|
@ -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
|
||||
=======
|
||||
|
||||
|
@ -2142,28 +2144,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
|
||||
|
|
|
@ -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
|
||||
|
@ -1897,7 +1905,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);
|
||||
|
@ -1907,7 +1915,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,
|
||||
|
@ -3300,6 +3308,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);
|
||||
|
||||
|
@ -3307,7 +3316,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)
|
||||
|
@ -3316,7 +3326,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;
|
||||
|
@ -3371,6 +3388,7 @@ retry:
|
|||
int proc_pid_readdir(struct file *file, struct dir_context *ctx)
|
||||
{
|
||||
struct tgid_iter iter;
|
||||
struct proc_fs_info *fs_info = proc_sb_info(file_inode(file)->i_sb);
|
||||
struct pid_namespace *ns = proc_pid_ns(file_inode(file));
|
||||
loff_t pos = ctx->pos;
|
||||
|
||||
|
@ -3378,13 +3396,13 @@ int proc_pid_readdir(struct file *file, struct dir_context *ctx)
|
|||
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;
|
||||
|
@ -3398,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);
|
||||
|
@ -3598,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);
|
||||
|
||||
|
@ -3608,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)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
133
fs/proc/root.c
133
fs/proc/root.c
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
@ -176,7 +204,7 @@ int open_related_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)
|
||||
{
|
||||
return inode->i_sb->s_fs_info;
|
||||
return proc_sb_info(inode->i_sb)->pid_ns;
|
||||
}
|
||||
|
||||
#endif /* _LINUX_PROC_FS_H */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue