Index: security/yama/yama_lsm.c |
diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b6fd94a38c574820b88e2157f1351053263433f3 |
--- /dev/null |
+++ b/security/yama/yama_lsm.c |
@@ -0,0 +1,252 @@ |
+/* |
+ * Yama Linux Security Module |
+ * |
+ * Author: Kees Cook <kees.cook@canonical.com> |
+ * |
+ * Copyright (C) 2010 Canonical, Ltd. |
+ * |
+ * This program is free software; you can redistribute it and/or modify |
+ * it under the terms of the GNU General Public License version 2, as |
+ * published by the Free Software Foundation. |
+ * |
+ */ |
+ |
+#include <linux/security.h> |
+#include <linux/sysctl.h> |
+#include <linux/ptrace.h> |
+#include <linux/ratelimit.h> |
+ |
+/* Ratelimiting from the future. */ |
+#ifndef printk_ratelimited |
+#ifdef CONFIG_PRINTK |
+#define printk_ratelimited(fmt, ...) ({ \ |
+ static DEFINE_RATELIMIT_STATE(_rs, \ |
+ DEFAULT_RATELIMIT_INTERVAL, \ |
+ DEFAULT_RATELIMIT_BURST); \ |
+ \ |
+ if (__ratelimit(&_rs)) \ |
+ printk(fmt, ##__VA_ARGS__); \ |
+}) |
+#else |
+/* No effect, but we still get type checking even in the !PRINTK case: */ |
+#define printk_ratelimited printk |
+#endif |
+#endif |
+ |
+static int ptrace_scope = 1; |
+static int protected_sticky_symlinks = 1; |
+static int protected_nonaccess_hardlinks = 1; |
+ |
+/** |
+ * yama_ptrace_access_check - validate PTRACE_ATTACH calls |
+ * @child: child task pointer |
+ * @mode: ptrace attach mode |
+ * |
+ * Returns 0 if following the ptrace is allowed, -ve on error. |
+ */ |
+static int yama_ptrace_access_check(struct task_struct *child, |
+ unsigned int mode) |
+{ |
+ int rc; |
+ |
+ rc = cap_ptrace_access_check(child, mode); |
+ if (rc != 0) |
+ return rc; |
+ |
+ /* require ptrace target be a child of ptracer on attach */ |
+ if (mode == PTRACE_MODE_ATTACH && ptrace_scope && |
+ !capable(CAP_SYS_PTRACE)) { |
+ struct task_struct *walker = child; |
+ |
+ rcu_read_lock(); |
+ read_lock(&tasklist_lock); |
+ while (walker->pid > 0) { |
+ if (walker == current) |
+ break; |
+ walker = walker->real_parent; |
+ } |
+ if (walker->pid == 0) |
+ rc = -EPERM; |
+ read_unlock(&tasklist_lock); |
+ rcu_read_unlock(); |
+ } |
+ |
+ if (rc) { |
+ char name[sizeof(current->comm)]; |
+ printk_ratelimited(KERN_INFO "ptrace of non-child" |
+ " pid %d was attempted by: %s (pid %d)\n", |
+ child->pid, |
+ get_task_comm(name, current), |
+ current->pid); |
+ } |
+ |
+ return rc; |
+} |
+ |
+/** |
+ * yama_inode_follow_link - check for symlinks in sticky world-writeable dirs |
+ * @dentry: The inode/dentry of the symlink |
+ * @nameidata: The path data of the symlink |
+ * |
+ * In the case of the protected_sticky_symlinks sysctl being enabled, |
+ * CAP_DAC_OVERRIDE needs to be specifically ignored if the symlink is |
+ * in a sticky world-writable directory. This is to protect privileged |
+ * processes from failing races against path names that may change out |
+ * from under them by way of other users creating malicious symlinks. |
+ * It will permit symlinks to only be followed when outside a sticky |
+ * world-writable directory, or when the uid of the symlink and follower |
+ * match, or when the directory owner matches the symlink's owner. |
+ * |
+ * Returns 0 if following the symlink is allowed, -ve on error. |
+ */ |
+static int yama_inode_follow_link(struct dentry *dentry, |
+ struct nameidata *nameidata) |
+{ |
+ int rc = 0; |
+ const struct inode *parent; |
+ const struct inode *inode; |
+ const struct cred *cred; |
+ |
+ if (!protected_sticky_symlinks) |
+ return 0; |
+ |
+ /* owner and follower match? */ |
+ cred = current_cred(); |
+ inode = dentry->d_inode; |
+ if (cred->fsuid == inode->i_uid) |
+ return 0; |
+ |
+ /* check parent directory mode and owner */ |
+ spin_lock(&dentry->d_lock); |
+ parent = dentry->d_parent->d_inode; |
+ if ((parent->i_mode & (S_ISVTX|S_IWOTH)) == (S_ISVTX|S_IWOTH) && |
+ parent->i_uid != inode->i_uid) { |
+ rc = -EACCES; |
+ } |
+ spin_unlock(&dentry->d_lock); |
+ |
+ if (rc) { |
+ char name[sizeof(current->comm)]; |
+ printk_ratelimited(KERN_NOTICE "non-matching-uid symlink " |
+ "following attempted in sticky world-writable " |
+ "directory by %s (fsuid %d != %d)\n", |
+ get_task_comm(name, current), |
+ cred->fsuid, inode->i_uid); |
+ } |
+ |
+ return rc; |
+} |
+ |
+/** |
+ * yama_path_link - verify that hardlinking is allowed |
+ * @old_dentry: the source inode/dentry to hardlink from |
+ * @new_dir: target directory |
+ * @new_dentry: the target inode/dentry to hardlink to |
+ * |
+ * Block hardlink when all of: |
+ * - fsuid does not match inode |
+ * - not CAP_FOWNER |
+ * - and at least one of: |
+ * - inode is not a regular file |
+ * - inode is setuid |
+ * - inode is setgid and group-exec |
+ * - access failure for read and write |
+ * |
+ * Returns 0 if successful, -ve on error. |
+ */ |
+static int yama_path_link(struct dentry *old_dentry, struct path *new_dir, |
+ struct dentry *new_dentry) |
+{ |
+ int rc = 0; |
+ struct inode *inode = old_dentry->d_inode; |
+ const int mode = inode->i_mode; |
+ const struct cred *cred = current_cred(); |
+ |
+ if (!protected_nonaccess_hardlinks) |
+ return 0; |
+ |
+ if (cred->fsuid != inode->i_uid && |
+ (!S_ISREG(mode) || (mode & S_ISUID) || |
+ ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) || |
+ (generic_permission(inode, MAY_READ | MAY_WRITE, NULL))) && |
+ !capable(CAP_FOWNER)) { |
+ char name[sizeof(current->comm)]; |
+ printk_ratelimited(KERN_INFO "non-accessible hardlink" |
+ " creation was attempted by: %s (fsuid %d)\n", |
+ get_task_comm(name, current), |
+ cred->fsuid); |
+ rc = -EPERM; |
+ } |
+ |
+ return rc; |
+} |
+ |
+static struct security_operations yama_ops = { |
+ .name = "yama", |
+ |
+ .ptrace_access_check = yama_ptrace_access_check, |
+ .inode_follow_link = yama_inode_follow_link, |
+ .path_link = yama_path_link, |
+}; |
+ |
+#ifdef CONFIG_SYSCTL |
+static int zero; |
+static int one = 1; |
+ |
+struct ctl_path yama_sysctl_path[] = { |
+ { .procname = "kernel", }, |
+ { .procname = "yama", }, |
+ { } |
+}; |
+ |
+static struct ctl_table yama_sysctl_table[] = { |
+ { |
+ .procname = "protected_sticky_symlinks", |
+ .data = &protected_sticky_symlinks, |
+ .maxlen = sizeof(int), |
+ .mode = 0644, |
+ .proc_handler = proc_dointvec_minmax, |
+ .extra1 = &zero, |
+ .extra2 = &one, |
+ }, |
+ { |
+ .procname = "protected_nonaccess_hardlinks", |
+ .data = &protected_nonaccess_hardlinks, |
+ .maxlen = sizeof(int), |
+ .mode = 0644, |
+ .proc_handler = proc_dointvec_minmax, |
+ .extra1 = &zero, |
+ .extra2 = &one, |
+ }, |
+ { |
+ .procname = "ptrace_scope", |
+ .data = &ptrace_scope, |
+ .maxlen = sizeof(int), |
+ .mode = 0644, |
+ .proc_handler = proc_dointvec_minmax, |
+ .extra1 = &zero, |
+ .extra2 = &one, |
+ }, |
+ { } |
+}; |
+#endif /* CONFIG_SYSCTL */ |
+ |
+static __init int yama_init(void) |
+{ |
+ if (!security_module_enable(&yama_ops)) |
+ return 0; |
+ |
+ printk(KERN_INFO "Yama: becoming mindful.\n"); |
+ |
+ if (register_security(&yama_ops)) |
+ panic("Yama: kernel registration failed.\n"); |
+ |
+#ifdef CONFIG_SYSCTL |
+ if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) |
+ panic("Yama: sysctl registration failed.\n"); |
+#endif |
+ |
+ return 0; |
+} |
+ |
+security_initcall(yama_init); |