fs: Update link security restrictions to match Linux 3.6:

- Drop kconfig options; restrictions can only be disabled by sysctl
- Change the audit message type from AUDIT_AVC (1400) to AUDIT_ANON_LINK (1702)

svn path=/dists/sid/linux/; revision=19310
This commit is contained in:
Ben Hutchings 2012-08-05 21:02:07 +00:00
parent b91d68d275
commit 4a4fd06c67
9 changed files with 456 additions and 1026 deletions

4
debian/changelog vendored
View File

@ -57,6 +57,10 @@ linux (3.2.26-1) UNRELEASED; urgency=low
* net: Add byte queue limits (bql) for reduced buffer-bloat
* bnx2,bnx2x,e1000e,forcedeth,igb,ixgbe,r8169,sfc,skge,sky2,tg3:
Add support for bql
* fs: Update link security restrictions to match Linux 3.6:
- Drop kconfig options; restrictions can only be disabled by sysctl
- Change the audit message type from AUDIT_AVC (1400) to
AUDIT_ANON_LINK (1702)
-- Ben Hutchings <ben@decadent.org.uk> Tue, 24 Jul 2012 02:20:37 +0100

View File

@ -0,0 +1,93 @@
From: Kees Cook <keescook@chromium.org>
Date: Wed, 25 Jul 2012 17:29:08 -0700
Subject: [2/2] fs: add link restriction audit reporting
commit a51d9eaa41866ab6b4b6ecad7b621f8b66ece0dc upstream.
Adds audit messages for unexpected link restriction violations so that
system owners will have some sort of potentially actionable information
about misbehaving processes.
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
fs/namei.c | 2 ++
include/linux/audit.h | 4 ++++
kernel/audit.c | 21 +++++++++++++++++++++
3 files changed, 27 insertions(+)
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -666,6 +666,7 @@ static inline int may_follow_link(struct
path_put_conditional(link, nd);
path_put(&nd->path);
+ audit_log_link_denied("follow_link", link);
return -EACCES;
}
@@ -734,6 +735,7 @@ static int may_linkat(struct path *link)
capable(CAP_FOWNER))
return 0;
+ audit_log_link_denied("linkat", link);
return -EPERM;
}
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -129,6 +129,7 @@
#define AUDIT_LAST_KERN_ANOM_MSG 1799
#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */
+#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */
#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */
#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */
#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */
@@ -611,6 +612,8 @@ extern void audit_log_d_path(struct
struct path *path);
extern void audit_log_key(struct audit_buffer *ab,
char *key);
+extern void audit_log_link_denied(const char *operation,
+ struct path *link);
extern void audit_log_lost(const char *message);
#ifdef CONFIG_SECURITY
extern void audit_log_secctx(struct audit_buffer *ab, u32 secid);
@@ -640,6 +643,7 @@ extern int audit_enabled;
#define audit_log_untrustedstring(a,s) do { ; } while (0)
#define audit_log_d_path(b, p, d) do { ; } while (0)
#define audit_log_key(b, k) do { ; } while (0)
+#define audit_log_link_denied(o, l) do { ; } while (0)
#define audit_log_secctx(b,s) do { ; } while (0)
#define audit_enabled 0
#endif
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1449,6 +1449,27 @@ void audit_log_key(struct audit_buffer *
}
/**
+ * audit_log_link_denied - report a link restriction denial
+ * @operation: specific link opreation
+ * @link: the path that triggered the restriction
+ */
+void audit_log_link_denied(const char *operation, struct path *link)
+{
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(current->audit_context, GFP_KERNEL,
+ AUDIT_ANOM_LINK);
+ audit_log_format(ab, "op=%s action=denied", operation);
+ audit_log_format(ab, " pid=%d comm=", current->pid);
+ audit_log_untrustedstring(ab, current->comm);
+ audit_log_d_path(ab, " path=", link);
+ audit_log_format(ab, " dev=");
+ audit_log_untrustedstring(ab, link->dentry->d_inode->i_sb->s_id);
+ audit_log_format(ab, " ino=%lu", link->dentry->d_inode->i_ino);
+ audit_log_end(ab);
+}
+
+/**
* audit_log_end - end one audit record
* @ab: the audit_buffer
*

View File

@ -0,0 +1,356 @@
From: Kees Cook <keescook@chromium.org>
Date: Wed, 25 Jul 2012 17:29:07 -0700
Subject: [1/2] fs: add link restrictions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
commit 800179c9b8a1e796e441674776d11cd4c05d61d7 upstream.
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>
[bwh: Backported to 3.2:
- Adjust context
- In path_openat(), convert error from may_follow_link() to filp as it
won't be converted outside the loop]
---
Documentation/sysctl/fs.txt | 42 +++++++++++++++
fs/namei.c | 122 +++++++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 2 +
kernel/sysctl.c | 18 +++++++
4 files changed, 184 insertions(+)
--- a/Documentation/sysctl/fs.txt
+++ b/Documentation/sysctl/fs.txt
@@ -32,6 +32,8 @@ Currently, these files are in /proc/sys/
- nr_open
- overflowuid
- overflowgid
+- protected_hardlinks
+- protected_symlinks
- suid_dumpable
- super-max
- super-nr
@@ -157,6 +159,46 @@ The default is 65534.
==============================================================
+protected_hardlinks:
+
+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,
+on systems without separated partitions, this stops unauthorized users
+from "pinning" vulnerable setuid/setgid files against being upgraded by
+the administrator, or linking to special files.
+
+When set to "0", hardlink creation behavior is unrestricted.
+
+When set to "1" hardlinks cannot be created by users if they do not
+already own the source file, or do not have read/write access to it.
+
+This protection is based on the restrictions in Openwall and grsecurity.
+
+==============================================================
+
+protected_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.
+
+==============================================================
+
suid_dumpable:
This value can be used to query and set the core dump mode for setuid
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -624,6 +624,119 @@ static inline void put_link(struct namei
path_put(link);
}
+int sysctl_protected_symlinks __read_mostly = 1;
+int sysctl_protected_hardlinks __read_mostly = 1;
+
+/**
+ * may_follow_link - Check symlink following for unsafe situations
+ * @link: The path of the symlink
+ *
+ * In the case of the sysctl_protected_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 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.
+ *
+ * Returns 0 if following the symlink is allowed, -ve on error.
+ */
+static inline int may_follow_link(struct path *link, struct nameidata *nd)
+{
+ const struct inode *inode;
+ const struct inode *parent;
+
+ if (!sysctl_protected_symlinks)
+ return 0;
+
+ /* Allowed if owner and follower match. */
+ inode = link->dentry->d_inode;
+ if (current_cred()->fsuid == inode->i_uid)
+ return 0;
+
+ /* Allowed if parent directory not sticky and world-writable. */
+ parent = nd->path.dentry->d_inode;
+ if ((parent->i_mode & (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH))
+ return 0;
+
+ /* Allowed if parent directory and link owner match. */
+ if (parent->i_uid == inode->i_uid)
+ return 0;
+
+ path_put_conditional(link, nd);
+ path_put(&nd->path);
+ return -EACCES;
+}
+
+/**
+ * safe_hardlink_source - Check for safe hardlink conditions
+ * @inode: the source inode to hardlink from
+ *
+ * Return false if at least one of the following conditions:
+ * - inode is not a regular file
+ * - inode is setuid
+ * - inode is setgid and group-exec
+ * - access failure for read and write
+ *
+ * Otherwise returns true.
+ */
+static bool safe_hardlink_source(struct inode *inode)
+{
+ umode_t mode = inode->i_mode;
+
+ /* Special files should not get pinned to the filesystem. */
+ if (!S_ISREG(mode))
+ return false;
+
+ /* Setuid files should not get pinned to the filesystem. */
+ if (mode & S_ISUID)
+ return false;
+
+ /* Executable setgid files should not get pinned to the filesystem. */
+ if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
+ return false;
+
+ /* Hardlinking to unreadable or unwritable sources is dangerous. */
+ if (inode_permission(inode, MAY_READ | MAY_WRITE))
+ return false;
+
+ return true;
+}
+
+/**
+ * may_linkat - Check permissions for creating a hardlink
+ * @link: the source to hardlink from
+ *
+ * Block hardlink when all of:
+ * - sysctl_protected_hardlinks enabled
+ * - fsuid does not match inode
+ * - hardlink source is unsafe (see safe_hardlink_source() above)
+ * - not CAP_FOWNER
+ *
+ * Returns 0 if successful, -ve on error.
+ */
+static int may_linkat(struct path *link)
+{
+ const struct cred *cred;
+ struct inode *inode;
+
+ if (!sysctl_protected_hardlinks)
+ return 0;
+
+ cred = current_cred();
+ inode = link->dentry->d_inode;
+
+ /* Source inode owner (or CAP_FOWNER) can hardlink all they like,
+ * otherwise, it must be a safe source.
+ */
+ if (cred->fsuid == inode->i_uid || safe_hardlink_source(inode) ||
+ capable(CAP_FOWNER))
+ return 0;
+
+ return -EPERM;
+}
+
static __always_inline int
follow_link(struct path *link, struct nameidata *nd, void **p)
{
@@ -1613,6 +1726,9 @@ static int path_lookupat(int dfd, const
while (err > 0) {
void *cookie;
struct path link = path;
+ err = may_follow_link(&link, nd);
+ if (unlikely(err))
+ break;
nd->flags |= LOOKUP_PARENT;
err = follow_link(&link, nd, &cookie);
if (!err)
@@ -2325,6 +2441,11 @@ static struct file *path_openat(int dfd,
filp = ERR_PTR(-ELOOP);
break;
}
+ error = may_follow_link(&link, nd);
+ if (unlikely(error)) {
+ filp = ERR_PTR(error);
+ break;
+ }
nd->flags |= LOOKUP_PARENT;
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
error = follow_link(&link, nd, &cookie);
@@ -2972,6 +3093,9 @@ SYSCALL_DEFINE5(linkat, int, olddfd, con
error = -EXDEV;
if (old_path.mnt != new_path.mnt)
goto out_dput;
+ error = may_linkat(&old_path);
+ if (unlikely(error))
+ goto out_dput;
error = mnt_want_write(new_path.mnt);
if (error)
goto out_dput;
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -420,6 +420,8 @@ extern unsigned long get_max_files(void)
extern int sysctl_nr_open;
extern struct inodes_stat_t inodes_stat;
extern int leases_enable, lease_break_time;
+extern int sysctl_protected_symlinks;
+extern int sysctl_protected_hardlinks;
struct buffer_head;
typedef int (get_block_t)(struct inode *inode, sector_t iblock,
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1495,6 +1495,24 @@ static struct ctl_table fs_table[] = {
#endif
#endif
{
+ .procname = "protected_symlinks",
+ .data = &sysctl_protected_symlinks,
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = &zero,
+ .extra2 = &one,
+ },
+ {
+ .procname = "protected_hardlinks",
+ .data = &sysctl_protected_hardlinks,
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = &zero,
+ .extra2 = &one,
+ },
+ {
.procname = "suid_dumpable",
.data = &suid_dumpable,
.maxlen = sizeof(int),

View File

@ -1,112 +0,0 @@
From 52db90d0fa770e2277645eb34956820cec26b2cb Mon Sep 17 00:00:00 2001
From: Kees Cook <keescook@chromium.org>
Date: Sat, 25 Feb 2012 12:28:44 +1100
Subject: [PATCH 5/5] fs: hardlink creation restriction cleanup
Clean-up of hardlink restriction logic, as suggested by Andrew Morton.
Signed-off-by: Kees Cook <keescook@chromium.org>
Cc: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
fs/namei.c | 62 ++++++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 44 insertions(+), 18 deletions(-)
diff --git a/fs/namei.c b/fs/namei.c
index fe13533..1436fae 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -693,46 +693,72 @@ static inline int may_follow_link(struct path *link)
}
/**
+ * safe_hardlink_source - Check for safe hardlink conditions
+ * @inode: the source inode to hardlink from
+ *
+ * Return false if at least one of the following conditions:
+ * - inode is not a regular file
+ * - inode is setuid
+ * - inode is setgid and group-exec
+ * - access failure for read and write
+ *
+ * Otherwise returns true.
+ */
+static bool safe_hardlink_source(struct inode *inode)
+{
+ mode_t mode = inode->i_mode;
+
+ /* Special files should not get pinned to the filesystem. */
+ if (!S_ISREG(mode))
+ return false;
+
+ /* Setuid files should not get pinned to the filesystem. */
+ if (mode & S_ISUID)
+ return false;
+
+ /* Executable setgid files should not get pinned to the filesystem. */
+ if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
+ return false;
+
+ /* Hardlinking to unreadable or unwritable sources is dangerous. */
+ if (inode_permission(inode, MAY_READ | MAY_WRITE))
+ return false;
+
+ return true;
+}
+
+/**
* may_linkat - Check permissions for creating a hardlink
* @link: the source to hardlink from
*
* Block hardlink when all of:
* - sysctl_protected_hardlinks enabled
* - fsuid does not match inode
- * - 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
+ * - hardlink source is unsafe (see safe_hardlink_source() above)
* - not CAP_FOWNER
*
* Returns 0 if successful, -ve on error.
*/
static int may_linkat(struct path *link)
{
- int error = 0;
const struct cred *cred;
struct inode *inode;
- int mode;
if (!sysctl_protected_hardlinks)
return 0;
cred = current_cred();
inode = link->dentry->d_inode;
- mode = inode->i_mode;
-
- if (cred->fsuid != inode->i_uid &&
- (!S_ISREG(mode) || (mode & S_ISUID) ||
- ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) ||
- (inode_permission(inode, MAY_READ | MAY_WRITE))) &&
- !capable(CAP_FOWNER))
- error = -EPERM;
- if (error)
- audit_log_link_denied("linkat", link);
+ /* Source inode owner (or CAP_FOWNER) can hardlink all they like,
+ * otherwise, it must be a safe source.
+ */
+ if (cred->fsuid == inode->i_uid || safe_hardlink_source(inode) ||
+ capable(CAP_FOWNER))
+ return 0;
- return error;
+ audit_log_link_denied("linkat", link);
+ return -EPERM;
}
#else
static inline int may_follow_link(struct path *link)
--
1.7.9.1

View File

@ -1,39 +0,0 @@
From 19f621ccbef745dedad641f44f535e3bcb00f30d Mon Sep 17 00:00:00 2001
From: Andrew Morton <akpm@linux-foundation.org>
Date: Sat, 25 Feb 2012 12:28:43 +1100
Subject: [PATCH 4/5] fs-hardlink-creation-restrictions-fix
uninline may_linkat() and audit_log_link_denied().
Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
fs/namei.c | 5 ++---
1 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/fs/namei.c b/fs/namei.c
index 7be190c..fe13533 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -629,8 +629,7 @@ int sysctl_protected_symlinks __read_mostly =
int sysctl_protected_hardlinks __read_mostly =
CONFIG_PROTECTED_HARDLINKS_SYSCTL;
-static inline void
-audit_log_link_denied(const char *operation, struct path *link)
+static void audit_log_link_denied(const char *operation, struct path *link)
{
struct audit_buffer *ab;
@@ -709,7 +708,7 @@ static inline int may_follow_link(struct path *link)
*
* Returns 0 if successful, -ve on error.
*/
-static inline int may_linkat(struct path *link)
+static int may_linkat(struct path *link)
{
int error = 0;
const struct cred *cred;
--
1.7.9.1

View File

@ -1,390 +0,0 @@
From fa3abdeee4e792ed794eef7ea71e7e0073cec32d Mon Sep 17 00:00:00 2001
From: Kees Cook <keescook@chromium.org>
Date: Sat, 25 Feb 2012 12:28:43 +1100
Subject: [PATCH 3/5] fs: hardlink creation restrictions
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.
This patch is based on the patch in Openwall and grsecurity. I have added
a sysctl to enable the protected behavior, documentation, and an audit
notification.
[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
Signed-off-by: Kees Cook <keescook@chromium.org>
Acked-by: Ingo Molnar <mingo@elte.hu>
Cc: Matthew Wilcox <matthew@wil.cx>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Rik van Riel <riel@redhat.com>
Cc: Federica Teodori <federica.teodori@googlemail.com>
Cc: Lucian Adrian Grijincu <lucian.grijincu@gmail.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Eric Paris <eparis@redhat.com>
Cc: Randy Dunlap <rdunlap@xenotime.net>
Cc: Dan Rosenberg <drosenberg@vsecurity.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
Documentation/sysctl/fs.txt | 21 ++++++++
fs/Kconfig | 54 ++++++++++++++++------
fs/namei.c | 109 ++++++++++++++++++++++++++++++++-----------
include/linux/fs.h | 1 +
kernel/sysctl.c | 11 ++++-
5 files changed, 153 insertions(+), 43 deletions(-)
diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt
index 01daa80..9d29414 100644
--- a/Documentation/sysctl/fs.txt
+++ b/Documentation/sysctl/fs.txt
@@ -32,6 +32,7 @@ Currently, these files are in /proc/sys/fs:
- nr_open
- overflowuid
- overflowgid
+- protected_hardlinks
- protected_symlinks
- suid_dumpable
- super-max
@@ -158,6 +159,26 @@ The default is 65534.
==============================================================
+protected_hardlinks:
+
+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,
+on systems without separated partitions, this stops unauthorized users
+from "pinning" vulnerable setuid/setgid files against being upgraded by
+the administrator, or linking to special files.
+
+When set to "0", hardlink creation behavior is unrestricted.
+
+When set to "1" hardlinks cannot be created by users if they do not
+already own the source file, or do not have read/write access to it.
+
+This protection is based on the restrictions in Openwall and grsecurity.
+
+==============================================================
+
protected_symlinks:
A long-standing class of security issues is the symlink-based
diff --git a/fs/Kconfig b/fs/Kconfig
index f2c46f3..d2a422e 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -272,27 +272,29 @@ endif # NETWORK_FILESYSTEMS
source "fs/nls/Kconfig"
source "fs/dlm/Kconfig"
-config PROTECTED_SYMLINKS
- bool "Evaluate vulnerable symlink conditions"
+config PROTECTED_LINKS
+ bool "Evaluate vulnerable link conditions"
default y
help
- A long-standing class of security issues is the symlink-based
+ A long-standing class of security issues is the link-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 malicious symlink belonging to another user).
+ when following a given link (i.e. a root process follows
+ a malicious symlink belonging to another user, or a hardlink
+ created to a root-owned file).
- Enabling this adds the logic to examine these dangerous symlink
- conditions. Whether or not the dangerous symlink situations are
- allowed is controlled by PROTECTED_SYMLINKS_ENABLED.
+ Enabling this adds the logic to examine these dangerous link
+ conditions. Whether or not the dangerous link situations are
+ allowed is controlled by PROTECTED_HARDLINKS_ENABLED and
+ PROTECTED_SYMLINKS_ENABLED.
-config PROTECTED_SYMLINKS_ENABLED
- depends on PROTECTED_SYMLINKS
+config PROTECTED_SYMLINKS
+ depends on PROTECTED_LINKS
bool "Disallow symlink following in sticky world-writable dirs"
default y
help
- Solve ToCToU symlink race vulnerablities by permitting symlinks
+ Solve ToCToU symlink race vulnerabilities by permitting symlinks
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 and symlink owners match.
@@ -300,10 +302,34 @@ config PROTECTED_SYMLINKS_ENABLED
When PROC_SYSCTL is enabled, this setting can also be controlled
via /proc/sys/kernel/protected_symlinks.
-config PROTECTED_SYMLINKS_ENABLED_SYSCTL
- depends on PROTECTED_SYMLINKS
+ See Documentation/sysctl/fs.txt for details.
+
+config PROTECTED_SYMLINKS_SYSCTL
+ depends on PROTECTED_LINKS
+ int
+ default "1" if PROTECTED_SYMLINKS
+ default "0"
+
+config PROTECTED_HARDLINKS
+ depends on PROTECTED_LINKS
+ bool "Disallow hardlink creation to non-accessible files"
+ default y
+ help
+ Solve ToCToU hardlink race vulnerabilities by permitting hardlinks
+ to be created only when to a regular file that is owned by the user,
+ or is readable and writable by the user. Also blocks users from
+ "pinning" vulnerable setuid/setgid programs from being upgraded by
+ the administrator.
+
+ When PROC_SYSCTL is enabled, this setting can also be controlled
+ via /proc/sys/kernel/protected_hardlinks.
+
+ See Documentation/sysctl/fs.txt for details.
+
+config PROTECTED_HARDLINKS_SYSCTL
+ depends on PROTECTED_LINKS
int
- default "1" if PROTECTED_SYMLINKS_ENABLED
+ default "1" if PROTECTED_HARDLINKS
default "0"
endmenu
diff --git a/fs/namei.c b/fs/namei.c
index 39edcf7..7be190c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -623,16 +623,33 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki
path_put(link);
}
-#ifdef CONFIG_PROTECTED_SYMLINKS
+#ifdef CONFIG_PROTECTED_LINKS
int sysctl_protected_symlinks __read_mostly =
- CONFIG_PROTECTED_SYMLINKS_ENABLED_SYSCTL;
+ CONFIG_PROTECTED_SYMLINKS_SYSCTL;
+int sysctl_protected_hardlinks __read_mostly =
+ CONFIG_PROTECTED_HARDLINKS_SYSCTL;
+
+static inline void
+audit_log_link_denied(const char *operation, struct path *link)
+{
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(current->audit_context, GFP_KERNEL, AUDIT_AVC);
+ audit_log_format(ab, "op=%s action=denied", operation);
+ audit_log_format(ab, " pid=%d comm=", current->pid);
+ audit_log_untrustedstring(ab, current->comm);
+ audit_log_d_path(ab, " path=", link);
+ audit_log_format(ab, " dev=");
+ audit_log_untrustedstring(ab, link->dentry->d_inode->i_sb->s_id);
+ audit_log_format(ab, " ino=%lu", link->dentry->d_inode->i_ino);
+ audit_log_end(ab);
+}
/**
* may_follow_link - Check symlink following for unsafe situations
- * @dentry: The inode/dentry of the symlink
- * @nameidata: The path data of the symlink
+ * @link: The path of the symlink
*
- * In the case of the protected_symlinks sysctl being enabled,
+ * In the case of the sysctl_protected_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
@@ -643,19 +660,20 @@ int sysctl_protected_symlinks __read_mostly =
*
* Returns 0 if following the symlink is allowed, -ve on error.
*/
-static inline int
-may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
+static inline int may_follow_link(struct path *link)
{
int error = 0;
const struct inode *parent;
const struct inode *inode;
const struct cred *cred;
+ struct dentry *dentry;
if (!sysctl_protected_symlinks)
return 0;
/* Allowed if owner and follower match. */
cred = current_cred();
+ dentry = link->dentry;
inode = dentry->d_inode;
if (cred->fsuid == inode->i_uid)
return 0;
@@ -669,29 +687,61 @@ may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
}
spin_unlock(&dentry->d_lock);
-#ifdef CONFIG_AUDIT
- if (error) {
- struct audit_buffer *ab;
-
- ab = audit_log_start(current->audit_context,
- GFP_KERNEL, AUDIT_AVC);
- audit_log_format(ab, "op=follow_link action=denied");
- audit_log_format(ab, " pid=%d comm=", current->pid);
- audit_log_untrustedstring(ab, current->comm);
- audit_log_d_path(ab, " path=", &nameidata->path);
- audit_log_format(ab, " name=");
- audit_log_untrustedstring(ab, dentry->d_name.name);
- audit_log_format(ab, " dev=");
- audit_log_untrustedstring(ab, inode->i_sb->s_id);
- audit_log_format(ab, " ino=%lu", inode->i_ino);
- audit_log_end(ab);
- }
-#endif
+ if (error)
+ audit_log_link_denied("follow_link", link);
+
+ return error;
+}
+
+/**
+ * may_linkat - Check permissions for creating a hardlink
+ * @link: the source to hardlink from
+ *
+ * Block hardlink when all of:
+ * - sysctl_protected_hardlinks enabled
+ * - fsuid does not match inode
+ * - 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
+ * - not CAP_FOWNER
+ *
+ * Returns 0 if successful, -ve on error.
+ */
+static inline int may_linkat(struct path *link)
+{
+ int error = 0;
+ const struct cred *cred;
+ struct inode *inode;
+ int mode;
+
+ if (!sysctl_protected_hardlinks)
+ return 0;
+
+ cred = current_cred();
+ inode = link->dentry->d_inode;
+ mode = inode->i_mode;
+
+ if (cred->fsuid != inode->i_uid &&
+ (!S_ISREG(mode) || (mode & S_ISUID) ||
+ ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) ||
+ (inode_permission(inode, MAY_READ | MAY_WRITE))) &&
+ !capable(CAP_FOWNER))
+ error = -EPERM;
+
+ if (error)
+ audit_log_link_denied("linkat", link);
+
return error;
}
#else
-static inline int
-may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
+static inline int may_follow_link(struct path *link)
+{
+ return 0;
+}
+
+static inline int may_linkat(struct path *link)
{
return 0;
}
@@ -720,7 +770,7 @@ follow_link(struct path *link, struct nameidata *nd, void **p, bool sensitive)
nd_set_link(nd, NULL);
if (sensitive)
- error = may_follow_link(link->dentry, nd);
+ error = may_follow_link(link);
if (!error)
error = security_inode_follow_link(link->dentry, nd);
if (error) {
@@ -3058,6 +3108,9 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
error = -EXDEV;
if (old_path.mnt != new_path.mnt)
goto out_dput;
+ error = may_linkat(&old_path);
+ if (error)
+ goto out_dput;
error = mnt_want_write(new_path.mnt);
if (error)
goto out_dput;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 404cc89..f42a557 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -424,6 +424,7 @@ extern int sysctl_nr_open;
extern struct inodes_stat_t inodes_stat;
extern int leases_enable, lease_break_time;
extern int sysctl_protected_symlinks;
+extern int sysctl_protected_hardlinks;
struct buffer_head;
typedef int (get_block_t)(struct inode *inode, sector_t iblock,
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 0624e7c..0b29d58 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1497,7 +1497,7 @@ static struct ctl_table fs_table[] = {
},
#endif
#endif
-#ifdef CONFIG_PROTECTED_SYMLINKS
+#ifdef CONFIG_PROTECTED_LINKS
{
.procname = "protected_symlinks",
.data = &sysctl_protected_symlinks,
@@ -1507,6 +1507,15 @@ static struct ctl_table fs_table[] = {
.extra1 = &zero,
.extra2 = &one,
},
+ {
+ .procname = "protected_hardlinks",
+ .data = &sysctl_protected_hardlinks,
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = &zero,
+ .extra2 = &one,
+ },
#endif
{
.procname = "suid_dumpable",
--
1.7.9.1

View File

@ -1,151 +0,0 @@
From d48f814bd83a3cbd95dedaf5e4dd91c05cffddc6 Mon Sep 17 00:00:00 2001
From: Kees Cook <keescook@chromium.org>
Date: Sat, 25 Feb 2012 12:28:43 +1100
Subject: [PATCH 2/5] fs-symlink-restrictions-on-sticky-directories-fix-2
s/sticky_//
Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
Documentation/sysctl/fs.txt | 4 ++--
fs/Kconfig | 16 ++++++++--------
fs/namei.c | 10 +++++-----
include/linux/fs.h | 2 +-
kernel/sysctl.c | 6 +++---
5 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt
index 4b47cd5..01daa80 100644
--- a/Documentation/sysctl/fs.txt
+++ b/Documentation/sysctl/fs.txt
@@ -32,7 +32,7 @@ Currently, these files are in /proc/sys/fs:
- nr_open
- overflowuid
- overflowgid
-- protected_sticky_symlinks
+- protected_symlinks
- suid_dumpable
- super-max
- super-nr
@@ -158,7 +158,7 @@ The default is 65534.
==============================================================
-protected_sticky_symlinks:
+protected_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
diff --git a/fs/Kconfig b/fs/Kconfig
index d0fdbdd..f2c46f3 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -272,7 +272,7 @@ endif # NETWORK_FILESYSTEMS
source "fs/nls/Kconfig"
source "fs/dlm/Kconfig"
-config PROTECTED_STICKY_SYMLINKS
+config PROTECTED_SYMLINKS
bool "Evaluate vulnerable symlink conditions"
default y
help
@@ -285,10 +285,10 @@ config PROTECTED_STICKY_SYMLINKS
Enabling this adds the logic to examine these dangerous symlink
conditions. Whether or not the dangerous symlink situations are
- allowed is controlled by PROTECTED_STICKY_SYMLINKS_ENABLED.
+ allowed is controlled by PROTECTED_SYMLINKS_ENABLED.
-config PROTECTED_STICKY_SYMLINKS_ENABLED
- depends on PROTECTED_STICKY_SYMLINKS
+config PROTECTED_SYMLINKS_ENABLED
+ depends on PROTECTED_SYMLINKS
bool "Disallow symlink following in sticky world-writable dirs"
default y
help
@@ -298,12 +298,12 @@ config PROTECTED_STICKY_SYMLINKS_ENABLED
directory and symlink owners match.
When PROC_SYSCTL is enabled, this setting can also be controlled
- via /proc/sys/kernel/protected_sticky_symlinks.
+ via /proc/sys/kernel/protected_symlinks.
-config PROTECTED_STICKY_SYMLINKS_ENABLED_SYSCTL
- depends on PROTECTED_STICKY_SYMLINKS
+config PROTECTED_SYMLINKS_ENABLED_SYSCTL
+ depends on PROTECTED_SYMLINKS
int
- default "1" if PROTECTED_STICKY_SYMLINKS_ENABLED
+ default "1" if PROTECTED_SYMLINKS_ENABLED
default "0"
endmenu
diff --git a/fs/namei.c b/fs/namei.c
index 5b4c05b..39edcf7 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -623,16 +623,16 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki
path_put(link);
}
-#ifdef CONFIG_PROTECTED_STICKY_SYMLINKS
-int sysctl_protected_sticky_symlinks __read_mostly =
- CONFIG_PROTECTED_STICKY_SYMLINKS_ENABLED_SYSCTL;
+#ifdef CONFIG_PROTECTED_SYMLINKS
+int sysctl_protected_symlinks __read_mostly =
+ CONFIG_PROTECTED_SYMLINKS_ENABLED_SYSCTL;
/**
* may_follow_link - Check symlink following for unsafe situations
* @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,
+ * In the case of the protected_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
@@ -651,7 +651,7 @@ may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
const struct inode *inode;
const struct cred *cred;
- if (!sysctl_protected_sticky_symlinks)
+ if (!sysctl_protected_symlinks)
return 0;
/* Allowed if owner and follower match. */
diff --git a/include/linux/fs.h b/include/linux/fs.h
index aba8db0..404cc89 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -423,7 +423,7 @@ extern unsigned long get_max_files(void);
extern int sysctl_nr_open;
extern struct inodes_stat_t inodes_stat;
extern int leases_enable, lease_break_time;
-extern int sysctl_protected_sticky_symlinks;
+extern int sysctl_protected_symlinks;
struct buffer_head;
typedef int (get_block_t)(struct inode *inode, sector_t iblock,
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index c469b88..0624e7c 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1497,10 +1497,10 @@ static struct ctl_table fs_table[] = {
},
#endif
#endif
-#ifdef CONFIG_PROTECTED_STICKY_SYMLINKS
+#ifdef CONFIG_PROTECTED_SYMLINKS
{
- .procname = "protected_sticky_symlinks",
- .data = &sysctl_protected_sticky_symlinks,
+ .procname = "protected_symlinks",
+ .data = &sysctl_protected_symlinks,
.maxlen = sizeof(int),
.mode = 0600,
.proc_handler = proc_dointvec_minmax,
--
1.7.9.1

View File

@ -1,329 +0,0 @@
From af16d0017a7de1f00af3966b5013bebfce8a81b4 Mon Sep 17 00:00:00 2001
From: Kees Cook <keescook@chromium.org>
Date: Sat, 25 Feb 2012 12:28:42 +1100
Subject: [PATCH 1/5] fs: symlink restrictions on sticky directories
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A longstanding 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)
This patch is based on the patch in Openwall and grsecurity, along with
suggestions from Al Viro. I have added a sysctl to enable the protected
behavior, documentation, and an audit notification.
[akpm@linux-foundation.org: move sysctl_protected_sticky_symlinks declaration into .h]
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Ingo Molnar <mingo@elte.hu>
Cc: Matthew Wilcox <matthew@wil.cx>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Rik van Riel <riel@redhat.com>
Cc: Federica Teodori <federica.teodori@googlemail.com>
Cc: Lucian Adrian Grijincu <lucian.grijincu@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Eric Paris <eparis@redhat.com>
Cc: Randy Dunlap <rdunlap@xenotime.net>
Cc: Dan Rosenberg <drosenberg@vsecurity.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
Documentation/sysctl/fs.txt | 21 ++++++++++
fs/Kconfig | 34 ++++++++++++++++
fs/namei.c | 91 ++++++++++++++++++++++++++++++++++++++++---
include/linux/fs.h | 1 +
kernel/sysctl.c | 11 +++++
5 files changed, 152 insertions(+), 6 deletions(-)
diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt
index 88fd7f5..4b47cd5 100644
--- a/Documentation/sysctl/fs.txt
+++ b/Documentation/sysctl/fs.txt
@@ -32,6 +32,7 @@ Currently, these files are in /proc/sys/fs:
- nr_open
- overflowuid
- overflowgid
+- protected_sticky_symlinks
- suid_dumpable
- super-max
- super-nr
@@ -157,6 +158,26 @@ The default is 65534.
==============================================================
+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.
+
+==============================================================
+
suid_dumpable:
This value can be used to query and set the core dump mode for setuid
diff --git a/fs/Kconfig b/fs/Kconfig
index 1497ddf..d0fdbdd 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -272,4 +272,38 @@ endif # NETWORK_FILESYSTEMS
source "fs/nls/Kconfig"
source "fs/dlm/Kconfig"
+config PROTECTED_STICKY_SYMLINKS
+ bool "Evaluate vulnerable symlink conditions"
+ default y
+ help
+ 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 malicious symlink belonging to another user).
+
+ Enabling this adds the logic to examine these dangerous symlink
+ conditions. Whether or not the dangerous symlink situations are
+ allowed is controlled by PROTECTED_STICKY_SYMLINKS_ENABLED.
+
+config PROTECTED_STICKY_SYMLINKS_ENABLED
+ depends on PROTECTED_STICKY_SYMLINKS
+ bool "Disallow symlink following in sticky world-writable dirs"
+ default y
+ help
+ Solve ToCToU symlink race vulnerablities by permitting symlinks
+ 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 and symlink owners match.
+
+ When PROC_SYSCTL is enabled, this setting can also be controlled
+ via /proc/sys/kernel/protected_sticky_symlinks.
+
+config PROTECTED_STICKY_SYMLINKS_ENABLED_SYSCTL
+ depends on PROTECTED_STICKY_SYMLINKS
+ int
+ default "1" if PROTECTED_STICKY_SYMLINKS_ENABLED
+ default "0"
+
endmenu
diff --git a/fs/namei.c b/fs/namei.c
index 5d1fab5..5b4c05b 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -623,10 +623,84 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki
path_put(link);
}
+#ifdef CONFIG_PROTECTED_STICKY_SYMLINKS
+int sysctl_protected_sticky_symlinks __read_mostly =
+ CONFIG_PROTECTED_STICKY_SYMLINKS_ENABLED_SYSCTL;
+
+/**
+ * may_follow_link - Check symlink following for unsafe situations
+ * @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 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.
+ *
+ * Returns 0 if following the symlink is allowed, -ve on error.
+ */
+static inline int
+may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
+{
+ int error = 0;
+ const struct inode *parent;
+ const struct inode *inode;
+ const struct cred *cred;
+
+ if (!sysctl_protected_sticky_symlinks)
+ return 0;
+
+ /* Allowed if owner and follower match. */
+ cred = current_cred();
+ inode = dentry->d_inode;
+ 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) {
+ error = -EACCES;
+ }
+ spin_unlock(&dentry->d_lock);
+
+#ifdef CONFIG_AUDIT
+ if (error) {
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(current->audit_context,
+ GFP_KERNEL, AUDIT_AVC);
+ audit_log_format(ab, "op=follow_link action=denied");
+ audit_log_format(ab, " pid=%d comm=", current->pid);
+ audit_log_untrustedstring(ab, current->comm);
+ audit_log_d_path(ab, " path=", &nameidata->path);
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, dentry->d_name.name);
+ audit_log_format(ab, " dev=");
+ audit_log_untrustedstring(ab, inode->i_sb->s_id);
+ audit_log_format(ab, " ino=%lu", inode->i_ino);
+ audit_log_end(ab);
+ }
+#endif
+ return error;
+}
+#else
+static inline int
+may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
+{
+ return 0;
+}
+#endif
+
static __always_inline int
-follow_link(struct path *link, struct nameidata *nd, void **p)
+follow_link(struct path *link, struct nameidata *nd, void **p, bool sensitive)
{
- int error;
+ int error = 0;
struct dentry *dentry = link->dentry;
BUG_ON(nd->flags & LOOKUP_RCU);
@@ -645,7 +719,10 @@ follow_link(struct path *link, struct nameidata *nd, void **p)
touch_atime(link->mnt, dentry);
nd_set_link(nd, NULL);
- error = security_inode_follow_link(link->dentry, nd);
+ if (sensitive)
+ error = may_follow_link(link->dentry, nd);
+ if (!error)
+ error = security_inode_follow_link(link->dentry, nd);
if (error) {
*p = ERR_PTR(error); /* no ->put_link(), please */
path_put(&nd->path);
@@ -1342,7 +1419,7 @@ static inline int nested_symlink(struct path *path, struct nameidata *nd)
struct path link = *path;
void *cookie;
- res = follow_link(&link, nd, &cookie);
+ res = follow_link(&link, nd, &cookie, false);
if (!res)
res = walk_component(nd, path, &nd->last,
nd->last_type, LOOKUP_FOLLOW);
@@ -1615,7 +1692,8 @@ static int path_lookupat(int dfd, const char *name,
void *cookie;
struct path link = path;
nd->flags |= LOOKUP_PARENT;
- err = follow_link(&link, nd, &cookie);
+
+ err = follow_link(&link, nd, &cookie, true);
if (!err)
err = lookup_last(nd, &path);
put_link(nd, &link, cookie);
@@ -2327,7 +2405,8 @@ static struct file *path_openat(int dfd, const char *pathname,
}
nd->flags |= LOOKUP_PARENT;
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
- error = follow_link(&link, nd, &cookie);
+
+ error = follow_link(&link, nd, &cookie, true);
if (unlikely(error))
filp = ERR_PTR(error);
else
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 9808b21..aba8db0 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -423,6 +423,7 @@ extern unsigned long get_max_files(void);
extern int sysctl_nr_open;
extern struct inodes_stat_t inodes_stat;
extern int leases_enable, lease_break_time;
+extern int sysctl_protected_sticky_symlinks;
struct buffer_head;
typedef int (get_block_t)(struct inode *inode, sector_t iblock,
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 62538ee..c469b88 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1497,6 +1497,17 @@ static struct ctl_table fs_table[] = {
},
#endif
#endif
+#ifdef CONFIG_PROTECTED_STICKY_SYMLINKS
+ {
+ .procname = "protected_sticky_symlinks",
+ .data = &sysctl_protected_sticky_symlinks,
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = &zero,
+ .extra2 = &one,
+ },
+#endif
{
.procname = "suid_dumpable",
.data = &suid_dumpable,
--
1.7.9.1

View File

@ -62,11 +62,9 @@ bugfix/x86/KVM-nVMX-Fix-warning-causing-idt-vectoring-info-beha.patch
features/all/hwmon-it87-Add-IT8728F-support.patch
features/all/fs-symlink-restrictions-on-sticky-directories.patch
features/all/fs-symlink-restrictions-on-sticky-directories-fix-2.patch
features/all/fs-hardlink-creation-restrictions.patch
features/all/fs-hardlink-creation-restrictions-fix.patch
features/all/fs-hardlink-creation-restriction-cleanup.patch
# Add link security restrictions from 3.6
features/all/fs-add-link-restrictions.patch
features/all/fs-add-link-restriction-audit-reporting.patch
# Update all Hyper-V drivers to 3.4-rc1 (no longer staging)
features/x86/hyperv/0001-NLS-improve-UTF8-UTF16-string-conversion-routine.patch