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;
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
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;
test_link_alloc();
test_link_alloc2();
test_openat_safe();
+ test_openat_safe_dir();
test_cleanup();
alarm(0);
test_end();