- nr_open
- overflowuid
- overflowgid
+- protected_hardlinks
- protected_symlinks
- suid_dumpable
- super-max
==============================================================
+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
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.
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
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
*
* 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;
}
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;
}
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) {
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;