|
@@ -650,6 +650,119 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki
|
|
|
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)
|
|
|
{
|
|
@@ -1818,6 +1931,9 @@ static int path_lookupat(int dfd, const char *name,
|
|
|
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)
|
|
@@ -2778,6 +2894,9 @@ static struct file *path_openat(int dfd, const char *pathname,
|
|
|
error = -ELOOP;
|
|
|
break;
|
|
|
}
|
|
|
+ error = may_follow_link(&link, nd);
|
|
|
+ if (unlikely(error))
|
|
|
+ break;
|
|
|
nd->flags |= LOOKUP_PARENT;
|
|
|
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
|
|
|
error = follow_link(&link, nd, &cookie);
|
|
@@ -3421,6 +3540,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 (unlikely(error))
|
|
|
+ goto out_dput;
|
|
|
error = security_path_link(old_path.dentry, &new_path, new_dentry);
|
|
|
if (error)
|
|
|
goto out_dput;
|