rsync/rsync-3.3.0-cve-2024-12086.patch
Jonathan Wright 7422694227 Fix for CVE-2024-12084
Fix for CVE-2024-12085
Fix for CVE-2024-12086
Fix for CVE-2024-12087
Fix for CVE-2024-12088
Fix for CVE-2024-12747
2025-01-14 17:30:01 -06:00

199 lines
5.7 KiB
Diff

diff --git a/receiver.c b/receiver.c
index 6b4b369..8031b8f 100644
--- a/receiver.c
+++ b/receiver.c
@@ -66,6 +66,7 @@ extern char sender_file_sum[MAX_DIGEST_LEN];
extern struct file_list *cur_flist, *first_flist, *dir_flist;
extern filter_rule_list daemon_filter_list;
extern OFF_T preallocated_len;
+extern int fuzzy_basis;
extern struct name_num_item *xfer_sum_nni;
extern int xfer_sum_len;
@@ -551,6 +552,8 @@ int recv_files(int f_in, int f_out, char *local_name)
progress_init();
while (1) {
+ const char *basedir = NULL;
+
cleanup_disable();
/* This call also sets cur_flist. */
@@ -716,28 +719,34 @@ int recv_files(int f_in, int f_out, char *local_name)
fnamecmp = get_backup_name(fname);
break;
case FNAMECMP_FUZZY:
+ if (fuzzy_basis == 0) {
+ rprintf(FERROR_XFER, "rsync: refusing malicious fuzzy operation for %s\n", xname);
+ exit_cleanup(RERR_PROTOCOL);
+ }
if (file->dirname) {
- pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, file->dirname, xname);
- fnamecmp = fnamecmpbuf;
- } else
- fnamecmp = xname;
+ basedir = file->dirname;
+ }
+ fnamecmp = xname;
break;
default:
if (fnamecmp_type > FNAMECMP_FUZZY && fnamecmp_type-FNAMECMP_FUZZY <= basis_dir_cnt) {
fnamecmp_type -= FNAMECMP_FUZZY + 1;
if (file->dirname) {
- stringjoin(fnamecmpbuf, sizeof fnamecmpbuf,
- basis_dir[fnamecmp_type], "/", file->dirname, "/", xname, NULL);
- } else
- pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], xname);
+ pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], file->dirname);
+ basedir = fnamecmpbuf;
+ } else {
+ basedir = basis_dir[fnamecmp_type];
+ }
+ fnamecmp = xname;
} else if (fnamecmp_type >= basis_dir_cnt) {
rprintf(FERROR,
"invalid basis_dir index: %d.\n",
fnamecmp_type);
exit_cleanup(RERR_PROTOCOL);
- } else
- pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], fname);
- fnamecmp = fnamecmpbuf;
+ } else {
+ basedir = basis_dir[fnamecmp_type];
+ fnamecmp = fname;
+ }
break;
}
if (!fnamecmp || (daemon_filter_list.head
@@ -760,7 +769,7 @@ int recv_files(int f_in, int f_out, char *local_name)
}
/* open the file */
- fd1 = do_open(fnamecmp, O_RDONLY, 0);
+ fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
if (fd1 == -1 && protocol_version < 29) {
if (fnamecmp != fname) {
@@ -771,14 +780,20 @@ int recv_files(int f_in, int f_out, char *local_name)
if (fd1 == -1 && basis_dir[0]) {
/* pre-29 allowed only one alternate basis */
- pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
- basis_dir[0], fname);
- fnamecmp = fnamecmpbuf;
+ basedir = basis_dir[0];
+ fnamecmp = fname;
fnamecmp_type = FNAMECMP_BASIS_DIR_LOW;
- fd1 = do_open(fnamecmp, O_RDONLY, 0);
+ fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
}
}
+ if (basedir) {
+ // for the following code we need the full
+ // path name as a single string
+ pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basedir, fnamecmp);
+ fnamecmp = fnamecmpbuf;
+ }
+
one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR;
updating_basis_or_equiv = one_inplace
|| (inplace && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP));
diff --git a/syscall.c b/syscall.c
index d92074a..47c5ea5 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,82 @@ 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)
+
+ The relpath must also not contain any ../ elements in the path
+*/
+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 (strncmp(relpath, "../", 3) == 0 || strstr(relpath, "/../")) {
+ // no ../ elements allowed in the relpath
+ 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
+}