From 33385aefe4773e7a3982d41995681eb079c92d12 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sat, 23 Nov 2024 12:26:10 +1100 Subject: [PATCH 2/4] added secure_relative_open() this is an open that enforces no symlink following for all path components in a relative path --- syscall.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/syscall.c b/syscall.c index d92074aa..a4b7f542 100644 --- a/syscall.c +++ b/syscall.c @@ -33,6 +33,8 @@ #include #endif +#include "ifuncs.h" + extern int dry_run; extern int am_root; extern int am_sender; @@ -712,3 +714,75 @@ int do_open_nofollow(const char *pathname, int flags) return fd; } + +/* + open a file relative to a base directory. The basedir can be NULL, + in which case the current working directory is used. The relpath + must be a relative path, and the relpath must not contain any + elements in the path which follow symlinks (ie. like O_NOFOLLOW, but + applies to all path components, not just the last component) +*/ +int secure_relative_open(const char *basedir, const char *relpath, int flags, mode_t mode) +{ + if (!relpath || relpath[0] == '/') { + // must be a relative path + errno = EINVAL; + return -1; + } + +#if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY) + // really old system, all we can do is live with the risks + if (!basedir) { + return open(relpath, flags, mode); + } + char fullpath[MAXPATHLEN]; + pathjoin(fullpath, sizeof fullpath, basedir, relpath); + return open(fullpath, flags, mode); +#else + int dirfd = AT_FDCWD; + if (basedir != NULL) { + dirfd = openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY); + if (dirfd == -1) { + return -1; + } + } + int retfd = -1; + + char *path_copy = my_strdup(relpath, __FILE__, __LINE__); + if (!path_copy) { + return -1; + } + + for (const char *part = strtok(path_copy, "/"); + part != NULL; + part = strtok(NULL, "/")) + { + int next_fd = openat(dirfd, part, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (next_fd == -1 && errno == ENOTDIR) { + if (strtok(NULL, "/") != NULL) { + // this is not the last component of the path + errno = ELOOP; + goto cleanup; + } + // this could be the last component of the path, try as a file + retfd = openat(dirfd, part, flags | O_NOFOLLOW, mode); + goto cleanup; + } + if (next_fd == -1) { + goto cleanup; + } + if (dirfd != AT_FDCWD) close(dirfd); + dirfd = next_fd; + } + + // the path must be a directory + errno = EINVAL; + +cleanup: + free(path_copy); + if (dirfd != AT_FDCWD) { + close(dirfd); + } + return retfd; +#endif // O_NOFOLLOW, O_DIRECTORY +} -- 2.34.1