Jonathan Wright
7422694227
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
199 lines
5.7 KiB
Diff
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
|
|
+}
|
|
|