[PATCH 4/9] procfs: improve /proc/<pid>/wchan protection

From: Djalal Harouni
Date: Mon May 26 2014 - 09:33:40 EST


Convert wchan from an INF entry to a REG one. This way we can perform
and cache the permission checks during ->open().

The checks are only cached, since /proc/<pid>/wchan is world readable,
and it needs permissions only when returning an address, returning the
symbol name is not subject to permission checks, so do not change this.

With this logic userspace should continue to work without any problem,
only cases where users are trying to play tricks or something
sophisticated is trying to use privileged programs to disclose sensitive
data will notice.

Suggested-by: Alexey Dobriyan <adobriyan@xxxxxxxxx>
Signed-off-by: Djalal Harouni <tixxdz@xxxxxxxxxx>
---
fs/proc/base.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 49 insertions(+), 6 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index efe2a11..ef35544 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -273,21 +273,64 @@ static int proc_pid_auxv(struct task_struct *task, char *buffer)
* Provides a wchan file via kallsyms in a proper one-value-per-file format.
* Returns the resolved symbol. If that fails, simply return the address.
*/
-static int proc_pid_wchan(struct task_struct *task, char *buffer)
+static int wchan_open(struct inode *inode, struct file *filp)
+{
+ /* we only cache the result for wchan entry */
+ if (pid_entry_access(filp, PTRACE_MODE_READ))
+ filp->private_data = (void *)(unsigned long)PID_ENTRY_DENY;
+ else
+ filp->private_data = (void *)(unsigned long)PID_ENTRY_ALLOW;
+
+ return 0;
+}
+
+static int proc_pid_wchan(char *buffer,
+ struct task_struct *task, int permitted)
{
unsigned long wchan;
char symname[KSYM_NAME_LEN];

wchan = get_wchan(task);

- if (lookup_symbol_name(wchan, symname) < 0)
- if (!ptrace_may_access(task, PTRACE_MODE_READ))
+ if (lookup_symbol_name(wchan, symname) < 0) {
+ if (!permitted)
+ return 0;
+
+ /* Update only if access was granted during ->open */
+ if (!mutex_lock_killable(&task->signal->cred_guard_mutex)) {
+ permitted = ptrace_may_access(task, PTRACE_MODE_READ);
+ mutex_unlock(&task->signal->cred_guard_mutex);
+ }
+
+ if (!permitted)
return 0;
else
return sprintf(buffer, "%lu", wchan);
- else
+ } else
return sprintf(buffer, "%s", symname);
}
+
+static ssize_t wchan_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t length;
+ unsigned long page = 0UL;
+
+ length = pid_entry_read(file, &page, proc_pid_wchan);
+ if (length >= 0) {
+ length = proc_read_from_buffer(buf, count, ppos,
+ (char *)page, length);
+ free_page(page);
+ }
+
+ return length;
+}
+
+static const struct file_operations proc_pid_wchan_operations = {
+ .open = wchan_open,
+ .read = wchan_read,
+ .llseek = generic_file_llseek,
+};
#endif /* CONFIG_KALLSYMS */

static int lock_trace(struct task_struct *task)
@@ -2631,7 +2674,7 @@ static const struct pid_entry tgid_base_stuff[] = {
DIR("attr", S_IRUGO|S_IXUGO, proc_attr_dir_inode_operations, proc_attr_dir_operations),
#endif
#ifdef CONFIG_KALLSYMS
- INF("wchan", S_IRUGO, proc_pid_wchan),
+ REG("wchan", S_IRUGO, proc_pid_wchan_operations),
#endif
#ifdef CONFIG_STACKTRACE
ONE("stack", S_IRUSR, proc_pid_stack),
@@ -2967,7 +3010,7 @@ static const struct pid_entry tid_base_stuff[] = {
DIR("attr", S_IRUGO|S_IXUGO, proc_attr_dir_inode_operations, proc_attr_dir_operations),
#endif
#ifdef CONFIG_KALLSYMS
- INF("wchan", S_IRUGO, proc_pid_wchan),
+ REG("wchan", S_IRUGO, proc_pid_wchan_operations),
#endif
#ifdef CONFIG_STACKTRACE
ONE("stack", S_IRUSR, proc_pid_stack),
--
1.7.11.7

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/