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(+) Index: rsync-3.1.3/syscall.c =================================================================== --- rsync-3.1.3.orig/syscall.c +++ rsync-3.1.3/syscall.c @@ -33,6 +33,8 @@ #include #endif +#include "ifuncs.h" + extern int dry_run; extern int am_root; extern int am_sender; @@ -578,3 +580,75 @@ int do_open_nofollow(const char *pathnam 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 +} Index: rsync-3.1.3/ifuncs.h =================================================================== --- rsync-3.1.3.orig/ifuncs.h +++ rsync-3.1.3/ifuncs.h @@ -104,3 +104,11 @@ free_stat_x(stat_x *sx_p) } #endif } + +static inline char *my_strdup(const char *str, const char *file, int line) +{ + int len = strlen(str)+1; + char *buf = my_alloc(NULL, len, 1, file, line); + memcpy(buf, str, len); + return buf; +} \ No newline at end of file Index: rsync-3.1.3/util2.c =================================================================== --- rsync-3.1.3.orig/util2.c +++ rsync-3.1.3/util2.c @@ -25,6 +25,9 @@ #include "itypes.h" #include "inums.h" +extern size_t max_alloc; + +char *do_calloc = "42"; /** * Sleep for a specified number of milliseconds. * @@ -77,6 +80,26 @@ void *_realloc_array(void *ptr, unsigned return realloc(ptr, size * num); } +void *my_alloc(void *ptr, size_t num, size_t size, const char *file, int line) +{ + if (max_alloc && num >= max_alloc/size) { + if (!file) + return NULL; + rprintf(FERROR, "[%s] exceeded --max-alloc=%s setting (file=%s, line=%d)\n", + who_am_i(), do_big_num(max_alloc, 0, NULL), src_file(file), line); + exit_cleanup(RERR_MALLOC); + } + if (!ptr) + ptr = malloc(num * size); + else if (ptr == do_calloc) + ptr = calloc(num, size); + else + ptr = realloc(ptr, num * size); + if (!ptr && file) + _out_of_memory("my_alloc caller", file, line); + return ptr; +} + const char *sum_as_hex(int csum_type, const char *sum, int flist_csum) { static char buf[MAX_DIGEST_LEN*2+1]; @@ -99,6 +122,27 @@ const char *sum_as_hex(int csum_type, co return buf; } +NORETURN void _out_of_memory(const char *msg, const char *file, int line) +{ + rprintf(FERROR, "[%s] out of memory: %s (file=%s, line=%d)\n", who_am_i(), msg, src_file(file), line); + exit_cleanup(RERR_MALLOC); +} + +const char *src_file(const char *file) +{ + static const char *util2 = __FILE__; + static int prefix = -1; + + if (prefix < 0) { + const char *cp = strrchr(util2, '/'); + prefix = cp ? cp - util2 + 1 : 0; + } + + if (prefix && strncmp(file, util2, prefix) == 0) + return file + prefix; + return file; +} + NORETURN void out_of_memory(const char *str) { rprintf(FERROR, "ERROR: out of memory in %s [%s]\n", str, who_am_i()); Index: rsync-3.1.3/options.c =================================================================== --- rsync-3.1.3.orig/options.c +++ rsync-3.1.3/options.c @@ -185,6 +185,9 @@ int link_dest = 0; int basis_dir_cnt = 0; char *dest_option = NULL; +#define DEFAULT_MAX_ALLOC (1024L * 1024 * 1024) +size_t max_alloc = DEFAULT_MAX_ALLOC; + static int remote_option_alloc = 0; int remote_option_cnt = 0; const char **remote_options = NULL; Index: rsync-3.1.3/Makefile.in =================================================================== --- rsync-3.1.3.orig/Makefile.in +++ rsync-3.1.3/Makefile.in @@ -46,7 +46,7 @@ popt_OBJS=popt/findme.o popt/popt.o po popt/popthelp.o popt/poptparse.o OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@ -TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@ +TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@ # Programs we must have to run the test cases CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \ @@ -128,7 +128,7 @@ getgroups$(EXEEXT): getgroups.o getfsdev$(EXEEXT): getfsdev.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS) -TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o +TRIMSLASH_OBJ = trimslash.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o trimslash$(EXEEXT): $(TRIMSLASH_OBJ) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS) Index: rsync-3.1.3/t_stub.c =================================================================== --- rsync-3.1.3.orig/t_stub.c +++ rsync-3.1.3/t_stub.c @@ -31,6 +31,7 @@ int module_dirlen = 0; int preserve_acls = 0; int preserve_times = 0; int preserve_xattrs = 0; +size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */ char *partial_dir; char *module_dir; filter_rule_list daemon_filter_list; Index: rsync-3.1.3/tls.c =================================================================== --- rsync-3.1.3.orig/tls.c +++ rsync-3.1.3/tls.c @@ -51,8 +51,8 @@ int link_owner = 0; int nsec_times = 0; int preserve_perms = 0; int preserve_executability = 0; -int preallocate_files = 0; -int inplace = 0; +// int preallocate_files = 0; +// int inplace = 0; int noatime = 0; #ifdef SUPPORT_XATTRS Index: rsync-3.1.3/trimslash.c =================================================================== --- rsync-3.1.3.orig/trimslash.c +++ rsync-3.1.3/trimslash.c @@ -28,8 +28,8 @@ int read_only = 1; int list_only = 0; int preserve_perms = 0; int preserve_executability = 0; -int preallocate_files = 0; -int inplace = 0; +// int preallocate_files = 0; +// int inplace = 0; int noatime = 0; int