2017-04-25 12:24:34

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 0/6] proc: support private proc instances per pidnamespace

Hi,

This is RFC v2 to modernize procfs and make it able to support multiple
private instances per the same pid namespace. RFC v1 is here [1]

This RFC v2 can be applied on top of next-20170424

Historically procfs was tied to pid namespaces, and mount options were
propagated to all other procfs instances in the same pid namespace. This
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. This was discussed
previously with Andy Lutomirski and the conclusion was to go into this
direction.


1) The main aim of this work is to have on embedded systems one
supervisor for apps. Right now we have some lightweight sandbox support,
however if we create pid namespacess we have to manages 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 without being able to
notice each other. We only want to use mount namespaces, and we want
procfs to behave more like a real mount point.


2) 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 users 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 LSMs should be able to analyze
open/read/write/close...


3) This will modernize procfs and align it with all other filesystems
and subsystems that have been updated recently to be able to work in a
flexible way. This is the same as devpts where each mount now is a distinct
filesystem such that ptys and their indicies allocated in one mount are
independent from ptys and their indicies in all other mounts.

We have to align procfs and modernize it to have a per mount context
where at least the mount option do not propagate to all other mounts,
then maybe we can continue to implement new features. One example is to
require CAP_SYS_ADMIN in the init user namespace on some /proc/* which are
not pids and which are are not virtualized by design, or CAP_NET_ADMIN
inside userns on the net bits that are virtualized, etc.
These mount options won't propagate to previous mounts, and the system
will continue to be usable.


This adds a new mount option 'limit_pids' that can be renamed to
whatever is best, when it is passed it will automatically instruct
procfs internally to create a new instance.

How to test:
$ sudo mount -t proc -o limit_pids=1 none /test


Note for userspace that should be documented:
If you are over mounting /proc, then make sure you are in a new mount
namespace where propagation to master is disconnected. This will avoid
to pin that new /proc mount.


# Changes since v1:
*) Removed 'unshared' mount option and replaced it with 'limit_pids'
which is attached to the current procfs mount.
Suggested-by Andy Lutomirski <[email protected]>
*) Do not fill dcache with pid entries that we can not ptrace.
*) Many bug fixes.


Djalal Harouni (6):
[PATCH 1/6] proc: add proc_fs_info struct to store proc information
[PATCH 2/6] proc: move /proc/{self|thread-self} dentries to proc_fs_info
[PATCH 3/6] proc: add helpers to set and get proc hidepid and gid
[PATCH 4/6] proc: support mounting private procfs instances inside
[PATCH 5/6] proc: instantiate only pids that we can ptrace on 'limit_pids=1' mount option
[PATCH 6/6] proc: flush task dcache entries from all procfs instances

fs/locks.c | 6 +-
fs/proc/base.c | 98 ++++++++++++++++-------
fs/proc/inode.c | 22 +++++-
fs/proc/internal.h | 2 +-
fs/proc/root.c | 176 ++++++++++++++++++++++++++++++++++++++----
fs/proc/self.c | 9 ++-
fs/proc/thread_self.c | 9 ++-
fs/proc_namespace.c | 14 ++--
include/linux/pid_namespace.h | 46 ++++++++++-
include/linux/proc_fs.h | 106 +++++++++++++++++++++++++
10 files changed, 422 insertions(+), 66 deletions(-)


[1] https://lkml.org/lkml/2017/3/30/670

Thanks!


2017-04-25 12:24:48

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 1/6] proc: add proc_fs_info struct to store proc information

This is a preparation patch that adds proc_fs_info to be able to store
different procfs options and informations. Right now some mount options
are stored inside the pid namespace which makes it hard to change or
modernize procfs without affecting pid namespaces. Plus we do want to
treat proc as more of a real mount point and filesystem. procfs is part
of Linux API where it offers some features using filesystem syscalls and
in order to support some features where we are able to have multiple
instances of procfs, each one with its mount options inside the same pid
namespace, we have to separate these procfs instances.

This is the same feature that was also added to other Linux interfaces
like devpts in order to support containers, sandboxes, and to have
multiple instances of devpts filesystem [1].

[1] http://lxr.free-electrons.com/source/Documentation/filesystems/devpts.txt?v=3.14

Cc: Kees Cook <[email protected]>
Suggested-by: Andy Lutomirski <[email protected]>
Signed-off-by: Djalal Harouni <[email protected]>
---
fs/locks.c | 6 +++--
fs/proc/base.c | 31 +++++++++++++----------
fs/proc/inode.c | 8 +++---
fs/proc/root.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++---
fs/proc/self.c | 8 +++---
fs/proc/thread_self.c | 6 +++--
fs/proc_namespace.c | 14 +++++------
include/linux/proc_fs.h | 11 +++++++++
8 files changed, 115 insertions(+), 34 deletions(-)

diff --git a/fs/locks.c b/fs/locks.c
index af2031a..fb90fc5 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -2617,7 +2617,8 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl,
unsigned int fl_pid;

if (fl->fl_nspid) {
- struct pid_namespace *proc_pidns = file_inode(f->file)->i_sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(file_inode(f->file)->i_sb);
+ struct pid_namespace *proc_pidns = fs_info->pid_ns;

/* Don't let fl_pid change based on who is reading the file */
fl_pid = pid_nr_ns(fl->fl_nspid, proc_pidns);
@@ -2701,7 +2702,8 @@ 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 proc_fs_info *fs_info = proc_sb(file_inode(f->file)->i_sb);
+ struct pid_namespace *proc_pidns = fs_info->pid_ns;

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

diff --git a/fs/proc/base.c b/fs/proc/base.c
index ea1039d..fa7d725 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -695,7 +695,8 @@ static bool has_pid_permissions(struct pid_namespace *pid,

static int proc_pid_permission(struct inode *inode, int mask)
{
- struct pid_namespace *pid = inode->i_sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *pid = fs_info->pid_ns;
struct task_struct *task;
bool has_perms;

@@ -730,12 +731,12 @@ 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;
struct pid *pid;
struct task_struct *task;
int ret;

- ns = inode->i_sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;
pid = proc_pid(inode);
task = get_pid_task(pid, PIDTYPE_PID);
if (!task)
@@ -1774,9 +1775,10 @@ struct inode *proc_pid_make_inode(struct super_block * sb,
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 task_struct *task;
- struct pid_namespace *pid = path->dentry->d_sb->s_fs_info;
+ struct inode *inode = d_inode(path->dentry);
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *pid = fs_info->pid_ns;

generic_fillattr(inode, stat);

@@ -2291,6 +2293,8 @@ static const struct seq_operations proc_timers_seq_ops = {
static int proc_timers_open(struct inode *inode, struct file *file)
{
struct timers_private *tp;
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;

tp = __seq_open_private(file, &proc_timers_seq_ops,
sizeof(struct timers_private));
@@ -2298,7 +2302,7 @@ static int proc_timers_open(struct inode *inode, struct file *file)
return -ENOMEM;

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

@@ -3132,13 +3136,13 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, unsign
int result = -ENOENT;
struct task_struct *task;
unsigned tgid;
- struct pid_namespace *ns;
+ struct proc_fs_info *fs_info = proc_sb(dir->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;

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

- ns = dentry->d_sb->s_fs_info;
rcu_read_lock();
task = find_task_by_pid_ns(tgid, ns);
if (task)
@@ -3202,7 +3206,8 @@ 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 = file_inode(file)->i_sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(file_inode(file)->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;
loff_t pos = ctx->pos;

if (pos >= PID_MAX_LIMIT + TGID_OFFSET)
@@ -3430,7 +3435,8 @@ 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 pid_namespace *ns;
+ struct proc_fs_info *fs_info = proc_sb(dentry->d_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;

if (!leader)
goto out_no_task;
@@ -3439,7 +3445,6 @@ static struct dentry *proc_task_lookup(struct inode *dir, struct dentry * dentry
if (tid == ~0U)
goto out;

- ns = dentry->d_sb->s_fs_info;
rcu_read_lock();
task = find_task_by_pid_ns(tid, ns);
if (task)
@@ -3541,7 +3546,8 @@ static int proc_task_readdir(struct file *file, struct dir_context *ctx)
{
struct inode *inode = file_inode(file);
struct task_struct *task;
- struct pid_namespace *ns;
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;
int tid;

if (proc_inode_is_dead(inode))
@@ -3553,7 +3559,6 @@ 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 = inode->i_sb->s_fs_info;
tid = (int)file->f_version;
file->f_version = 0;
for (task = first_tid(proc_pid(inode), tid, ctx->pos - 2, ns);
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index e250910..64cd0e2 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -103,7 +103,8 @@ void __init proc_init_inodecache(void)
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(sb);
+ struct pid_namespace *pid = fs_info->pid_ns;

if (!gid_eq(pid->pid_gid, GLOBAL_ROOT_GID))
seq_printf(seq, ",gid=%u", from_kgid_munged(&init_user_ns, pid->pid_gid));
@@ -473,7 +474,8 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)

int proc_fill_super(struct super_block *s, void *data, int silent)
{
- struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
+ struct proc_fs_info *fs_info = proc_sb(s);
+ struct pid_namespace *ns = get_pid_ns(fs_info->pid_ns);
struct inode *root_inode;
int ret;

@@ -495,7 +497,7 @@ int proc_fill_super(struct super_block *s, void *data, int silent)
* top of it
*/
s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
-
+
pde_get(&proc_root);
root_inode = proc_get_inode(s, &proc_root);
if (!root_inode) {
diff --git a/fs/proc/root.c b/fs/proc/root.c
index deecb39..a625361 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -15,6 +15,7 @@
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/sched/stat.h>
+#include <linux/slab.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/user_namespace.h>
@@ -79,16 +80,45 @@ int proc_parse_options(char *options, struct pid_namespace *pid)

int proc_remount(struct super_block *sb, int *flags, char *data)
{
- struct pid_namespace *pid = sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(sb);
+ struct pid_namespace *pid = fs_info->pid_ns;

sync_filesystem(sb);
return !proc_parse_options(data, pid);
}

+static int proc_test_super(struct super_block *s, void *data)
+{
+ struct proc_fs_info *p = data;
+ struct proc_fs_info *fs_info = proc_sb(s);
+
+ return p->pid_ns == fs_info->pid_ns;
+}
+
+static int proc_set_super(struct super_block *sb, void *data)
+{
+ sb->s_fs_info = data;
+ return set_anon_super(sb, NULL);
+}
+
static struct dentry *proc_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
+ int error;
+ struct super_block *sb;
struct pid_namespace *ns;
+ struct proc_fs_info *fs_info;
+
+ /*
+ * Don't allow mounting unless the caller has CAP_SYS_ADMIN over
+ * the namespace.
+ */
+ if (!(flags & MS_KERNMOUNT) && !ns_capable(current_user_ns(), CAP_SYS_ADMIN))
+ return ERR_PTR(-EPERM);
+
+ fs_info = kzalloc(sizeof(*fs_info), GFP_NOFS);
+ if (!fs_info)
+ return ERR_PTR(-ENOMEM);

if (flags & MS_KERNMOUNT) {
ns = data;
@@ -97,20 +127,47 @@ static struct dentry *proc_mount(struct file_system_type *fs_type,
ns = task_active_pid_ns(current);
}

- return mount_ns(fs_type, flags, data, ns, ns->user_ns, proc_fill_super);
+ fs_info->pid_ns = ns;
+
+ sb = sget_userns(fs_type, proc_test_super, proc_set_super, flags,
+ ns->user_ns, fs_info);
+ if (IS_ERR(sb)) {
+ error = PTR_ERR(sb);
+ goto error_fs_info;
+ }
+
+ if (sb->s_root) {
+ kfree(fs_info);
+ } else {
+ error = proc_fill_super(sb, data, flags & MS_SILENT ? 1 : 0);
+ if (error) {
+ deactivate_locked_super(sb);
+ goto error;
+ }
+
+ sb->s_flags |= MS_ACTIVE;
+ }
+
+ return dget(sb->s_root);
+
+error_fs_info:
+ kfree(fs_info);
+error:
+ return ERR_PTR(error);
}

static void proc_kill_sb(struct super_block *sb)
{
- struct pid_namespace *ns;
+ struct proc_fs_info *fs_info = proc_sb(sb);
+ struct pid_namespace *ns = (struct pid_namespace *)fs_info->pid_ns;

- 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);
put_pid_ns(ns);
+ kfree(fs_info);
}

static struct file_system_type proc_fs_type = {
diff --git a/fs/proc/self.c b/fs/proc/self.c
index 39857f6..9f95174 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -10,7 +10,8 @@ static const char *proc_self_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
- struct pid_namespace *ns = inode->i_sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;
pid_t tgid = task_tgid_nr_ns(current, ns);
char *name;

@@ -34,9 +35,10 @@ static unsigned self_inum;
int proc_setup_self(struct super_block *s)
{
struct inode *root_inode = d_inode(s->s_root);
- struct pid_namespace *ns = s->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(s);
+ struct pid_namespace *ns = fs_info->pid_ns;
struct dentry *self;
-
+
inode_lock(root_inode);
self = d_alloc_name(s->s_root, "self");
if (self) {
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 20614b6..13d9aef 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -10,7 +10,8 @@ static const char *proc_thread_self_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
- struct pid_namespace *ns = inode->i_sb->s_fs_info;
+ struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
+ struct pid_namespace *ns = fs_info->pid_ns;
pid_t tgid = task_tgid_nr_ns(current, ns);
pid_t pid = task_pid_nr_ns(current, ns);
char *name;
@@ -34,8 +35,9 @@ static unsigned thread_self_inum;

int proc_setup_thread_self(struct super_block *s)
{
+ struct proc_fs_info *fs_info = proc_sb(s);
+ struct pid_namespace *ns = fs_info->pid_ns;
struct inode *root_inode = d_inode(s->s_root);
- struct pid_namespace *ns = s->s_fs_info;
struct dentry *thread_self;

inode_lock(root_inode);
diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c
index b5713fe..d0ae937 100644
--- a/fs/proc_namespace.c
+++ b/fs/proc_namespace.c
@@ -36,23 +36,23 @@ static unsigned 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[] = {
{ MS_SYNCHRONOUS, ",sync" },
{ MS_DIRSYNC, ",dirsync" },
{ MS_MANDLOCK, ",mand" },
{ MS_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);
}
@@ -62,7 +62,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" },
@@ -71,9 +71,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);
}
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 2d2bf59..dd1dadd 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -6,11 +6,21 @@

#include <linux/types.h>
#include <linux/fs.h>
+#include <linux/refcount.h>
+
+struct proc_fs_info {
+ struct pid_namespace *pid_ns;
+};

struct proc_dir_entry;

#ifdef CONFIG_PROC_FS

+static inline struct proc_fs_info *proc_sb(struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
extern void proc_root_init(void);
extern void proc_flush_task(struct task_struct *);

@@ -53,6 +63,7 @@ static inline void proc_flush_task(struct task_struct *task)
{
}

+extern inline struct proc_fs_info *proc_sb(struct super_block *sb) { return NULL;}
static inline struct proc_dir_entry *proc_symlink(const char *name,
struct proc_dir_entry *parent,const char *dest) { return NULL;}
static inline struct proc_dir_entry *proc_mkdir(const char *name,
--
2.10.2

2017-04-25 12:24:57

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 2/6] proc: move /proc/{self|thread-self} dentries to proc_fs_info

This is a preparation patch that moves /proc/{self|thread-self} dentries
to be stored inside procfs fs_info struct instead of making them per pid
namespace. Since we want to support multiple procfs instances we need to
make sure that these dentries are also per-superblock instead of
per-pidns, unmounting a private procfs won't clash with other procfs
mounts.

Cc: Kees Cook <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Signed-off-by: Djalal Harouni <[email protected]>
---
fs/proc/base.c | 4 ++--
fs/proc/root.c | 8 ++++----
fs/proc/self.c | 3 +--
fs/proc/thread_self.c | 5 ++---
include/linux/pid_namespace.h | 4 +---
include/linux/proc_fs.h | 2 ++
6 files changed, 12 insertions(+), 14 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index fa7d725..c96a9c6 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -3214,13 +3214,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;
diff --git a/fs/proc/root.c b/fs/proc/root.c
index a625361..f44b864 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -161,10 +161,10 @@ static void proc_kill_sb(struct super_block *sb)
struct proc_fs_info *fs_info = proc_sb(sb);
struct pid_namespace *ns = (struct pid_namespace *)fs_info->pid_ns;

- if (ns->proc_self)
- dput(ns->proc_self);
- if (ns->proc_thread_self)
- dput(ns->proc_thread_self);
+ if (fs_info->proc_self)
+ dput(fs_info->proc_self);
+ if (fs_info->proc_thread_self)
+ dput(fs_info->proc_thread_self);
kill_anon_super(sb);
put_pid_ns(ns);
kfree(fs_info);
diff --git a/fs/proc/self.c b/fs/proc/self.c
index 9f95174..d3aaf71 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -36,7 +36,6 @@ int proc_setup_self(struct super_block *s)
{
struct inode *root_inode = d_inode(s->s_root);
struct proc_fs_info *fs_info = proc_sb(s);
- struct pid_namespace *ns = fs_info->pid_ns;
struct dentry *self;

inode_lock(root_inode);
@@ -63,7 +62,7 @@ int proc_setup_self(struct super_block *s)
pr_err("proc_fill_super: can't allocate /proc/self\n");
return PTR_ERR(self);
}
- ns->proc_self = self;
+ fs_info->proc_self = self;
return 0;
}

diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 13d9aef..14ea8e6 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -36,7 +36,6 @@ static unsigned thread_self_inum;
int proc_setup_thread_self(struct super_block *s)
{
struct proc_fs_info *fs_info = proc_sb(s);
- struct pid_namespace *ns = fs_info->pid_ns;
struct inode *root_inode = d_inode(s->s_root);
struct dentry *thread_self;

@@ -61,10 +60,10 @@ int proc_setup_thread_self(struct super_block *s)
}
inode_unlock(root_inode);
if (IS_ERR(thread_self)) {
- pr_err("proc_fill_super: can't allocate /proc/thread_self\n");
+ pr_err("proc_fill_super: can't allocate /proc/thread-self\n");
return PTR_ERR(thread_self);
}
- ns->proc_thread_self = thread_self;
+ fs_info->proc_thread_self = thread_self;
return 0;
}

diff --git a/include/linux/pid_namespace.h b/include/linux/pid_namespace.h
index c2a989d..306bdc6 100644
--- a/include/linux/pid_namespace.h
+++ b/include/linux/pid_namespace.h
@@ -38,9 +38,7 @@ struct pid_namespace {
unsigned int level;
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
- struct vfsmount *proc_mnt;
- struct dentry *proc_self;
- struct dentry *proc_thread_self;
+ struct vfsmount *proc_mnt; /* Internal proc mounted during each new pidns */
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
struct fs_pin *bacct;
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index dd1dadd..7ba8540 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -10,6 +10,8 @@

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/ */
};

struct proc_dir_entry;
--
2.10.2

2017-04-25 12:25:15

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 3/6] proc: add helpers to set and get proc hidepid and gid mount options

This is a cleaning patch to add helpers to set and get proc mount
options instead of directly using them. This make it easy to track
what's happening and easy to update in future.

Cc: Kees Cook <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Signed-off-by: Djalal Harouni <[email protected]>
---
fs/proc/base.c | 16 +++++++++-------
fs/proc/inode.c | 5 +++--
fs/proc/internal.h | 2 +-
fs/proc/root.c | 15 ++++++++++-----
include/linux/proc_fs.h | 44 ++++++++++++++++++++++++++++++++++++++++++--
5 files changed, 65 insertions(+), 17 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index c96a9c6..3351275 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -681,13 +681,16 @@ 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)
{
- if (pid->hide_pid < hide_pid_min)
+ int hide_pid = proc_fs_hide_pid(fs_info);
+ kgid_t gid = proc_fs_pid_gid(fs_info);
+
+ if (hide_pid < hide_pid_min)
return true;
- if (in_group_p(pid->pid_gid))
+ if (in_group_p(gid))
return true;
return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
}
@@ -703,7 +706,7 @@ static int proc_pid_permission(struct inode *inode, int mask)
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) {
@@ -1778,7 +1781,6 @@ int pid_getattr(const struct path *path, struct kstat *stat,
struct task_struct *task;
struct inode *inode = d_inode(path->dentry);
struct proc_fs_info *fs_info = proc_sb(inode->i_sb);
- struct pid_namespace *pid = fs_info->pid_ns;

generic_fillattr(inode, stat);

@@ -1787,7 +1789,7 @@ int pid_getattr(const struct path *path, struct kstat *stat,
stat->gid = GLOBAL_ROOT_GID;
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,
@@ -3234,7 +3236,7 @@ int proc_pid_readdir(struct file *file, struct dir_context *ctx)
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), "%d", iter.tgid);
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 64cd0e2..7c0e122 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -475,11 +475,12 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
int proc_fill_super(struct super_block *s, void *data, int silent)
{
struct proc_fs_info *fs_info = proc_sb(s);
- struct pid_namespace *ns = get_pid_ns(fs_info->pid_ns);
struct inode *root_inode;
int ret;

- if (!proc_parse_options(data, ns))
+ get_pid_ns(fs_info->pid_ns);
+
+ if (!proc_parse_options(data, fs_info))
return -EINVAL;

/* User space would break if executables or devices appear on proc */
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index c5ae09b..126fa31 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -261,7 +261,7 @@ static inline void proc_tty_init(void) {}
* root.c
*/
extern struct proc_dir_entry proc_root;
-extern int proc_parse_options(char *options, struct pid_namespace *pid);
+extern int proc_parse_options(char *options, struct proc_fs_info *fs_info);

extern void proc_self_init(void);
extern int proc_remount(struct super_block *, int *, char *);
diff --git a/fs/proc/root.c b/fs/proc/root.c
index f44b864..a76ceb0 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -36,11 +36,12 @@ static const match_table_t tokens = {
{Opt_err, NULL},
};

-int proc_parse_options(char *options, struct pid_namespace *pid)
+int proc_parse_options(char *options, struct proc_fs_info *fs_info)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
+ kgid_t gid;

if (!options)
return 1;
@@ -56,7 +57,12 @@ int proc_parse_options(char *options, struct pid_namespace *pid)
case Opt_gid:
if (match_int(&args[0], &option))
return 0;
- pid->pid_gid = make_kgid(current_user_ns(), option);
+ gid = make_kgid(current_user_ns(), option);
+ if (!gid_valid(gid)) {
+ pr_err("proc: invalid gid mount option.\n");
+ return 0;
+ }
+ proc_fs_set_pid_gid(fs_info, gid);
break;
case Opt_hidepid:
if (match_int(&args[0], &option))
@@ -66,7 +72,7 @@ int proc_parse_options(char *options, struct pid_namespace *pid)
pr_err("proc: hidepid value must be between 0 and 2.\n");
return 0;
}
- pid->hide_pid = option;
+ proc_fs_set_hide_pid(fs_info, option);
break;
default:
pr_err("proc: unrecognized mount option \"%s\" "
@@ -81,10 +87,9 @@ int proc_parse_options(char *options, struct pid_namespace *pid)
int proc_remount(struct super_block *sb, int *flags, char *data)
{
struct proc_fs_info *fs_info = proc_sb(sb);
- struct pid_namespace *pid = fs_info->pid_ns;

sync_filesystem(sb);
- return !proc_parse_options(data, pid);
+ return !proc_parse_options(data, fs_info);
}

static int proc_test_super(struct super_block *s, void *data)
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 7ba8540..7a8d641 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -7,6 +7,10 @@
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/refcount.h>
+#include <linux/pid_namespace.h>
+
+struct proc_dir_entry;
+struct pid_namespace;

struct proc_fs_info {
struct pid_namespace *pid_ns;
@@ -14,8 +18,6 @@ struct proc_fs_info {
struct dentry *proc_thread_self; /* For /proc/thread-self/ */
};

-struct proc_dir_entry;
-
#ifdef CONFIG_PROC_FS

static inline struct proc_fs_info *proc_sb(struct super_block *sb)
@@ -23,6 +25,26 @@ static inline struct proc_fs_info *proc_sb(struct super_block *sb)
return sb->s_fs_info;
}

+static inline void proc_fs_set_hide_pid(struct proc_fs_info *fs_info, int hide_pid)
+{
+ fs_info->pid_ns->hide_pid = hide_pid;
+}
+
+static inline void proc_fs_set_pid_gid(struct proc_fs_info *fs_info, kgid_t gid)
+{
+ fs_info->pid_ns->pid_gid = gid;
+}
+
+static inline int proc_fs_hide_pid(struct proc_fs_info *fs_info)
+{
+ return fs_info->pid_ns->hide_pid;
+}
+
+static inline kgid_t proc_fs_pid_gid(struct proc_fs_info *fs_info)
+{
+ return fs_info->pid_ns->pid_gid;
+}
+
extern void proc_root_init(void);
extern void proc_flush_task(struct task_struct *);

@@ -65,6 +87,24 @@ static inline void proc_flush_task(struct task_struct *task)
{
}

+static inline void proc_fs_set_hide_pid(struct proc_fs_info *fs_info, int hide_pid)
+{
+}
+
+static inline void proc_fs_set_pid_gid(struct proc_info_fs *fs_info, kgid_t gid)
+{
+}
+
+static inline int proc_fs_hide_pid(struct proc_fs_info *fs_info)
+{
+ return 0;
+}
+
+extern kgid_t proc_fs_pid_gid(struct proc_fs_info *fs_info)
+{
+ return GLOBAL_ROOT_GID;
+}
+
extern inline struct proc_fs_info *proc_sb(struct super_block *sb) { return NULL;}
static inline struct proc_dir_entry *proc_symlink(const char *name,
struct proc_dir_entry *parent,const char *dest) { return NULL;}
--
2.10.2

2017-04-25 12:25:27

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 4/6] proc: support mounting private procfs instances inside same pid namespace

This patch allows to have multiple private procfs instances inside the
same pid namespace. Lot of other areas in the kernel and filesystems
have been updated to be able to support private instances, devpts is one
major example. The aim here is lightweight sandboxes, and to allow that we
have to modernize procfs internals.

1) The main aim of this work is to have on embedded systems one
supervisor for apps. Right now we have some lightweight sandbox support,
however if we create pid namespacess we have to manages 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 without being able to
notice each other. We only want to use mount namespaces, and we want
procfs to behave more like a real mount point.

2) 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 users 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 LSMs should be able to analyze
open/read/write/close...

3) This will modernize procfs and align it with all other filesystems
and subsystems that have been updated recently to be able to work in a
flexible way. This is the same as devpts where each mount now is a distinct
filesystem such that ptys and their indicies allocated in one mount are
independent from ptys and their indicies in all other mounts.

We have to align procfs and modernize it to have a per mount context
where at least the mount option do not propagate to all other mounts,
then maybe we can continue to implement new features. One example is to
require CAP_SYS_ADMIN in the init user namespace on some /proc/* which are
not pids and which are are not virtualized by design, or CAP_NET_ADMIN
inside userns on the net bits that are virtualized, etc.
These mount options won't propagate to previous mounts, and the system
will continue to be usable.

Ths patch introduces the new 'limit_pids' mount option as it was also
suggesed by Andy Lutomirski [1]. When this option is passed we
automatically create a private procfs instance. This is not the default
behaviour since we do not want to break userspace and we do not want to
provide different devices IDs by default, please see [1] for why.

* If 'limit_pids=0' this will create a private procfs instance without
any restrictions.

* If 'limit_pids=1' this will create a private procfs instance where
processes will only be able to see pids that they can ptrace inside
/proc/

This allows to have a stable LSM path later inside has_pid_permissions()
to make sure that processes are not using filesystem syscall on
/proc/pid to inspect or write to pid where other mechanisms are supposed
to prevent this. This allows to align filesystem syscalls that go
through procfs on pids with the other syscalls that can be blocked by
seccomp filters.

Later Yama LSM can be updated to check that processes are able only
able to see their children inside /proc/.

[1] https://lkml.org/lkml/2017/3/31/324

Cc: Kees Cook <[email protected]>
Suggested-by: Andy Lutomirski <[email protected]>
Signed-off-by: Djalal Harouni <[email protected]>
---
fs/proc/base.c | 21 ++++++++----
fs/proc/inode.c | 4 +++
fs/proc/root.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++---
include/linux/proc_fs.h | 51 ++++++++++++++++++++++++++++
4 files changed, 153 insertions(+), 11 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index 3351275..2e0f661 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -685,13 +685,22 @@ static bool has_pid_permissions(struct proc_fs_info *fs_info,
struct task_struct *task,
int hide_pid_min)
{
- int hide_pid = proc_fs_hide_pid(fs_info);
- kgid_t gid = proc_fs_pid_gid(fs_info);
+ int limit_pids = proc_fs_limit_pids(fs_info);

- if (hide_pid < hide_pid_min)
- return true;
- if (in_group_p(gid))
- return true;
+ /*
+ * If 'limit_pids' mount is set force a ptrace check,
+ * we indicate that we are using a filesystem syscall
+ * by passing PTRACE_MODE_READ_FSCREDS
+ */
+ if (limit_pids == PROC_LIMIT_PIDS_OFF) {
+ int hide_pid = proc_fs_hide_pid(fs_info);
+ kgid_t gid = proc_fs_pid_gid(fs_info);
+
+ if (hide_pid < hide_pid_min)
+ return true;
+ if (in_group_p(gid))
+ return true;
+ }
return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
}

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 7c0e122..b4ee4a1 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -105,12 +105,16 @@ static int proc_show_options(struct seq_file *seq, struct dentry *root)
struct super_block *sb = root->d_sb;
struct proc_fs_info *fs_info = proc_sb(sb);
struct pid_namespace *pid = fs_info->pid_ns;
+ int limit_pids = proc_fs_limit_pids(fs_info);

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 (limit_pids > PROC_LIMIT_PIDS_OFF)
+ seq_printf(seq, ",limit_pids=%u", limit_pids);
+
return 0;
}

diff --git a/fs/proc/root.c b/fs/proc/root.c
index a76ceb0..f398c14 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -27,15 +27,66 @@
#include "internal.h"

enum {
- Opt_gid, Opt_hidepid, Opt_err,
+ Opt_gid, Opt_hidepid, Opt_limit_pids, Opt_err,
};

static const match_table_t tokens = {
{Opt_hidepid, "hidepid=%u"},
{Opt_gid, "gid=%u"},
+ {Opt_limit_pids, "limit_pids=%u"},
{Opt_err, NULL},
};

+/* We only parse 'limit_pids' option here */
+int proc_parse_early_options(char *options, struct proc_fs_info *fs_info)
+{
+ char *p, *opts, *orig;
+ substring_t args[MAX_OPT_ARGS];
+ int option, ret;
+
+ if (!options)
+ return 0;
+
+ opts = kstrdup(options, GFP_KERNEL);
+ if (!opts)
+ return -ENOMEM;
+
+ orig = opts;
+
+ while ((p = strsep(&opts, ",")) != NULL) {
+ int token;
+
+ if (!*p)
+ continue;
+
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case Opt_limit_pids:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ ret = proc_fs_set_limit_pids(fs_info, option);
+ if (ret < 0) {
+ pr_err("proc: faild to parse mount option "
+ "\"%s\" \n", p);
+ return ret;
+ }
+ proc_fs_set_newinstance(fs_info, true);
+ pr_info("proc: mounting a new procfs instance ");
+ break;
+ case Opt_gid:
+ case Opt_hidepid:
+ break;
+ default:
+ pr_err("proc: unrecognized mount option \"%s\" "
+ "or missing value\n", p);
+ return -EINVAL;
+ }
+ }
+
+ kfree(orig);
+ return 0;
+}
+
int proc_parse_options(char *options, struct proc_fs_info *fs_info)
{
char *p;
@@ -74,6 +125,8 @@ int proc_parse_options(char *options, struct proc_fs_info *fs_info)
}
proc_fs_set_hide_pid(fs_info, option);
break;
+ case Opt_limit_pids:
+ break;
default:
pr_err("proc: unrecognized mount option \"%s\" "
"or missing value\n", p);
@@ -86,18 +139,34 @@ int proc_parse_options(char *options, struct proc_fs_info *fs_info)

int proc_remount(struct super_block *sb, int *flags, char *data)
{
+ int error;
struct proc_fs_info *fs_info = proc_sb(sb);

sync_filesystem(sb);
+
+ /*
+ * If this is a new instance, then parse again the proc mount
+ * options.
+ */
+ if (proc_fs_newinstance(fs_info)) {
+ error = proc_parse_early_options(data, fs_info);
+ if (error < 0)
+ return error;
+ }
+
return !proc_parse_options(data, fs_info);
}

-static int proc_test_super(struct super_block *s, void *data)
+static int proc_test_super(struct super_block *sb, void *data)
{
struct proc_fs_info *p = data;
- struct proc_fs_info *fs_info = proc_sb(s);
+ struct proc_fs_info *fs_info = proc_sb(sb);

- return p->pid_ns == fs_info->pid_ns;
+ if (!proc_fs_newinstance(p) && !proc_fs_newinstance(fs_info) &&
+ p->pid_ns == fs_info->pid_ns)
+ return 1;
+
+ return 0;
}

static int proc_set_super(struct super_block *sb, void *data)
@@ -109,7 +178,7 @@ static int proc_set_super(struct super_block *sb, void *data)
static struct dentry *proc_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
- int error;
+ int error = 0;
struct super_block *sb;
struct pid_namespace *ns;
struct proc_fs_info *fs_info;
@@ -125,10 +194,19 @@ static struct dentry *proc_mount(struct file_system_type *fs_type,
if (!fs_info)
return ERR_PTR(-ENOMEM);

+ /* Set it as early as possible */
+ proc_fs_set_newinstance(fs_info, false);
+ proc_fs_set_limit_pids(fs_info, PROC_LIMIT_PIDS_OFF);
+
if (flags & MS_KERNMOUNT) {
ns = data;
data = NULL;
} else {
+ /* Parse early mount options if not a MS_KERNMOUNT */
+ error = proc_parse_early_options(data, fs_info);
+ if (error < 0)
+ goto error_fs_info;
+
ns = task_active_pid_ns(current);
}

diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 7a8d641..0fddb84 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -12,10 +12,17 @@
struct proc_dir_entry;
struct pid_namespace;

+enum { /* definitions for proc mount option limit_pids */
+ PROC_LIMIT_PIDS_OFF = 0, /* Limit pids is off */
+ PROC_LIMIT_PIDS_PTRACE = 1, /* Limit pids to only ptracable pids */
+};
+
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/ */
+ bool newinstance; /* Private flag for new separated instances */
+ int limit_pids:1;
};

#ifdef CONFIG_PROC_FS
@@ -35,6 +42,21 @@ static inline void proc_fs_set_pid_gid(struct proc_fs_info *fs_info, kgid_t gid)
fs_info->pid_ns->pid_gid = gid;
}

+static inline void proc_fs_set_newinstance(struct proc_fs_info *fs_info, bool value)
+{
+ fs_info->newinstance = value;
+}
+
+static inline int proc_fs_set_limit_pids(struct proc_fs_info *fs_info, int value)
+{
+ if (value < PROC_LIMIT_PIDS_OFF || value > PROC_LIMIT_PIDS_PTRACE)
+ return -EINVAL;
+
+ fs_info->limit_pids = value;
+
+ return 0;
+}
+
static inline int proc_fs_hide_pid(struct proc_fs_info *fs_info)
{
return fs_info->pid_ns->hide_pid;
@@ -45,6 +67,16 @@ static inline kgid_t proc_fs_pid_gid(struct proc_fs_info *fs_info)
return fs_info->pid_ns->pid_gid;
}

+static inline bool proc_fs_newinstance(struct proc_fs_info *fs_info)
+{
+ return fs_info->newinstance;
+}
+
+static inline int proc_fs_limit_pids(struct proc_fs_info *fs_info)
+{
+ return fs_info->limit_pids;
+}
+
extern void proc_root_init(void);
extern void proc_flush_task(struct task_struct *);

@@ -95,6 +127,15 @@ static inline void proc_fs_set_pid_gid(struct proc_info_fs *fs_info, kgid_t gid)
{
}

+static inline void proc_fs_set_newinstance(struct proc_fs_info *fs_info, bool value)
+{
+}
+
+static inline int proc_fs_set_limit_pids(struct proc_fs_info *fs_info, int value)
+{
+ return 0;
+}
+
static inline int proc_fs_hide_pid(struct proc_fs_info *fs_info)
{
return 0;
@@ -105,6 +146,16 @@ extern kgid_t proc_fs_pid_gid(struct proc_fs_info *fs_info)
return GLOBAL_ROOT_GID;
}

+static inline bool proc_fs_newinstance(struct proc_fs_info *fs_info)
+{
+ return false;
+}
+
+static inline int proc_fs_limit_pids(struct proc_fs_info *fs_info)
+{
+ return 0;
+}
+
extern inline struct proc_fs_info *proc_sb(struct super_block *sb) { return NULL;}
static inline struct proc_dir_entry *proc_symlink(const char *name,
struct proc_dir_entry *parent,const char *dest) { return NULL;}
--
2.10.2

2017-04-25 12:25:42

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 5/6] proc: instantiate only pids that we can ptrace on 'limit_pids=1' mount option

If "limit_pids=1" mount option is set then do not instantiate pids that
we can not ptrace. "limit_pids=1" means that procfs should only contain
pids that the caller can ptrace.

Cc: Kees Cook <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Signed-off-by: Djalal Harouni <[email protected]>
---
fs/proc/base.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index 2e0f661..a663284 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -3149,6 +3149,7 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, unsign
unsigned tgid;
struct proc_fs_info *fs_info = proc_sb(dir->i_sb);
struct pid_namespace *ns = fs_info->pid_ns;
+ int limit_pids = proc_fs_limit_pids(fs_info);

tgid = name_to_int(&dentry->d_name);
if (tgid == ~0U)
@@ -3162,7 +3163,15 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, unsign
if (!task)
goto out;

+ /* Limit procfs to only ptracable tasks */
+ if (limit_pids == PROC_LIMIT_PIDS_PTRACE) {
+ cond_resched();
+ if (!has_pid_permissions(fs_info, task, HIDEPID_NO_ACCESS))
+ goto out_put_task;
+ }
+
result = proc_pid_instantiate(dir, dentry, task, NULL);
+out_put_task:
put_task_struct(task);
out:
return ERR_PTR(result);
--
2.10.2

2017-04-25 12:27:04

by Djalal Harouni

[permalink] [raw]
Subject: [PATCH RFC v2 6/6] proc: flush task dcache entries from all procfs instances

This allows to flush dcache entries of a task on multiple procfs mounts
per pid namespace.

Cc: Kees Cook <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Signed-off-by: Djalal Harouni <[email protected]>
---
fs/proc/base.c | 27 ++++++++++++++++++++++-----
fs/proc/inode.c | 9 ++++++++-
fs/proc/root.c | 10 ++++++++++
include/linux/pid_namespace.h | 42 ++++++++++++++++++++++++++++++++++++++++++
include/linux/proc_fs.h | 2 ++
5 files changed, 84 insertions(+), 6 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index a663284..3c35126 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -3030,7 +3030,8 @@ static const struct inode_operations proc_tgid_base_inode_operations = {
.permission = proc_pid_permission,
};

-static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
+static void proc_flush_task_mnt_root(struct dentry *mnt_root,
+ pid_t pid, pid_t tgid)
{
struct dentry *dentry, *leader, *dir;
char buf[PROC_NUMBUF];
@@ -3039,7 +3040,7 @@ static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
name.name = buf;
name.len = snprintf(buf, sizeof(buf), "%d", pid);
/* no ->d_hash() rejects on procfs */
- dentry = d_hash_and_lookup(mnt->mnt_root, &name);
+ dentry = d_hash_and_lookup(mnt_root, &name);
if (dentry) {
d_invalidate(dentry);
dput(dentry);
@@ -3050,7 +3051,7 @@ static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)

name.name = buf;
name.len = snprintf(buf, sizeof(buf), "%d", tgid);
- leader = d_hash_and_lookup(mnt->mnt_root, &name);
+ leader = d_hash_and_lookup(mnt_root, &name);
if (!leader)
goto out;

@@ -3105,14 +3106,30 @@ void proc_flush_task(struct task_struct *task)
int i;
struct pid *pid, *tgid;
struct upid *upid;
+ struct proc_fs_info *fs_info_entry;
+ struct pid_namespace *pid_ns;
+ struct dentry *mnt_root;

pid = task_pid(task);
tgid = task_tgid(task);

for (i = 0; i <= pid->level; i++) {
upid = &pid->numbers[i];
- proc_flush_task_mnt(upid->ns->proc_mnt, upid->nr,
- tgid->numbers[i].nr);
+ pid_ns = upid->ns;
+
+ pidns_proc_lock_shared(pid_ns);
+ list_for_each_entry(fs_info_entry, &pid_ns->procfs_mounts,
+ pidns_entry) {
+ if (proc_fs_newinstance(fs_info_entry)) {
+ mnt_root = fs_info_entry->sb->s_root;
+ proc_flush_task_mnt_root(mnt_root, upid->nr,
+ tgid->numbers[i].nr);
+ }
+ }
+ pidns_proc_unlock_shared(pid_ns);
+
+ mnt_root = pid_ns->proc_mnt->mnt_root;
+ proc_flush_task_mnt_root(mnt_root, upid->nr, tgid->numbers[i].nr);
}
}

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index b4ee4a1..f374dac 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -479,10 +479,17 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
int proc_fill_super(struct super_block *s, void *data, int silent)
{
struct proc_fs_info *fs_info = proc_sb(s);
+ struct pid_namespace *ns = get_pid_ns(fs_info->pid_ns);
struct inode *root_inode;
int ret;

- get_pid_ns(fs_info->pid_ns);
+ fs_info->sb = s;
+
+ if (proc_fs_newinstance(fs_info)) {
+ pidns_proc_lock(ns);
+ list_add_tail(&fs_info->pidns_entry, &ns->procfs_mounts);
+ pidns_proc_unlock(ns);
+ }

if (!proc_parse_options(data, fs_info))
return -EINVAL;
diff --git a/fs/proc/root.c b/fs/proc/root.c
index f398c14..3f04584 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -248,6 +248,13 @@ static void proc_kill_sb(struct super_block *sb)
dput(fs_info->proc_self);
if (fs_info->proc_thread_self)
dput(fs_info->proc_thread_self);
+
+ if (proc_fs_newinstance(fs_info)) {
+ pidns_proc_lock(ns);
+ list_del(&fs_info->pidns_entry);
+ pidns_proc_unlock(ns);
+ }
+
kill_anon_super(sb);
put_pid_ns(ns);
kfree(fs_info);
@@ -363,6 +370,9 @@ int pid_ns_prepare_proc(struct pid_namespace *ns)
return PTR_ERR(mnt);

ns->proc_mnt = mnt;
+ init_rwsem(&ns->rw_procfs_mnts);
+ INIT_LIST_HEAD(&ns->procfs_mounts);
+
return 0;
}

diff --git a/include/linux/pid_namespace.h b/include/linux/pid_namespace.h
index 306bdc6..377b751 100644
--- a/include/linux/pid_namespace.h
+++ b/include/linux/pid_namespace.h
@@ -39,6 +39,8 @@ struct pid_namespace {
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
struct vfsmount *proc_mnt; /* Internal proc mounted during each new pidns */
+ struct rw_semaphore rw_procfs_mnts;
+ struct list_head procfs_mounts; /* list of separated procfs mounts */
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
struct fs_pin *bacct;
@@ -105,4 +107,44 @@ extern struct pid_namespace *task_active_pid_ns(struct task_struct *tsk);
void pidhash_init(void);
void pidmap_init(void);

+#ifdef CONFIG_PROC_FS
+static inline void pidns_proc_lock(struct pid_namespace *pid_ns)
+{
+ down_write(&pid_ns->rw_procfs_mnts);
+}
+
+static inline void pidns_proc_unlock(struct pid_namespace *pid_ns)
+{
+ up_write(&pid_ns->rw_procfs_mnts);
+}
+
+static inline void pidns_proc_lock_shared(struct pid_namespace *pid_ns)
+{
+ down_read(&pid_ns->rw_procfs_mnts);
+}
+
+static inline void pidns_proc_unlock_shared(struct pid_namespace *pid_ns)
+{
+ up_read(&pid_ns->rw_procfs_mnts);
+}
+#else /* !CONFIG_PROC_FS */
+
+static inline void pidns_proc_lock(struct pid_namespace *pid_ns)
+{
+}
+
+static inline void pidns_proc_unlock(struct pid_namespace *pid_ns)
+{
+}
+
+static inline void pidns_proc_lock_shared(struct pid_namespace *pid_ns)
+{
+}
+
+static inline void pidns_proc_unlock_shared(struct pid_namespace *pid_ns)
+{
+}
+
+#endif /* CONFIG_PROC_FS */
+
#endif /* _LINUX_PID_NS_H */
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 0fddb84..1af01c2 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -18,7 +18,9 @@ enum { /* definitions for proc mount option limit_pids */
};

struct proc_fs_info {
+ struct super_block *sb;
struct pid_namespace *pid_ns;
+ struct list_head pidns_entry; /* Node in procfs_mounts of a pidns */
struct dentry *proc_self; /* For /proc/self */
struct dentry *proc_thread_self; /* For /proc/thread-self/ */
bool newinstance; /* Private flag for new separated instances */
--
2.10.2

2017-04-26 22:09:41

by Andy Lutomirski

[permalink] [raw]
Subject: Re: [PATCH RFC v2 5/6] proc: instantiate only pids that we can ptrace on 'limit_pids=1' mount option

On Tue, Apr 25, 2017 at 5:23 AM, Djalal Harouni <[email protected]> wrote:
> If "limit_pids=1" mount option is set then do not instantiate pids that
> we can not ptrace. "limit_pids=1" means that procfs should only contain
> pids that the caller can ptrace.
>
> Cc: Kees Cook <[email protected]>
> Cc: Andy Lutomirski <[email protected]>
> Signed-off-by: Djalal Harouni <[email protected]>
> ---
> fs/proc/base.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index 2e0f661..a663284 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -3149,6 +3149,7 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, unsign
> unsigned tgid;
> struct proc_fs_info *fs_info = proc_sb(dir->i_sb);
> struct pid_namespace *ns = fs_info->pid_ns;
> + int limit_pids = proc_fs_limit_pids(fs_info);

Shouldn't the addition of proc_fs_limit_pids() be in this patch?

Also, can we name it something self-documented?
"ptraceable_pids_only=1", perhaps? Or even pids=ptraceable (as
opposed to pids=all or maybe other choices in the future)?

--Andy

2017-04-26 22:14:21

by Andy Lutomirski

[permalink] [raw]
Subject: Re: [PATCH RFC v2 4/6] proc: support mounting private procfs instances inside same pid namespace

On Tue, Apr 25, 2017 at 5:23 AM, Djalal Harouni <[email protected]> wrote:
> This patch allows to have multiple private procfs instances inside the
> same pid namespace. Lot of other areas in the kernel and filesystems
> have been updated to be able to support private instances, devpts is one
> major example. The aim here is lightweight sandboxes, and to allow that we
> have to modernize procfs internals.
>
> 1) The main aim of this work is to have on embedded systems one
> supervisor for apps. Right now we have some lightweight sandbox support,
> however if we create pid namespacess we have to manages 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 without being able to
> notice each other. We only want to use mount namespaces, and we want
> procfs to behave more like a real mount point.
>
> 2) 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 users 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 LSMs should be able to analyze
> open/read/write/close...
>
> 3) This will modernize procfs and align it with all other filesystems
> and subsystems that have been updated recently to be able to work in a
> flexible way. This is the same as devpts where each mount now is a distinct
> filesystem such that ptys and their indicies allocated in one mount are
> independent from ptys and their indicies in all other mounts.
>
> We have to align procfs and modernize it to have a per mount context
> where at least the mount option do not propagate to all other mounts,
> then maybe we can continue to implement new features. One example is to
> require CAP_SYS_ADMIN in the init user namespace on some /proc/* which are
> not pids and which are are not virtualized by design, or CAP_NET_ADMIN
> inside userns on the net bits that are virtualized, etc.
> These mount options won't propagate to previous mounts, and the system
> will continue to be usable.
>
> Ths patch introduces the new 'limit_pids' mount option as it was also
> suggesed by Andy Lutomirski [1]. When this option is passed we
> automatically create a private procfs instance. This is not the default
> behaviour since we do not want to break userspace and we do not want to
> provide different devices IDs by default, please see [1] for why.

I think that calling the option to make a separate instance
"limit_pids" is extremely counterintuitive.

My strong preference would be to make proc *always* make a separate
instance (unless it's a bind mount) and to make it work. If that
means fudging stat() output, so be it.

Failing that, let's come up with some coherent way to make this work.
"new_instance" or similar would do. Then make limit_pid cause an
error unless new_instance is also set.

--Andy