From 5579f85663d10269e7ac7464be6548c99cea4ada Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 23 Jan 2018 14:03:34 +0100
Subject: [PATCH] tmpfiles: refuse to chown()/chmod() files which are
 hardlinked, unless protected_hardlinks sysctl is on

Let's add some extra safety.

Fixes: #7736
---
 src/tmpfiles/tmpfiles.c | 43 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index d733768f277..5b56e7dcdd2 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -604,6 +604,39 @@ finish:
         return r;
 }
 
+static bool dangerous_hardlinks(void) {
+        _cleanup_free_ char *value = NULL;
+        static int cached = -1;
+        int r;
+
+        /* Check whether the fs.protected_hardlinks sysctl is on. If we can't determine it we assume its off, as that's
+         * what the upstream default is. */
+
+        if (cached >= 0)
+                return cached;
+
+        r = read_one_line_file("/proc/sys/fs/protected_hardlinks", &value);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl: %m");
+                return true;
+        }
+
+        r = parse_boolean(value);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to parse fs.protected_hardlinks sysctl: %m");
+                return true;
+        }
+
+        cached = r == 0;
+        return cached;
+}
+
+static bool hardlink_vulnerable(struct stat *st) {
+        assert(st);
+
+        return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && dangerous_hardlinks();
+}
+
 static int path_set_perms(Item *i, const char *path) {
         _cleanup_close_ int fd = -1;
         struct stat st;
@@ -623,6 +623,11 @@ static int path_set_perms(Item *i, const char *path) {
         if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
                 return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
 
+        if (hardlink_vulnerable(&st)) {
+                log_error("Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", path);
+                return -EPERM;
+        }
+
         if (S_ISLNK(st.st_mode))
                 log_debug("Skipping mode an owner fix for symlink %s.", path);
         else {

@@ -971,6 +1009,11 @@ static int path_set_acls(Item *item, const char *path) {
         if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
                 return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
 
+        if (hardlink_vulnerable(&st)) {
+                log_error("Refusing to set ACLs on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", path);
+                return -EPERM;
+        }
+
         if (S_ISLNK(st.st_mode)) {
                 log_debug("Skipping ACL fix for symlink %s.", path);
                 return 0;