[PATCH 4/5] UBUNTU: SAUCE: (no-up) Yama: add link restrictions

Tyler Hicks tyhicks at canonical.com
Wed Feb 5 23:20:00 UTC 2014


From: Kees Cook <keescook at chromium.org>

Add symlink and hardlink restrictions that have shown real-world security
benefits, along with sysctl knobs to control them.

Signed-off-by: Kees Cook <keescook at chromium.org>
Signed-off-by: Tim Gardner <tim.gardner at canonical.com>
[tyhicks: forward ported from Quantal]
Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
---
 Documentation/security/Yama.txt |  44 +++++++++++++
 include/linux/security.h        |  17 +++++
 security/security.c             |  16 +++++
 security/yama/Kconfig           |   5 +-
 security/yama/yama_lsm.c        | 136 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 216 insertions(+), 2 deletions(-)

diff --git a/Documentation/security/Yama.txt b/Documentation/security/Yama.txt
index dd908cf..1a93b48 100644
--- a/Documentation/security/Yama.txt
+++ b/Documentation/security/Yama.txt
@@ -5,10 +5,54 @@ any other LSM).
 
 Yama is controlled through sysctl in /proc/sys/kernel/yama:
 
+- protected_sticky_symlinks
+- protected_nonaccess_hardlinks
 - ptrace_scope
 
 ==============================================================
 
+protected_sticky_symlinks:
+
+A long-standing class of security issues is the symlink-based
+time-of-check-time-of-use race, most commonly seen in world-writable
+directories like /tmp. The common method of exploitation of this flaw
+is to cross privilege boundaries when following a given symlink (i.e. a
+root process follows a symlink belonging to another user). For a likely
+incomplete list of hundreds of examples across the years, please see:
+http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=/tmp
+
+When set to "0", symlink following behavior is unrestricted.
+
+When set to "1" symlinks are permitted to be followed only 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.
+
+This protection is based on the restrictions in Openwall and grsecurity.
+
+==============================================================
+
+protected_nonaccess_hardlinks:
+
+Hardlinks can be abused in a similar fashion to symlinks in sticky
+world-writable directories, but their weakness is not limited to
+just that scenario. For example, if /etc and /home are on the same
+partition, a regular user can create a hardlink to /etc/shadow in their
+home directory. While it retains the original owner and permissions,
+it is possible for privileged programs that are otherwise symlink-safe
+to mistakenly access the file through its hardlink. Additionally, a very
+minor untraceable quota-bypassing local denial of service is possible by
+an attacker exhausting disk space by filling a world-writable directory
+with hardlinks.
+
+When set to "0", hardlink creation behavior is unrestricted.
+
+When set to "1", hardlinks cannot be created to files that a given user
+would be unable to read and write originally, or are otherwise sensitive.
+
+This protection is based on the restrictions in Openwall and grsecurity.
+
+==============================================================
+
 ptrace_scope:
 
 As Linux grows in popularity, it will become a larger target for
diff --git a/include/linux/security.h b/include/linux/security.h
index 8c1d318..acad9c7 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -3052,6 +3052,10 @@ static inline void free_secdata(void *secdata)
 extern int yama_ptrace_access_check(struct task_struct *child,
 				    unsigned int mode);
 extern int yama_ptrace_traceme(struct task_struct *parent);
+extern int yama_path_link(struct dentry *old_dentry, struct path *new_dir,
+			  struct dentry *new_dentry);
+extern int yama_inode_follow_link(struct dentry *dentry,
+				  struct nameidata *nameidata);
 extern void yama_task_free(struct task_struct *task);
 extern int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
 			   unsigned long arg4, unsigned long arg5);
@@ -3067,6 +3071,19 @@ static inline int yama_ptrace_traceme(struct task_struct *parent)
 	return 0;
 }
 
+static inline int yama_path_link(struct dentry *old_dentry,
+				 struct path *new_dir,
+				 struct dentry *new_dentry)
+{
+	return 0;
+}
+
+static inline int yama_inode_follow_link(struct dentry *dentry,
+					 struct nameidata *nameidata)
+{
+	return 0;
+}
+
 static inline void yama_task_free(struct task_struct *task)
 {
 }
diff --git a/security/security.c b/security/security.c
index eaafb1a..023aa53 100644
--- a/security/security.c
+++ b/security/security.c
@@ -432,8 +432,16 @@ int security_path_symlink(struct path *dir, struct dentry *dentry,
 int security_path_link(struct dentry *old_dentry, struct path *new_dir,
 		       struct dentry *new_dentry)
 {
+#ifdef CONFIG_SECURITY_YAMA_STACKED
+	int rc;
+#endif
 	if (unlikely(IS_PRIVATE(old_dentry->d_inode)))
 		return 0;
+#ifdef CONFIG_SECURITY_YAMA_STACKED
+	rc = yama_path_link(old_dentry, new_dir, new_dentry);
+	if (rc)
+		return rc;
+#endif
 	return security_ops->path_link(old_dentry, new_dir, new_dentry);
 }
 
@@ -547,8 +555,16 @@ int security_inode_readlink(struct dentry *dentry)
 
 int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd)
 {
+#ifdef CONFIG_SECURITY_YAMA_STACKED
+	int rc;
+#endif
 	if (unlikely(IS_PRIVATE(dentry->d_inode)))
 		return 0;
+#ifdef CONFIG_SECURITY_YAMA_STACKED
+	rc = yama_inode_follow_link(dentry, nd);
+	if (rc)
+		return rc;
+#endif
 	return security_ops->inode_follow_link(dentry, nd);
 }
 
diff --git a/security/yama/Kconfig b/security/yama/Kconfig
index 20ef514..92b7c32 100644
--- a/security/yama/Kconfig
+++ b/security/yama/Kconfig
@@ -7,8 +7,9 @@ config SECURITY_YAMA
 	help
 	  This selects Yama, which extends DAC support with additional
 	  system-wide security settings beyond regular Linux discretionary
-	  access controls. Currently available is ptrace scope restriction.
-	  Further information can be found in Documentation/security/Yama.txt.
+	  access controls. Currently available are symlink, hardlink, and
+	  ptrace scope restrictions. Further information can be found in
+	  Documentation/security/Yama.txt.
 
 	  If you are unsure how to answer this question, answer N.
 
diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c
index da219ee..82b7edb 100644
--- a/security/yama/yama_lsm.c
+++ b/security/yama/yama_lsm.c
@@ -15,6 +15,7 @@
 #include <linux/security.h>
 #include <linux/sysctl.h>
 #include <linux/ptrace.h>
+#include <linux/fs.h>
 #include <linux/prctl.h>
 #include <linux/ratelimit.h>
 
@@ -24,6 +25,8 @@
 #define YAMA_SCOPE_NO_ATTACH	3
 
 static int ptrace_scope = YAMA_SCOPE_RELATIONAL;
+static int protected_sticky_symlinks = 1;
+static int protected_nonaccess_hardlinks = 1;
 
 /* describe a ptrace relationship for potential exception */
 struct ptrace_relation {
@@ -330,6 +333,119 @@ int yama_ptrace_traceme(struct task_struct *parent)
 	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.
+ */
+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;
+
+	/* if inode isn't a symlink, don't try to evaluate blocking it */
+	inode = dentry->d_inode;
+	if (!S_ISLNK(inode->i_mode))
+		return 0;
+
+	/* owner and follower match? */
+	cred = current_cred();
+	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;
+}
+
+static int yama_generic_permission(struct inode *inode, int mask)
+{
+	int retval;
+
+	if (inode->i_op->permission)
+		retval = inode->i_op->permission(inode, mask);
+	else
+		retval = generic_permission(inode, mask);
+	return retval;
+}
+
+/**
+ * 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.
+ */
+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)) ||
+	     (yama_generic_permission(inode, MAY_READ | MAY_WRITE))) &&
+	    !capable(CAP_FOWNER)) {
+		char name[sizeof(current->comm)];
+		printk_ratelimited(KERN_NOTICE "non-accessible hardlink"
+			" creation was attempted by: %s (fsuid %d)\n",
+			get_task_comm(name, current),
+			cred->fsuid);
+		rc = -EPERM;
+	}
+
+	return rc;
+}
+
 #ifndef CONFIG_SECURITY_YAMA_STACKED
 static struct security_operations yama_ops = {
 	.name =			"yama",
@@ -338,6 +454,8 @@ static struct security_operations yama_ops = {
 	.ptrace_traceme =	yama_ptrace_traceme,
 	.task_prctl =		yama_task_prctl,
 	.task_free =		yama_task_free,
+	.inode_follow_link =	yama_inode_follow_link,
+	.path_link =		yama_path_link,
 };
 #endif
 
@@ -373,6 +491,24 @@ struct ctl_path yama_sysctl_path[] = {
 
 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),
-- 
1.9.rc1





More information about the kernel-team mailing list