diff options
author | Kees Cook <keescook@chromium.org> | 2011-11-30 14:20:13 -0800 |
---|---|---|
committer | John Rigby <john.rigby@linaro.org> | 2012-06-24 16:28:08 -0600 |
commit | c6242be4e4d3e734a5abf1891768a1667e03fb05 (patch) | |
tree | b94dda5139d65323cb700119b4060d9958c0e7cd /security | |
parent | 93dad4fa5941d18546b5bdcd5730302e20fd63da (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/Kconfig | 5 | ||||
-rw-r--r-- | security/yama/yama_lsm.c | 138 |
2 files changed, 141 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 573723843a0..4c6c2decb88 100644 --- a/security/yama/yama_lsm.c +++ b/security/yama/yama_lsm.c @@ -17,8 +17,13 @@ #include <linux/ptrace.h> #include <linux/prctl.h> #include <linux/ratelimit.h> +#include <linux/stat.h> +#include <linux/dcache.h> +#include <linux/fs.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 { @@ -270,10 +275,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, }; @@ -290,6 +410,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), |