aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorKees Cook <keescook@chromium.org>2012-07-25 20:29:07 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2012-07-29 13:37:58 -0400
commit800179c9b8a1e796e441674776d11cd4c05d61d7 (patch)
tree5760992f4453c35b57b2686d8b8d5caee239b637 /fs
parent3134f37e931d75931bdf6d4eacd82a3fd26eca7c (diff)
fs: add link restrictions
This adds symlink and hardlink restrictions to the Linux VFS. 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 The solution is to 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. Some pointers to the history of earlier discussion that I could find: 1996 Aug, Zygo Blaxell http://marc.info/?l=bugtraq&m=87602167419830&w=2 1996 Oct, Andrew Tridgell http://lkml.indiana.edu/hypermail/linux/kernel/9610.2/0086.html 1997 Dec, Albert D Cahalan http://lkml.org/lkml/1997/12/16/4 2005 Feb, Lorenzo Hernández García-Hierro http://lkml.indiana.edu/hypermail/linux/kernel/0502.0/1896.html 2010 May, Kees Cook https://lkml.org/lkml/2010/5/30/144 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. - Might break unknown applications that use this feature. - Applications that break because of the change are easy to spot and fix. Applications that are vulnerable to symlink ToCToU by not having the change aren't. Additionally, no applications have yet been found that rely on this behavior. - Applications should just use mkstemp() or O_CREATE|O_EXCL. - 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 should live in the core VFS. - This should live in an LSM. (https://lkml.org/lkml/2010/5/31/135) - This should live in an LSM. - This should live in the core VFS. (https://lkml.org/lkml/2010/8/2/188) Hardlinks: On systems that have user-writable directories on the same partition as system files, a long-standing class of security issues is the hardlink-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 hardlink (i.e. a root process follows a hardlink created by another user). Additionally, an issue exists where users can "pin" a potentially vulnerable setuid/setgid file so that an administrator will not actually upgrade a system fully. The solution is to permit hardlinks to only be created when the user is already the existing file's owner, or if they already have read/write access to the existing file. Many Linux users are surprised when they learn they can link to files they have no access to, so this change appears to follow the doctrine of "least surprise". Additionally, this change does not violate POSIX, which states "the implementation may require that the calling process has permission to access the existing file"[1]. This change is known to break some implementations of the "at" daemon, though the version used by Fedora and Ubuntu has been fixed[2] for a while. Otherwise, the change has been undisruptive while in use in Ubuntu for the last 1.5 years. [1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html [2] http://anonscm.debian.org/gitweb/?p=collab-maint/at.git;a=commitdiff;h=f4114656c3a6c6f6070e315ffdf940a49eda3279 This patch is based on the patches in Openwall and grsecurity, along with suggestions from Al Viro. I have added a sysctl to enable the protected behavior, and documentation. Signed-off-by: Kees Cook <keescook@chromium.org> Acked-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs')
-rw-r--r--fs/namei.c122
1 files changed, 122 insertions, 0 deletions
diff --git a/fs/namei.c b/fs/namei.c
index afa087649ddb..3861d85f8488 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -650,6 +650,119 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki
650 path_put(link); 650 path_put(link);
651} 651}
652 652
653int sysctl_protected_symlinks __read_mostly = 1;
654int sysctl_protected_hardlinks __read_mostly = 1;
655
656/**
657 * may_follow_link - Check symlink following for unsafe situations
658 * @link: The path of the symlink
659 *
660 * In the case of the sysctl_protected_symlinks sysctl being enabled,
661 * CAP_DAC_OVERRIDE needs to be specifically ignored if the symlink is
662 * in a sticky world-writable directory. This is to protect privileged
663 * processes from failing races against path names that may change out
664 * from under them by way of other users creating malicious symlinks.
665 * It will permit symlinks to be followed only when outside a sticky
666 * world-writable directory, or when the uid of the symlink and follower
667 * match, or when the directory owner matches the symlink's owner.
668 *
669 * Returns 0 if following the symlink is allowed, -ve on error.
670 */
671static inline int may_follow_link(struct path *link, struct nameidata *nd)
672{
673 const struct inode *inode;
674 const struct inode *parent;
675
676 if (!sysctl_protected_symlinks)
677 return 0;
678
679 /* Allowed if owner and follower match. */
680 inode = link->dentry->d_inode;
681 if (current_cred()->fsuid == inode->i_uid)
682 return 0;
683
684 /* Allowed if parent directory not sticky and world-writable. */
685 parent = nd->path.dentry->d_inode;
686 if ((parent->i_mode & (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH))
687 return 0;
688
689 /* Allowed if parent directory and link owner match. */
690 if (parent->i_uid == inode->i_uid)
691 return 0;
692
693 path_put_conditional(link, nd);
694 path_put(&nd->path);
695 return -EACCES;
696}
697
698/**
699 * safe_hardlink_source - Check for safe hardlink conditions
700 * @inode: the source inode to hardlink from
701 *
702 * Return false if at least one of the following conditions:
703 * - inode is not a regular file
704 * - inode is setuid
705 * - inode is setgid and group-exec
706 * - access failure for read and write
707 *
708 * Otherwise returns true.
709 */
710static bool safe_hardlink_source(struct inode *inode)
711{
712 umode_t mode = inode->i_mode;
713
714 /* Special files should not get pinned to the filesystem. */
715 if (!S_ISREG(mode))
716 return false;
717
718 /* Setuid files should not get pinned to the filesystem. */
719 if (mode & S_ISUID)
720 return false;
721
722 /* Executable setgid files should not get pinned to the filesystem. */
723 if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
724 return false;
725
726 /* Hardlinking to unreadable or unwritable sources is dangerous. */
727 if (inode_permission(inode, MAY_READ | MAY_WRITE))
728 return false;
729
730 return true;
731}
732
733/**
734 * may_linkat - Check permissions for creating a hardlink
735 * @link: the source to hardlink from
736 *
737 * Block hardlink when all of:
738 * - sysctl_protected_hardlinks enabled
739 * - fsuid does not match inode
740 * - hardlink source is unsafe (see safe_hardlink_source() above)
741 * - not CAP_FOWNER
742 *
743 * Returns 0 if successful, -ve on error.
744 */
745static int may_linkat(struct path *link)
746{
747 const struct cred *cred;
748 struct inode *inode;
749
750 if (!sysctl_protected_hardlinks)
751 return 0;
752
753 cred = current_cred();
754 inode = link->dentry->d_inode;
755
756 /* Source inode owner (or CAP_FOWNER) can hardlink all they like,
757 * otherwise, it must be a safe source.
758 */
759 if (cred->fsuid == inode->i_uid || safe_hardlink_source(inode) ||
760 capable(CAP_FOWNER))
761 return 0;
762
763 return -EPERM;
764}
765
653static __always_inline int 766static __always_inline int
654follow_link(struct path *link, struct nameidata *nd, void **p) 767follow_link(struct path *link, struct nameidata *nd, void **p)
655{ 768{
@@ -1818,6 +1931,9 @@ static int path_lookupat(int dfd, const char *name,
1818 while (err > 0) { 1931 while (err > 0) {
1819 void *cookie; 1932 void *cookie;
1820 struct path link = path; 1933 struct path link = path;
1934 err = may_follow_link(&link, nd);
1935 if (unlikely(err))
1936 break;
1821 nd->flags |= LOOKUP_PARENT; 1937 nd->flags |= LOOKUP_PARENT;
1822 err = follow_link(&link, nd, &cookie); 1938 err = follow_link(&link, nd, &cookie);
1823 if (err) 1939 if (err)
@@ -2778,6 +2894,9 @@ static struct file *path_openat(int dfd, const char *pathname,
2778 error = -ELOOP; 2894 error = -ELOOP;
2779 break; 2895 break;
2780 } 2896 }
2897 error = may_follow_link(&link, nd);
2898 if (unlikely(error))
2899 break;
2781 nd->flags |= LOOKUP_PARENT; 2900 nd->flags |= LOOKUP_PARENT;
2782 nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); 2901 nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
2783 error = follow_link(&link, nd, &cookie); 2902 error = follow_link(&link, nd, &cookie);
@@ -3421,6 +3540,9 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
3421 error = -EXDEV; 3540 error = -EXDEV;
3422 if (old_path.mnt != new_path.mnt) 3541 if (old_path.mnt != new_path.mnt)
3423 goto out_dput; 3542 goto out_dput;
3543 error = may_linkat(&old_path);
3544 if (unlikely(error))
3545 goto out_dput;
3424 error = security_path_link(old_path.dentry, &new_path, new_dentry); 3546 error = security_path_link(old_path.dentry, &new_path, new_dentry);
3425 if (error) 3547 if (error)
3426 goto out_dput; 3548 goto out_dput;