]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
added secure_relative_open()
authorAndrew Tridgell <andrew@tridgell.net>
Sat, 23 Nov 2024 01:26:10 +0000 (12:26 +1100)
committerAndrew Tridgell <andrew@tridgell.net>
Tue, 14 Jan 2025 18:30:32 +0000 (05:30 +1100)
this is an open that enforces no symlink following for all path
components in a relative path

syscall.c

index b4b0f1f16d7175b915502349607d52cf3e6fb28e..cffc814b7b1c1eae12e149bdfb76ba1ae29085c2 100644 (file)
--- a/syscall.c
+++ b/syscall.c
@@ -33,6 +33,8 @@
 #include <sys/syscall.h>
 #endif
 
+#include "ifuncs.h"
+
 extern int dry_run;
 extern int am_root;
 extern int am_sender;
@@ -707,3 +709,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
+}