aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKees Cook <kees@ubuntu.com>2010-05-12 09:03:08 -0700
committerLeann Ogasawara <leann.ogasawara@canonical.com>2010-06-14 08:51:19 -0700
commit069cb89e17c6dc5b2a1de2469746bc42935850fb (patch)
tree2aae93751c776fe3c7de81570a1097bec01e993b
parent40f37f14877d4161d5c78bd8e1bd5e33ac17c929 (diff)
UBUNTU: SAUCE: fs: block hardlinks to non-accessible sources
Hardlinks can be abused in a similar fashion to symlinks above, but they are not limited to world-writable directories. 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. The solution is to not allow the creation of hardlinks to files that a given user would be unable to read or write originally, or are otherwise sensitive. Some links to the history of its discussion: 1997 Dec, Yuri Kuzmenko http://lkml.org/lkml/1997/12/29/20 2002 Apr, Chris Wright http://lkml.org/lkml/2002/4/13/99 Past objections and rebuttals could be summarized as: - Violates POSIX. - POSIX didn't consider this situation, and it's not useful to follow a broken specification at the cost of security. Also, please reference where POSIX says this. - Might break atd, courier, and other unknown applications that use this feature. - These applications are easy to spot and can be tested and fixed. Applications that are vulnerable to hardlink attacks by not having the change aren't. - Applications should correctly drop privileges before attempting to access user files. - True, but applications are not perfect, and new software is written all the time that makes these mistakes; blocking this flaw at the kernel is a single solution to the entire class of vulnerability. This patch is based on the patch in grsecurity, which is similar to the patch in Openwall. I have added a sysctl to toggle the behavior back to the old handling via /proc/sys/fs/weak-nonaccess-hardlinks, as well as a ratelimited deprecation warning. Signed-off-by: Kees Cook <kees.cook@canonical.com> Acked-by: Tim Gardner <tim.gardner@canonical.com> Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com>
-rw-r--r--include/linux/security.h4
-rw-r--r--kernel/sysctl.c10
-rw-r--r--security/apparmor/lsm.c5
-rw-r--r--security/capability.c6
-rw-r--r--security/commoncap.c44
5 files changed, 62 insertions, 7 deletions
diff --git a/include/linux/security.h b/include/linux/security.h
index a06d5685326..edc137ff41f 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -79,6 +79,10 @@ extern int cap_task_setioprio(struct task_struct *p, int ioprio);
extern int cap_task_setnice(struct task_struct *p, int nice);
extern int cap_syslog(int type, bool from_file);
extern int cap_vm_enough_memory(struct mm_struct *mm, long pages);
+#ifdef CONFIG_SECURITY_PATH
+extern int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry);
+#endif
struct msghdr;
struct sk_buff;
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 313bc9a8255..9be395856eb 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -89,6 +89,7 @@ extern int max_threads;
extern int core_uses_pid;
extern int suid_dumpable;
extern int weak_sticky_symlinks;
+extern int weak_nonaccess_hardlinks;
extern char core_pattern[];
extern unsigned int core_pipe_limit;
extern int pid_max;
@@ -1471,6 +1472,15 @@ static struct ctl_table fs_table[] = {
.mode = 0644,
.proc_handler = &proc_dointvec,
},
+#ifdef CONFIG_SECURITY_PATH
+ {
+ .procname = "weak-nonaccess-hardlinks",
+ .data = &weak_nonaccess_hardlinks,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+#endif
#if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE)
{
.procname = "binfmt_misc",
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 814b086f072..82e222dce8b 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -317,7 +317,10 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry)
{
struct aa_profile *profile;
- int error = 0;
+ int error = 0, rc;
+
+ if ( (rc = cap_path_link(old_dentry, new_dir, new_dentry)) )
+ return rc;
if (!mediated_filesystem(old_dentry->d_inode))
return 0;
diff --git a/security/capability.c b/security/capability.c
index ff34291111a..76593e227c1 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -250,12 +250,6 @@ static int cap_path_symlink(struct path *dir, struct dentry *dentry,
return 0;
}
-static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
- struct dentry *new_dentry)
-{
- return 0;
-}
-
static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
struct path *new_path, struct dentry *new_dentry)
{
diff --git a/security/commoncap.c b/security/commoncap.c
index 167f170e8e9..b906188f349 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -31,6 +31,8 @@
/* sysctl for symlink permissions checking */
int weak_sticky_symlinks;
+/* sysctl for hardlink permissions checking */
+int weak_nonaccess_hardlinks;
/*
* If a non-root user executes a setuid-root binary in
@@ -305,6 +307,48 @@ int cap_inode_follow_link(struct dentry *dentry,
return 0;
}
+#ifdef CONFIG_SECURITY_PATH
+/*
+ * cap_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 or write
+ *
+ * Returns 0 if successful, -ve on error.
+ */
+int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry)
+{
+ struct inode *inode = old_dentry->d_inode;
+ const int mode = inode->i_mode;
+ const struct cred *cred = current_cred();
+
+ if (weak_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)) {
+ printk_ratelimited(KERN_INFO "deprecated non-accessible"
+ " hardlink creation was attempted by: %s\n",
+ current->comm);
+ return -EPERM;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_SECURITY_PATH */
+
/*
* Calculate the new process capability sets from the capability sets attached
* to a file.