105 lines
2.6 KiB
Diff
105 lines
2.6 KiB
Diff
From 33385aefe4773e7a3982d41995681eb079c92d12 Mon Sep 17 00:00:00 2001
|
|
From: Andrew Tridgell <andrew@tridgell.net>
|
|
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 <sys/syscall.h>
|
|
#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
|
|
|
|
|