]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 3/5] lib: Add t_openat_safe_dir()
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Wed, 3 Jun 2026 15:10:36 +0000 (18:10 +0300)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
Gbp-Pq: Name 0003-lib-Add-t_openat_safe_dir.patch

src/lib/path-util.c
src/lib/path-util.h
src/lib/test-path-util.c

index 3b8c1544f5a8bf15732a9dc56014471651ff9b62..59ea492c3a94bdaa73969650f137f0485d26eec7 100644 (file)
@@ -528,6 +528,25 @@ int t_openat_safe(int base_fd, const char *path, int flags,
        return path_openat_safe_walk(base_fd, path, flags, error_r);
 }
 
+int t_openat_safe_dir(const char *base_dir, const char *path, int flags,
+                     const char **error_r)
+{
+       i_assert(base_dir != NULL);
+       i_assert(path != NULL);
+       i_assert(error_r != NULL);
+
+       int dir_fd = open(base_dir,
+                         O_DIRECTORY | O_RDONLY | O_CLOEXEC |
+                         (flags & O_NOFOLLOW));
+       if (dir_fd < 0) {
+               *error_r = t_strdup_printf("open(%s) failed: %m", base_dir);
+               return -1;
+       }
+       int fd = t_openat_safe(dir_fd, path, flags, error_r);
+       i_close_fd(&dir_fd);
+       return fd;
+}
+
 bool t_binary_abspath(const char **binpath, const char **error_r)
 {
        const char *path_env, *const *paths;
index ec64ba68d302e90ac3657c13163e7a93dcd42253..ceeb483935dff4d5fbe57e83386129e5c691c3f0 100644 (file)
@@ -86,6 +86,13 @@ int t_readlink(const char *path, const char **dest_r, const char **error_r);
 int t_openat_safe(int base_fd, const char *path, int flags,
                  const char **error_r);
 
+/* Convenience wrapper: opens base_dir as a directory fd, then delegates to
+   t_openat_safe(). If O_NOFOLLOW is present in flags it is also applied to
+   the open() of base_dir itself, refusing a symlink as its final component.
+   Returns the new fd on success, -1 on failure. */
+int t_openat_safe_dir(const char *base_dir, const char *path, int flags,
+                     const char **error_r);
+
 /* Update binpath to be absolute:
  * a) begins with '/' -> no change
  * b) contains '/' -> assume relative to working directory
index 66c551a34a428c757193f761c7f11b1b03302d7f..3e96702b6d33130b255bef74dcf58e857f08d5c1 100644 (file)
@@ -356,6 +356,57 @@ static void test_openat_safe(void)
                i_error("rmdir(%s) failed: %m", subdir);
 }
 
+static void test_openat_safe_dir(void)
+{
+       const char *error;
+       int fd;
+
+       /* Plain file: success. */
+       const char *plain = t_strconcat(tmpdir, "/plain2", NULL);
+       int wfd = creat(plain, 0600);
+       if (wfd < 0)
+               i_fatal("creat(%s) failed: %m", plain);
+       i_close_fd(&wfd);
+
+       fd = t_openat_safe_dir(tmpdir, "plain2", O_RDONLY, &error);
+       test_assert(fd >= 0);
+       i_close_fd(&fd);
+
+       /* Nonexistent base_dir. */
+       errno = 0;
+       fd = t_openat_safe_dir(t_strconcat(tmpdir, "/no-such-dir", NULL),
+                              "plain2", O_RDONLY, &error);
+       test_assert(fd == -1);
+       test_assert(errno == ENOENT);
+       test_assert(error != NULL);
+
+       /* Absolute symlink in relative part: refused. */
+       const char *abs_link2 = t_strconcat(tmpdir, "/abs-link2", NULL);
+       if (symlink("/etc/hostname", abs_link2) < 0)
+               i_fatal("symlink failed: %m");
+       errno = 0;
+       fd = t_openat_safe_dir(tmpdir, "abs-link2", O_RDONLY, &error);
+       test_assert(fd == -1);
+       test_assert(errno == ELOOP);
+
+       /* O_NOFOLLOW: symlink as base_dir final component refused. */
+       const char *dir_link = t_strconcat(tmpdir, "/dir-link", NULL);
+       if (symlink(tmpdir, dir_link) < 0)
+               i_fatal("symlink failed: %m");
+       errno = 0;
+       fd = t_openat_safe_dir(dir_link, "plain2",
+                              O_RDONLY | O_NOFOLLOW, &error);
+       test_assert(fd == -1);
+       /* Without O_NOFOLLOW the same symlinked base_dir succeeds. */
+       fd = t_openat_safe_dir(dir_link, "plain2", O_RDONLY, &error);
+       test_assert(fd >= 0);
+       i_close_fd(&fd);
+
+       i_unlink(plain);
+       i_unlink(abs_link2);
+       i_unlink(dir_link);
+}
+
 static void test_cleanup(void)
 {
        const char *error;
@@ -396,6 +447,7 @@ void test_path_util(void)
        test_link_alloc();
        test_link_alloc2();
        test_openat_safe();
+       test_openat_safe_dir();
        test_cleanup();
        alarm(0);
        test_end();