aboutsummaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorKees Cook <keescook@chromium.org>2011-11-30 14:20:13 -0800
committerTim Gardner <tim.gardner@canonical.com>2012-03-19 11:29:56 -0600
commitad6edf1ec8ccf6673d931340cdfec237db38b640 (patch)
treefe670fd1947be2647e2949adf19c5413828387c4 /security
parentecd35f1cca9c7d2ea459e67e78acc0438aa90cd1 (diff)
UBUNTU: SAUCE: Yama: add link restrictions
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@chromium.org> Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
Diffstat (limited to 'security')
-rw-r--r--security/yama/Kconfig5
-rw-r--r--security/yama/yama_lsm.c135
2 files changed, 138 insertions, 2 deletions
diff --git a/security/yama/Kconfig b/security/yama/Kconfig
index 51d6709d8bb..1a5d1c1a7db 100644
--- a/security/yama/Kconfig
+++ b/security/yama/Kconfig
@@ -7,7 +7,8 @@ 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 d8f892c108d..a70e9d7c4a8 100644
--- a/security/yama/yama_lsm.c
+++ b/security/yama/yama_lsm.c
@@ -19,6 +19,8 @@
#include <linux/ratelimit.h>
static int ptrace_scope = 1;
+static int protected_sticky_symlinks = 1;
+static int protected_nonaccess_hardlinks = 1;
/* describe a ptrace relationship for potential exception */
struct ptrace_relation {
@@ -271,10 +273,125 @@ static int yama_ptrace_access_check(struct task_struct *child,
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;
+
+ /* 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.
+ */
+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)) ||
+ (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;
+}
+
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,
.task_prctl = yama_task_prctl,
.task_free = yama_task_free,
};
@@ -291,6 +408,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),