From patchwork Thu Nov 9 16:14:05 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Djalal Harouni X-Patchwork-Id: 10051229 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id EF1D4603FA for ; Thu, 9 Nov 2017 16:16:22 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E1E652AF8B for ; Thu, 9 Nov 2017 16:16:22 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D6A462AFC4; Thu, 9 Nov 2017 16:16:22 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.wl.linuxfoundation.org (Postfix) with SMTP id 25F532AFAB for ; Thu, 9 Nov 2017 16:16:20 +0000 (UTC) Received: (qmail 5804 invoked by uid 550); 9 Nov 2017 16:14:52 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Delivered-To: mailing list kernel-hardening@lists.openwall.com Received: (qmail 5663 invoked from network); 9 Nov 2017 16:14:48 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=giTZywRTkyoZxT1TLgZSLEKazrzfal/pRL545h6hL+g=; b=JHZRwhV4UFnJM8Y1QGe2Bt82qbSoT2SUL3WP+gMtLzY34HDmQeEJSRwWTSFwSspRT+ m7/1b+i1JfDm+IGDPiDLNJyr3L6HY/QNH7zfmKD7GUth63VLUOkVhEERICLqpL2IeGmA 0P3yfVTAdrbcHZe5HyD5S1tHxwHiJ4LZ6HqZc/LcJ4E8bGE/ksvwcCEtQyxfBWR37vQ0 d4Yb5d0pdwc9/7tgXoa7Uls7IgzzZZtG7bAAztru4UGQ0qh4NhMwFqW3xgpwveWfS3yb 0JVE2My9c9nowJMWl2q20Bx7PCnotL/nuSwjxbSzh9p/jFtOlvM0BFDEx8CiQFPKdCVV vL6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=giTZywRTkyoZxT1TLgZSLEKazrzfal/pRL545h6hL+g=; b=qWhvieU/pfv3u2UxkSdc1T971f8hdVYlWfBCGqn1CEczppGym9EwtJ8yq4c4a4mJDY w6Dt70AN4Y7NfGQzQ1fqoie3eYs66PBD0vZo00GX6KmrMIwZeuMv+TN5pYzfkDwE6Ree FyKpHU6rW1aCLTQ67wi8W3LphdQNGje9HFK8E1KCZP+iqRnR/H7YZOLnLwIbx7REZB2U tatD9LPYH5IBH9wRUeKz8hAtk8vTB7qHn3JNwSvTxvYzDwckh/1Ummf4L0LKMdAm+fQk DMT5lsg5QurpRXb2QPoGv0W2+Affnnx1+ofx447i9h4t+IjNuNJXhZCHP/qTdgbC4Uwc nQxg== X-Gm-Message-State: AJaThX4//3xtFQGm+4LyKwvd9dwbrXU/wQ49BLpMqlhvgK88rIcS/qx5 uXO13qBUd0dcJe3g4dg1BdE= X-Google-Smtp-Source: ABhQp+T8a3wv0eVL5ld/9cKqUlCfnk/lvME4w5H4gYh14isIp6UVCmcNDw9jWG1IC5zmjs1ZOFrQHA== X-Received: by 10.80.141.18 with SMTP id s18mr1357138eds.241.1510244076673; Thu, 09 Nov 2017 08:14:36 -0800 (PST) From: Djalal Harouni To: Kees Cook , Alexey Gladkov , Andy Lutomirski , Andrew Morton , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, kernel-hardening@lists.openwall.com, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org Cc: Greg Kroah-Hartman , Alexander Viro , Akinobu Mita , me@tobin.cc, Oleg Nesterov , Jeff Layton , Ingo Molnar , Alexey Dobriyan , ebiederm@xmission.com, Linus Torvalds , Daniel Micay , Jonathan Corbet , bfields@fieldses.org, Stephen Rothwell , solar@openwall.com, Djalal Harouni Date: Thu, 9 Nov 2017 17:14:05 +0100 Message-Id: <1510244046-3256-7-git-send-email-tixxdz@gmail.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1510244046-3256-1-git-send-email-tixxdz@gmail.com> References: <1510244046-3256-1-git-send-email-tixxdz@gmail.com> Subject: [kernel-hardening] [PATCH RFC v3 6/7] proc: support new 'pids=all|ptraceable' mount option X-Virus-Scanned: ClamAV using ClamSMTP This patch introduces the new 'pids' mount option, as it was discussed and suggested by Andy Lutomirski [1]. * If 'pids=' is passed without 'newinstance' then it has no effect. * If 'newinstance,pids=all' then all processes will be shown in proc. * If 'newinstance,pids=ptraceable' then only ptraceable processes will be shown. * 'pids=' takes precendence over 'hidepid=' since 'hidepid=' can be ignored if "gid=" was set and caller has the "gid=" set in its groups. We want to guarantee that LSM have a security path there that can not be disabled with "gid=". This allows to support lightweight sandboxes in Embedded Linux. Later Yama LSM can be updated to check that processes are able only able to see their children inside /proc/, allowing to support more tight cases. [1] https://lkml.org/lkml/2017/4/26/646 Cc: Kees Cook Cc: Greg Kroah-Hartman Suggested-by: Andy Lutomirski Signed-off-by: Alexey Gladkov Signed-off-by: Djalal Harouni Reviewed-by: James Morris --- fs/proc/base.c | 36 +++++++++++++++++++++++++++++------- fs/proc/inode.c | 6 +++++- fs/proc/root.c | 20 ++++++++++++++++++-- include/linux/proc_fs.h | 30 ++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/fs/proc/base.c b/fs/proc/base.c index 54b527c..88b92bc 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -686,13 +686,24 @@ 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 pids = proc_fs_pids(fs_info); + + /* + * If 'pids=all' or if it was not set then lets fallback + * to 'hidepid' and 'gid', if those are not enforced too, then + * ptrace checks are skipped. Otherwise ptrace permission is + * required for all other cases. + */ + if (pids == PIDS_ALL) { + 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 (hide_pid < hide_pid_min) - return true; - if (in_group_p(gid)) - return true; + if (in_group_p(gid)) + return true; + } return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS); } @@ -701,6 +712,7 @@ static int proc_pid_permission(struct inode *inode, int mask) { struct proc_fs_info *fs_info = proc_sb(inode->i_sb); int hide_pid = proc_fs_hide_pid(fs_info); + int pids = proc_fs_pids(fs_info); struct task_struct *task; bool has_perms; @@ -711,7 +723,8 @@ static int proc_pid_permission(struct inode *inode, int mask) put_task_struct(task); if (!has_perms) { - if (hide_pid == HIDEPID_INVISIBLE) { + if (pids == PIDS_PTRACEABLE || + hide_pid == HIDEPID_INVISIBLE) { /* * Let's make getdents(), stat(), and open() * consistent with each other. If a process @@ -3140,6 +3153,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 pids = proc_fs_pids(fs_info); tgid = name_to_int(&dentry->d_name); if (tgid == ~0U) @@ -3153,7 +3167,15 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, unsign if (!task) goto out; + /* Limit procfs to only ptraceable tasks */ + if (pids != PIDS_ALL) { + 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); diff --git a/fs/proc/inode.c b/fs/proc/inode.c index faec32a..2707d5f 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -108,8 +108,12 @@ static int proc_show_options(struct seq_file *seq, struct dentry *root) int hide_pid = proc_fs_hide_pid(fs_info); kgid_t pid_gid = proc_fs_pid_gid(fs_info); - if (proc_fs_newinstance(fs_info)) + if (proc_fs_newinstance(fs_info)) { + int pids = proc_fs_pids(fs_info); + seq_printf(seq, ",newinstance"); + seq_printf(seq, ",pids=%s", pids == PIDS_ALL ? "all" : "ptraceable"); + } if (!gid_eq(pid_gid, GLOBAL_ROOT_GID)) seq_printf(seq, ",gid=%u", from_kgid_munged(current_user_ns(),pid_gid)); diff --git a/fs/proc/root.c b/fs/proc/root.c index 33ab965..5cdff69 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -28,13 +28,14 @@ #include "internal.h" enum { - Opt_gid, Opt_hidepid, Opt_newinstance, Opt_err, + Opt_gid, Opt_hidepid, Opt_newinstance, Opt_pids, Opt_err, }; static const match_table_t tokens = { {Opt_hidepid, "hidepid=%u"}, {Opt_gid, "gid=%u"}, {Opt_newinstance, "newinstance"}, + {Opt_pids, "pids=%s"}, {Opt_err, NULL}, }; @@ -67,6 +68,7 @@ int proc_parse_early_options(char *options, struct proc_fs_info *fs_info) break; case Opt_gid: case Opt_hidepid: + case Opt_pids: break; default: pr_err("proc: unrecognized mount option \"%s\" " @@ -83,7 +85,7 @@ int proc_parse_options(char *options, struct proc_fs_info *fs_info) { char *p; substring_t args[MAX_OPT_ARGS]; - int option; + int option, ret = 0; kgid_t gid; if (!options) @@ -119,6 +121,19 @@ int proc_parse_options(char *options, struct proc_fs_info *fs_info) break; case Opt_newinstance: break; + case Opt_pids: + if (strcmp(args[0].from, "all") == 0) + ret = proc_fs_set_pids(fs_info, PIDS_ALL); + else if (strcmp(args[0].from, "ptraceable") == 0) + ret = proc_fs_set_pids(fs_info, PIDS_PTRACEABLE); + else + ret = -EINVAL; + + if (ret < 0) { + pr_err("proc: invalid 'pids' mount option.\n"); + return 0; + } + break; default: pr_err("proc: unrecognized mount option \"%s\" " "or missing value\n", p); @@ -188,6 +203,7 @@ static struct dentry *proc_mount(struct file_system_type *fs_type, /* Set it as early as possible */ proc_fs_set_newinstance(fs_info, false); + proc_fs_set_pids(fs_info, PIDS_ALL); if (flags & SB_KERNMOUNT) { ns = data; diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h index c123e5ec..0730f52 100644 --- a/include/linux/proc_fs.h +++ b/include/linux/proc_fs.h @@ -18,6 +18,11 @@ enum { /* definitions for 'hidepid' mount option */ HIDEPID_INVISIBLE = 2, }; +enum { /* definitions for 'pids' mount option */ + PIDS_ALL = 0, + PIDS_PTRACEABLE = 1, +}; + struct proc_fs_info { struct pid_namespace *pid_ns; struct dentry *proc_self; /* For /proc/self/ */ @@ -25,6 +30,7 @@ struct proc_fs_info { bool newinstance; /* Flag for new separated instances */ kgid_t pid_gid; int hide_pid; + int pids; }; #ifdef CONFIG_PROC_FS @@ -49,6 +55,16 @@ static inline void proc_fs_set_newinstance(struct proc_fs_info *fs_info, bool va fs_info->newinstance = value; } +static inline int proc_fs_set_pids(struct proc_fs_info *fs_info, int value) +{ + if (value != PIDS_ALL && + (value != PIDS_PTRACEABLE || !fs_info->newinstance)) + return -EINVAL; + + fs_info->pids = value; + return 0; +} + static inline int proc_fs_hide_pid(struct proc_fs_info *fs_info) { return fs_info->hide_pid; @@ -64,6 +80,11 @@ static inline bool proc_fs_newinstance(struct proc_fs_info *fs_info) return fs_info->newinstance; } +static inline int proc_fs_pids(struct proc_fs_info *fs_info) +{ + return fs_info->pids; +} + extern void proc_root_init(void); extern void proc_flush_task(struct task_struct *); @@ -112,6 +133,10 @@ static inline void proc_fs_set_newinstance(struct proc_fs_info *fs_info, bool va { } +static inline int proc_fs_set_pids(struct proc_fs_info *fs_info, int value) +{ +} + static inline int proc_fs_hide_pid(struct proc_fs_info *fs_info) { return 0; @@ -127,6 +152,11 @@ static inline bool proc_fs_newinstance(struct proc_fs_info *fs_info) return false; } +static inline int proc_fs_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;}