8d419f
From 5ec751ab9a06dadc62b30dc07e9dd7a41f8da403 Mon Sep 17 00:00:00 2001
8d419f
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
8d419f
Date: Fri, 4 Mar 2022 18:47:31 +0100
8d419f
Subject: [PATCH] shared/install: reuse the standard symlink verification
8d419f
 subroutine
8d419f
MIME-Version: 1.0
8d419f
Content-Type: text/plain; charset=UTF-8
8d419f
Content-Transfer-Encoding: 8bit
8d419f
8d419f
We save a few lines, but the important thing is that we don't have two
8d419f
different implementations with slightly different rules used for enablement
8d419f
and loading. Fixes #22000.
8d419f
8d419f
Tested with:
8d419f
- the report in #22000, it now says:
8d419f
$ SYSTEMD_LOG_LEVEL=debug systemctl --root=/ enable test.service
8d419f
Suspicious symlink /etc/systemd/system/test.service→/etc/systemd/system/myown.d/test.service, treating as alias.
8d419f
unit_file_resolve_symlink: self-alias: /etc/systemd/system/test.service → test.service, ignoring.
8d419f
running_in_chroot(): Permission denied
8d419f
Suspicious symlink /etc/systemd/system/test.service→/etc/systemd/system/myown.d/test.service, treating as alias.
8d419f
unit_file_resolve_symlink: self-alias: /etc/systemd/system/test.service → test.service, ignoring.
8d419f
Failed to enable unit, refusing to operate on linked unit file test.service
8d419f
8d419f
- a symlink to /dev/null:
8d419f
...
8d419f
unit_file_resolve_symlink: linked unit file: /etc/systemd/system/test3.service → /dev/null
8d419f
Failed to enable unit, unit /etc/systemd/system/test3.service is masked.
8d419f
8d419f
- the same from the host:
8d419f
...
8d419f
unit_file_resolve_symlink: linked unit file: /var/lib/machines/rawhide/etc/systemd/system/test3.service → /var/lib/machines/rawhide/dev/null
8d419f
Failed to enable unit, unit /var/lib/machines/rawhide/etc/systemd/system/test3.service is masked.
8d419f
8d419f
- through the manager:
8d419f
$ sudo systemctl enable test.service
8d419f
Failed to enable unit: Refusing to operate on alias name or linked unit file: test.service
8d419f
$ sudo systemctl enable test3.service
8d419f
Failed to enable unit: Unit file /etc/systemd/system/test3.service is masked.
8d419f
8d419f
As seen in the first example, the warning is repeated. This is because we call
8d419f
the lookup logic twice: first for sysv-compat, and then again for real. I think
8d419f
that since this is only for broken setups, and when sysv-compat is enabled, and
8d419f
in an infrequent manual operation, at debug level, this is OK.
8d419f
8d419f
(cherry picked from commit 047d37dc3d376d912275c14d217f7a0dda9a5f0e)
8d419f
8d419f
Related: #2082131
8d419f
---
8d419f
 src/basic/unit-file.c | 70 +++++++++++++++++++++++++++++++----------
8d419f
 src/basic/unit-file.h | 10 ++++++
8d419f
 src/shared/install.c  | 72 +++++++------------------------------------
8d419f
 3 files changed, 75 insertions(+), 77 deletions(-)
8d419f
8d419f
diff --git a/src/basic/unit-file.c b/src/basic/unit-file.c
8d419f
index 25abce932a..f7a10b22c6 100644
8d419f
--- a/src/basic/unit-file.c
8d419f
+++ b/src/basic/unit-file.c
8d419f
@@ -260,27 +260,50 @@ static int directory_name_is_valid(const char *name) {
8d419f
         return false;
8d419f
 }
8d419f
 
8d419f
-static int unit_file_resolve_symlink(
8d419f
+int unit_file_resolve_symlink(
8d419f
                 const char *root_dir,
8d419f
                 char **search_path,
8d419f
                 const char *dir,
8d419f
                 int dirfd,
8d419f
                 const char *filename,
8d419f
+                bool resolve_destination_target,
8d419f
                 char **ret_destination) {
8d419f
 
8d419f
-        _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL;
8d419f
+        _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL, *_dir = NULL, *_filename = NULL;
8d419f
         int r;
8d419f
 
8d419f
-        assert(dir);
8d419f
-        assert(dirfd >= 0);
8d419f
+        /* This can be called with either dir+dirfd valid and filename just a name,
8d419f
+         * or !dir && dirfd==AT_FDCWD, and filename being a full path.
8d419f
+         *
8d419f
+         * If resolve_destination_target is true, an absolute path will be returned.
8d419f
+         * If not, an absolute path is returned for linked unit files, and a relative
8d419f
+         * path otherwise. */
8d419f
+
8d419f
         assert(filename);
8d419f
         assert(ret_destination);
8d419f
+        assert(dir || path_is_absolute(filename));
8d419f
+        assert(dirfd >= 0 || dirfd == AT_FDCWD);
8d419f
 
8d419f
         r = readlinkat_malloc(dirfd, filename, &target);
8d419f
         if (r < 0)
8d419f
                 return log_warning_errno(r, "Failed to read symlink %s%s%s: %m",
8d419f
                                          dir, dir ? "/" : "", filename);
8d419f
 
8d419f
+        if (!dir) {
8d419f
+                r = path_extract_directory(filename, &_dir);
8d419f
+                if (r < 0)
8d419f
+                        return r;
8d419f
+                dir = _dir;
8d419f
+
8d419f
+                r = path_extract_filename(filename, &_filename);
8d419f
+                if (r < 0)
8d419f
+                        return r;
8d419f
+                if (r == O_DIRECTORY)
8d419f
+                        return log_warning_errno(SYNTHETIC_ERRNO(EISDIR),
8d419f
+                                                 "Unexpected path to a directory \"%s\", refusing.", filename);
8d419f
+                filename = _filename;
8d419f
+        }
8d419f
+
8d419f
         bool is_abs = path_is_absolute(target);
8d419f
         if (root_dir || !is_abs) {
8d419f
                 char *target_abs = path_join(is_abs ? root_dir : dir, target);
8d419f
@@ -296,24 +319,36 @@ static int unit_file_resolve_symlink(
8d419f
                 return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m",
8d419f
                                          dir, filename, target);
8d419f
 
8d419f
+        assert(path_is_absolute(simplified));
8d419f
+
8d419f
         /* Check if the symlink goes outside of our search path.
8d419f
-         * If yes, it's a linked unit file or mask, and we don't care about the target name.
8d419f
-         * Let's just store the link source directly.
8d419f
-         * If not, let's verify that it's a good symlink. */
8d419f
+         * If yes, it's a linked unit file or mask, and we don't care about the target name
8d419f
+         * when loading units, and we return the link *source* (resolve_destination_target == false);
8d419f
+         * When this is called for installation purposes, we want the final destination,
8d419f
+         * so we return the *target*.
8d419f
+         *
8d419f
+         * Otherwise, let's verify that it's a good alias.
8d419f
+         */
8d419f
         const char *tail = path_startswith_strv(simplified, search_path);
8d419f
         if (!tail) {
8d419f
                 log_debug("Linked unit file: %s/%s → %s", dir, filename, simplified);
8d419f
 
8d419f
-                dst = path_join(dir, filename);
8d419f
-                if (!dst)
8d419f
-                        return log_oom();
8d419f
+                if (resolve_destination_target)
8d419f
+                        dst = TAKE_PTR(simplified);
8d419f
+                else {
8d419f
+                        dst = path_join(dir, filename);
8d419f
+                        if (!dst)
8d419f
+                                return log_oom();
8d419f
+                }
8d419f
 
8d419f
         } else {
8d419f
-                r = path_extract_filename(simplified, &dst);
8d419f
+                _cleanup_free_ char *target_name = NULL;
8d419f
+
8d419f
+                r = path_extract_filename(simplified, &target_name);
8d419f
                 if (r < 0)
8d419f
                         return r;
8d419f
 
8d419f
-                bool self_alias = streq(dst, filename);
8d419f
+                bool self_alias = streq(target_name, filename);
8d419f
 
8d419f
                 if (is_path(tail))
8d419f
                         log_full(self_alias ? LOG_DEBUG : LOG_WARNING,
8d419f
@@ -324,13 +359,15 @@ static int unit_file_resolve_symlink(
8d419f
                 if (r < 0)
8d419f
                         return r;
8d419f
 
8d419f
-                if (self_alias)
8d419f
-                        /* A self-alias that has no effect */
8d419f
+                if (self_alias && !resolve_destination_target)
8d419f
+                        /* A self-alias that has no effect when loading, let's just ignore it. */
8d419f
                         return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
8d419f
                                                "Unit file self-alias: %s/%s → %s, ignoring.",
8d419f
-                                               dir, filename, dst);
8d419f
+                                               dir, filename, target_name);
8d419f
+
8d419f
+                log_debug("Unit file alias: %s/%s → %s", dir, filename, target_name);
8d419f
 
8d419f
-                log_debug("Unit file alias: %s/%s → %s", dir, filename, dst);
8d419f
+                dst = resolve_destination_target ? TAKE_PTR(simplified) : TAKE_PTR(target_name);
8d419f
         }
8d419f
 
8d419f
         *ret_destination = TAKE_PTR(dst);
8d419f
@@ -475,6 +512,7 @@ int unit_file_build_name_map(
8d419f
 
8d419f
                                 r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
8d419f
                                                               *dir, dirfd(d), de->d_name,
8d419f
+                                                              /* resolve_destination_target= */ false,
8d419f
                                                               &dst);
8d419f
                                 if (r == -ENOMEM)
8d419f
                                         return r;
8d419f
diff --git a/src/basic/unit-file.h b/src/basic/unit-file.h
8d419f
index cc731a9e06..e29e878cfd 100644
8d419f
--- a/src/basic/unit-file.h
8d419f
+++ b/src/basic/unit-file.h
8d419f
@@ -44,6 +44,16 @@ int unit_symlink_name_compatible(const char *symlink, const char *target, bool i
8d419f
 int unit_validate_alias_symlink_and_warn(const char *filename, const char *target);
8d419f
 
8d419f
 bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new);
8d419f
+
8d419f
+int unit_file_resolve_symlink(
8d419f
+                const char *root_dir,
8d419f
+                char **search_path,
8d419f
+                const char *dir,
8d419f
+                int dirfd,
8d419f
+                const char *filename,
8d419f
+                bool resolve_destination_target,
8d419f
+                char **ret_destination);
8d419f
+
8d419f
 int unit_file_build_name_map(
8d419f
                 const LookupPaths *lp,
8d419f
                 uint64_t *cache_timestamp_hash,
8d419f
diff --git a/src/shared/install.c b/src/shared/install.c
8d419f
index 79e5109ce1..e07ca31797 100644
8d419f
--- a/src/shared/install.c
8d419f
+++ b/src/shared/install.c
8d419f
@@ -1338,76 +1338,26 @@ static int unit_file_load_or_readlink(
8d419f
                 const char *path,
8d419f
                 const LookupPaths *lp,
8d419f
                 SearchFlags flags) {
8d419f
-
8d419f
-        _cleanup_free_ char *resolved = NULL;
8d419f
         int r;
8d419f
 
8d419f
         r = unit_file_load(c, info, path, lp->root_dir, flags);
8d419f
         if (r != -ELOOP || (flags & SEARCH_DROPIN))
8d419f
                 return r;
8d419f
 
8d419f
-        r = chase_symlinks(path, lp->root_dir, CHASE_WARN | CHASE_NONEXISTENT, &resolved, NULL);
8d419f
-        if (r >= 0 &&
8d419f
-            lp->root_dir &&
8d419f
-            path_equal_ptr(path_startswith(resolved, lp->root_dir), "dev/null"))
8d419f
-                /* When looking under root_dir, we can't expect /dev/ to be mounted,
8d419f
-                 * so let's see if the path is a (possibly dangling) symlink to /dev/null. */
8d419f
-                info->type = UNIT_FILE_TYPE_MASKED;
8d419f
-
8d419f
-        else if (r > 0 && null_or_empty_path(resolved) > 0)
8d419f
+        /* This is a symlink, let's read and verify it. */
8d419f
+        r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
8d419f
+                                      NULL, AT_FDCWD, path,
8d419f
+                                      true, &info->symlink_target);
8d419f
+        if (r < 0)
8d419f
+                return r;
8d419f
 
8d419f
+        r = null_or_empty_path_with_root(info->symlink_target, lp->root_dir);
8d419f
+        if (r < 0 && r != -ENOENT)
8d419f
+                return log_debug_errno(r, "Failed to stat %s: %m", info->symlink_target);
8d419f
+        if (r > 0)
8d419f
                 info->type = UNIT_FILE_TYPE_MASKED;
8d419f
-
8d419f
-        else {
8d419f
-                _cleanup_free_ char *target = NULL;
8d419f
-                const char *bn;
8d419f
-                UnitType a, b;
8d419f
-
8d419f
-                /* This is a symlink, let's read it. We read the link again, because last time
8d419f
-                 * we followed the link until resolution, and here we need to do one step. */
8d419f
-
8d419f
-                r = readlink_malloc(path, &target);
8d419f
-                if (r < 0)
8d419f
-                        return r;
8d419f
-
8d419f
-                bn = basename(target);
8d419f
-
8d419f
-                if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) {
8d419f
-
8d419f
-                        if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN))
8d419f
-                                return -EINVAL;
8d419f
-
8d419f
-                } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
8d419f
-
8d419f
-                        if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
8d419f
-                                return -EINVAL;
8d419f
-
8d419f
-                } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) {
8d419f
-
8d419f
-                        if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE))
8d419f
-                                return -EINVAL;
8d419f
-                } else
8d419f
-                        return -EINVAL;
8d419f
-
8d419f
-                /* Enforce that the symlink destination does not
8d419f
-                 * change the unit file type. */
8d419f
-
8d419f
-                a = unit_name_to_type(info->name);
8d419f
-                b = unit_name_to_type(bn);
8d419f
-                if (a < 0 || b < 0 || a != b)
8d419f
-                        return -EINVAL;
8d419f
-
8d419f
-                if (path_is_absolute(target))
8d419f
-                        /* This is an absolute path, prefix the root so that we always deal with fully qualified paths */
8d419f
-                        info->symlink_target = path_join(lp->root_dir, target);
8d419f
-                else
8d419f
-                        /* This is a relative path, take it relative to the dir the symlink is located in. */
8d419f
-                        info->symlink_target = file_in_same_dir(path, target);
8d419f
-                if (!info->symlink_target)
8d419f
-                        return -ENOMEM;
8d419f
-
8d419f
+        else
8d419f
                 info->type = UNIT_FILE_TYPE_SYMLINK;
8d419f
-        }
8d419f
 
8d419f
         return 0;
8d419f
 }