]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
chase-symlinks: Add CHASE_PARENT
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 23 Dec 2022 11:08:19 +0000 (12:08 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 6 Mar 2023 12:42:43 +0000 (13:42 +0100)
Let's simplify chasing the parent directory of some path by adding
CHASE_PARENT.

src/basic/chase-symlinks.c
src/basic/chase-symlinks.h
src/test/test-fs-util.c

index bab5a5b2864f0577320f611ebe59719a2b48b674..cc0e263852b314c53a74de00f130358ad48af287 100644 (file)
@@ -101,6 +101,14 @@ int chase_symlinks_at(
         if (isempty(path))
                 path = ".";
 
+        if (flags & CHASE_PARENT) {
+                r = path_extract_directory(path, &buffer);
+                if (r == -EDESTADDRREQ)
+                        path = "."; /* If we don't have a parent directory, fall back to the dir_fd directory. */
+                else if (r < 0)
+                        return r;
+        }
+
         /* This function resolves symlinks of the path relative to the given directory file descriptor. If
          * CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
          * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
@@ -169,7 +177,7 @@ int chase_symlinks_at(
 
                 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
                  * set and doesn't care about any of the other special features we provide either. */
-                r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+                r = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
                 if (r < 0)
                         return -errno;
 
@@ -177,9 +185,11 @@ int chase_symlinks_at(
                 return 0;
         }
 
-        buffer = strdup(path);
-        if (!buffer)
-                return -ENOMEM;
+        if (!buffer) {
+                buffer = strdup(path);
+                if (!buffer)
+                        return -ENOMEM;
+        }
 
         /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
          * a relative path would be interpreted relative to the current working directory. */
@@ -380,6 +390,12 @@ int chase_symlinks_at(
                 close_and_replace(fd, child);
         }
 
+        if (flags & CHASE_PARENT) {
+                r = fd_verify_directory(fd);
+                if (r < 0)
+                        return r;
+        }
+
         if (ret_path) {
                 if (!done) {
                         done = strdup(append_trail_slash ? "./" : ".");
@@ -532,7 +548,7 @@ int chase_symlinks_and_open(
                 return -EINVAL;
 
         if (empty_or_root(root) && !ret_path &&
-            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0) {
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0) {
                 /* Shortcut this call if none of the special features of this call are requested */
                 r = open(path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
                 if (r < 0)
@@ -574,7 +590,7 @@ int chase_symlinks_and_opendir(
                 return -EINVAL;
 
         if (empty_or_root(root) && !ret_path &&
-            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0) {
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0) {
                 /* Shortcut this call if none of the special features of this call are requested */
                 d = opendir(path);
                 if (!d)
@@ -619,7 +635,7 @@ int chase_symlinks_and_stat(
                 return -EINVAL;
 
         if (empty_or_root(root) && !ret_path &&
-            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0 && !ret_fd) {
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0 && !ret_fd) {
                 /* Shortcut this call if none of the special features of this call are requested */
 
                 if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
@@ -662,7 +678,7 @@ int chase_symlinks_and_access(
                 return -EINVAL;
 
         if (empty_or_root(root) && !ret_path &&
-            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0 && !ret_fd) {
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0 && !ret_fd) {
                 /* Shortcut this call if none of the special features of this call are requested */
 
                 if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
@@ -729,20 +745,17 @@ int chase_symlinks_and_unlink(
                 int unlink_flags,
                 char **ret_path) {
 
-        _cleanup_free_ char *p = NULL, *rp = NULL, *dir = NULL, *fname = NULL;
+        _cleanup_free_ char *p = NULL, *rp = NULL, *fname = NULL;
         _cleanup_close_ int fd = -EBADF;
         int r;
 
         assert(path);
 
-        r = path_extract_directory(path, &dir);
-        if (r < 0)
-                return r;
         r = path_extract_filename(path, &fname);
         if (r < 0)
                 return r;
 
-        fd = chase_symlinks_and_open(dir, root, chase_flags, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL);
+        fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL);
         if (fd < 0)
                 return fd;
 
index be5e2bb69650135c9ed55178bd40ff3fa171d40b..6aea8d6781300949db996b2bde61c179b0c545e2 100644 (file)
@@ -7,19 +7,20 @@
 #include "stat-util.h"
 
 typedef enum ChaseSymlinksFlags {
-        CHASE_PREFIX_ROOT        = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */
-        CHASE_NONEXISTENT        = 1 << 1, /* It's OK if the path doesn't actually exist. */
-        CHASE_NO_AUTOFS          = 1 << 2, /* Return -EREMOTE if autofs mount point found */
-        CHASE_SAFE               = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
-        CHASE_TRAIL_SLASH        = 1 << 4, /* Any trailing slash will be preserved */
-        CHASE_STEP               = 1 << 5, /* Just execute a single step of the normalization */
-        CHASE_NOFOLLOW           = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's
-                                            * right-most component refers to symlink, return O_PATH fd of the symlink. */
-        CHASE_WARN               = 1 << 7, /* Emit an appropriate warning when an error is encountered.
-                                            * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
-        CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
-                                            * relative to the given directory fd instead of root. */
-        CHASE_PROHIBIT_SYMLINKS  = 1 << 9, /* Refuse all symlinks */
+        CHASE_PREFIX_ROOT        = 1 << 0,  /* The specified path will be prefixed by the specified root before beginning the iteration */
+        CHASE_NONEXISTENT        = 1 << 1,  /* It's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS          = 1 << 2,  /* Return -EREMOTE if autofs mount point found */
+        CHASE_SAFE               = 1 << 3,  /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
+        CHASE_TRAIL_SLASH        = 1 << 4,  /* Any trailing slash will be preserved */
+        CHASE_STEP               = 1 << 5,  /* Just execute a single step of the normalization */
+        CHASE_NOFOLLOW           = 1 << 6,  /* Do not follow the path's right-most component. With ret_fd, when the path's
+                                             * right-most component refers to symlink, return O_PATH fd of the symlink. */
+        CHASE_WARN               = 1 << 7,  /* Emit an appropriate warning when an error is encountered.
+                                             * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
+        CHASE_AT_RESOLVE_IN_ROOT = 1 << 8,  /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
+                                             * relative to the given directory fd instead of root. */
+        CHASE_PROHIBIT_SYMLINKS  = 1 << 9,  /* Refuse all symlinks */
+        CHASE_PARENT             = 1 << 10, /* Chase the parent directory of the given path. */
 } ChaseSymlinksFlags;
 
 bool unsafe_transition(const struct stat *a, const struct stat *b);
index 3fc5f88d26e1e9299052e3a7c5faca9dd9c096fa..97b6bdabc8090b408826d1d05140045e93973c15 100644 (file)
@@ -426,6 +426,18 @@ TEST(chase_symlinks) {
         assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
         assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
 
+        /* Test CHASE_PARENT */
+
+        assert_se(chase_symlinks("/chase/parent", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, &result, NULL) >= 0);
+        p = strjoina(temp, "/chase");
+        assert_se(streq(p, result));
+        result = mfree(result);
+        assert_se(chase_symlinks("/chase", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(streq(temp, result));
+        result = mfree(result);
+        assert_se(chase_symlinks("/", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, NULL, NULL) == -EADDRNOTAVAIL);
+        assert_se(chase_symlinks(".", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, NULL, NULL) == -EADDRNOTAVAIL);
+
  cleanup:
         assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
 }
@@ -463,7 +475,17 @@ TEST(chase_symlinks_at) {
 
         /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
          * host's root. */
-        assert_se(chase_symlinks_at(tfd, "/qed", 0, &result, NULL) == -ENOENT);
+        assert_se(chase_symlinks_at(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
+
+        /* Test CHASE_PARENT */
+
+        assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0);
+        assert_se(streq(result, "chase"));
+        result = mfree(result);
+
+        assert_se(chase_symlinks_at(tfd, "chase", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
 }
 
 TEST(unlink_noerrno) {