Reviewed-by: Michael Tremer Thank you. > On 5 Aug 2022, at 13:00, Peter Müller wrote: > > https://www.openwall.com/lists/oss-security/2022/08/02/1 > > Signed-off-by: Peter Müller > --- > lfs/rsync | 5 +- > src/patches/rsync-CVE-2022-29154.patch | 322 +++++++++++++++++++++++++ > 2 files changed, 326 insertions(+), 1 deletion(-) > create mode 100644 src/patches/rsync-CVE-2022-29154.patch > > diff --git a/lfs/rsync b/lfs/rsync > index f40c28ce2..c27258929 100644 > --- a/lfs/rsync > +++ b/lfs/rsync > @@ -34,7 +34,7 @@ DL_FROM = $(URL_IPFIRE) > DIR_APP = $(DIR_SRC)/$(THISAPP) > TARGET = $(DIR_INFO)/$(THISAPP) > PROG = rsync > -PAK_VER = 14 > +PAK_VER = 15 > > DEPS = > > @@ -85,6 +85,9 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) > # Replace shebang in rsync-ssl > cd $(DIR_APP) && sed -i -e "s@^#!.*@#!/bin/bash@" rsync-ssl > > + # Fix for CVE-2022-29154 > + cd $(DIR_APP) && patch -Np1 < $(DIR_SRC)/src/patches/rsync-CVE-2022-29154.patch > + > cd $(DIR_APP) && ./configure \ > --prefix=/usr \ > --without-included-popt \ > diff --git a/src/patches/rsync-CVE-2022-29154.patch b/src/patches/rsync-CVE-2022-29154.patch > new file mode 100644 > index 000000000..d3b4499a4 > --- /dev/null > +++ b/src/patches/rsync-CVE-2022-29154.patch > @@ -0,0 +1,322 @@ > +commit b7231c7d02cfb65d291af74ff66e7d8c507ee871 > +Author: Wayne Davison > +Date: Sun Jul 31 16:55:34 2022 -0700 > + > + Some extra file-list safety checks. > + > +diff --git a/exclude.c b/exclude.c > +index 39073a0c..b670c8ba 100644 > +--- a/exclude.c > ++++ b/exclude.c > +@@ -27,16 +27,22 @@ extern int am_server; > + extern int am_sender; > + extern int eol_nulls; > + extern int io_error; > ++extern int xfer_dirs; > ++extern int recurse; > + extern int local_server; > + extern int prune_empty_dirs; > + extern int ignore_perishable; > ++extern int old_style_args; > ++extern int relative_paths; > + extern int delete_mode; > + extern int delete_excluded; > + extern int cvs_exclude; > + extern int sanitize_paths; > + extern int protocol_version; > ++extern int list_only; > + extern int module_id; > + > ++extern char *filesfrom_host; > + extern char curr_dir[MAXPATHLEN]; > + extern unsigned int curr_dir_len; > + extern unsigned int module_dirlen; > +@@ -44,8 +50,10 @@ extern unsigned int module_dirlen; > + filter_rule_list filter_list = { .debug_type = "" }; > + filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; > + filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; > ++filter_rule_list implied_filter_list = { .debug_type = " [implied]" }; > + > + int saw_xattr_filter = 0; > ++int trust_sender_filter = 0; > + > + /* Need room enough for ":MODS " prefix plus some room to grow. */ > + #define MAX_RULE_PREFIX (16) > +@@ -292,6 +300,125 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_ > + } > + } > + > ++/* Each arg the client sends to the remote sender turns into an implied include > ++ * that the receiver uses to validate the file list from the sender. */ > ++void add_implied_include(const char *arg) > ++{ > ++ filter_rule *rule; > ++ int arg_len, saw_wild = 0, backslash_cnt = 0; > ++ int slash_cnt = 1; /* We know we're adding a leading slash. */ > ++ const char *cp; > ++ char *p; > ++ if (old_style_args || list_only || filesfrom_host != NULL) > ++ return; > ++ if (relative_paths) { > ++ cp = strstr(arg, "/./"); > ++ if (cp) > ++ arg = cp+3; > ++ } else { > ++ if ((cp = strrchr(arg, '/')) != NULL) > ++ arg = cp + 1; > ++ } > ++ arg_len = strlen(arg); > ++ if (arg_len) { > ++ if (strpbrk(arg, "*[?")) { > ++ /* We need to add room to escape backslashes if wildcard chars are present. */ > ++ cp = arg; > ++ while ((cp = strchr(cp, '\\')) != NULL) { > ++ arg_len++; > ++ cp++; > ++ } > ++ saw_wild = 1; > ++ } > ++ arg_len++; /* Leave room for the prefixed slash */ > ++ rule = new0(filter_rule); > ++ if (!implied_filter_list.head) > ++ implied_filter_list.head = implied_filter_list.tail = rule; > ++ else { > ++ rule->next = implied_filter_list.head; > ++ implied_filter_list.head = rule; > ++ } > ++ rule->rflags = FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0); > ++ p = rule->pattern = new_array(char, arg_len + 1); > ++ *p++ = '/'; > ++ cp = arg; > ++ while (*cp) { > ++ switch (*cp) { > ++ case '\\': > ++ backslash_cnt++; > ++ if (saw_wild) > ++ *p++ = '\\'; > ++ *p++ = *cp++; > ++ break; > ++ case '/': > ++ if (p[-1] == '/') /* This is safe because of the initial slash. */ > ++ break; > ++ if (relative_paths) { > ++ filter_rule const *ent; > ++ int found = 0; > ++ *p = '\0'; > ++ for (ent = implied_filter_list.head; ent; ent = ent->next) { > ++ if (ent != rule && strcmp(ent->pattern, rule->pattern) == 0) > ++ found = 1; > ++ } > ++ if (!found) { > ++ filter_rule *R_rule = new0(filter_rule); > ++ R_rule->rflags = FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0); > ++ R_rule->pattern = strdup(rule->pattern); > ++ R_rule->u.slash_cnt = slash_cnt; > ++ R_rule->next = implied_filter_list.head; > ++ implied_filter_list.head = R_rule; > ++ } > ++ } > ++ slash_cnt++; > ++ *p++ = *cp++; > ++ break; > ++ default: > ++ *p++ = *cp++; > ++ break; > ++ } > ++ } > ++ *p = '\0'; > ++ rule->u.slash_cnt = slash_cnt; > ++ arg = (const char *)rule->pattern; > ++ } > ++ > ++ if (recurse || xfer_dirs) { > ++ /* Now create a rule with an added "/" & "**" or "*" at the end */ > ++ rule = new0(filter_rule); > ++ if (recurse) > ++ rule->rflags = FILTRULE_INCLUDE | FILTRULE_WILD | FILTRULE_WILD2; > ++ else > ++ rule->rflags = FILTRULE_INCLUDE | FILTRULE_WILD; > ++ /* A +4 in the len leaves enough room for / * * \0 or / * \0 \0 */ > ++ if (!saw_wild && backslash_cnt) { > ++ /* We are appending a wildcard, so now the backslashes need to be escaped. */ > ++ p = rule->pattern = new_array(char, arg_len + backslash_cnt + 3 + 1); > ++ cp = arg; > ++ while (*cp) { > ++ if (*cp == '\\') > ++ *p++ = '\\'; > ++ *p++ = *cp++; > ++ } > ++ } else { > ++ p = rule->pattern = new_array(char, arg_len + 3 + 1); > ++ if (arg_len) { > ++ memcpy(p, arg, arg_len); > ++ p += arg_len; > ++ } > ++ } > ++ if (p[-1] != '/') > ++ *p++ = '/'; > ++ *p++ = '*'; > ++ if (recurse) > ++ *p++ = '*'; > ++ *p = '\0'; > ++ rule->u.slash_cnt = slash_cnt + 1; > ++ rule->next = implied_filter_list.head; > ++ implied_filter_list.head = rule; > ++ } > ++} > ++ > + /* This frees any non-inherited items, leaving just inherited items on the list. */ > + static void pop_filter_list(filter_rule_list *listp) > + { > +@@ -718,7 +845,7 @@ static void report_filter_result(enum logcode code, char const *name, > + : name_flags & NAME_IS_DIR ? "directory" > + : "file"; > + rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n", > +- w, actions[*w!='s'][!(ent->rflags & FILTRULE_INCLUDE)], > ++ w, actions[*w=='g'][!(ent->rflags & FILTRULE_INCLUDE)], > + t, name, ent->pattern, > + ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type); > + } > +@@ -890,6 +1017,7 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr, > + } > + switch (ch) { > + case ':': > ++ trust_sender_filter = 1; > + rule->rflags |= FILTRULE_PERDIR_MERGE > + | FILTRULE_FINISH_SETUP; > + /* FALL THROUGH */ > +diff --git a/flist.c b/flist.c > +index 1ba306bc..0e6bf782 100644 > +--- a/flist.c > ++++ b/flist.c > +@@ -73,6 +73,7 @@ extern int need_unsorted_flist; > + extern int sender_symlink_iconv; > + extern int output_needs_newline; > + extern int sender_keeps_checksum; > ++extern int trust_sender_filter; > + extern int unsort_ndx; > + extern uid_t our_uid; > + extern struct stats stats; > +@@ -83,8 +84,7 @@ extern char curr_dir[MAXPATHLEN]; > + > + extern struct chmod_mode_struct *chmod_modes; > + > +-extern filter_rule_list filter_list; > +-extern filter_rule_list daemon_filter_list; > ++extern filter_rule_list filter_list, implied_filter_list, daemon_filter_list; > + > + #ifdef ICONV_OPTION > + extern int filesfrom_convert; > +@@ -986,6 +986,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x > + exit_cleanup(RERR_UNSUPPORTED); > + } > + > ++ if (*thisname != '.' || thisname[1] != '\0') { > ++ int filt_flags = S_ISDIR(mode) ? NAME_IS_DIR : NAME_IS_FILE; > ++ if (!trust_sender_filter /* a per-dir filter rule means we must trust the sender's filtering */ > ++ && filter_list.head && check_filter(&filter_list, FINFO, thisname, filt_flags) < 0) { > ++ rprintf(FERROR, "ERROR: rejecting excluded file-list name: %s\n", thisname); > ++ exit_cleanup(RERR_PROTOCOL); > ++ } > ++ if (implied_filter_list.head && check_filter(&implied_filter_list, FINFO, thisname, filt_flags) <= 0) { > ++ rprintf(FERROR, "ERROR: rejecting unrequested file-list name: %s\n", thisname); > ++ exit_cleanup(RERR_PROTOCOL); > ++ } > ++ } > ++ > + if (inc_recurse && S_ISDIR(mode)) { > + if (one_file_system) { > + /* Room to save the dir's device for -x */ > +diff --git a/io.c b/io.c > +index cf94cee7..a6e3ed30 100644 > +--- a/io.c > ++++ b/io.c > +@@ -419,6 +419,7 @@ static void forward_filesfrom_data(void) > + while (s != eob) { > + if (*s++ == '\0') { > + ff_xb.len = s - sob - 1; > ++ add_implied_include(sob); > + if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) > + exit_cleanup(RERR_PROTOCOL); /* impossible? */ > + write_buf(iobuf.out_fd, s-1, 1); /* Send the '\0'. */ > +@@ -450,9 +451,12 @@ static void forward_filesfrom_data(void) > + char *f = ff_xb.buf + ff_xb.pos; > + char *t = ff_xb.buf; > + char *eob = f + len; > ++ char *cur = t; > + /* Eliminate any multi-'\0' runs. */ > + while (f != eob) { > + if (!(*t++ = *f++)) { > ++ add_implied_include(cur); > ++ cur = t; > + while (f != eob && *f == '\0') > + f++; > + } > +diff --git a/main.c b/main.c > +index 58920a2d..5a7fbdd7 100644 > +--- a/main.c > ++++ b/main.c > +@@ -89,6 +89,7 @@ extern int backup_dir_len; > + extern int basis_dir_cnt; > + extern int default_af_hint; > + extern int stdout_format_has_i; > ++extern int trust_sender_filter; > + extern struct stats stats; > + extern char *stdout_format; > + extern char *logfile_format; > +@@ -104,7 +105,7 @@ extern char curr_dir[MAXPATHLEN]; > + extern char backup_dir_buf[MAXPATHLEN]; > + extern char *basis_dir[MAX_BASIS_DIRS+1]; > + extern struct file_list *first_flist; > +-extern filter_rule_list daemon_filter_list; > ++extern filter_rule_list daemon_filter_list, implied_filter_list; > + > + uid_t our_uid; > + gid_t our_gid; > +@@ -635,6 +636,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in > + #ifdef ICONV_CONST > + setup_iconv(); > + #endif > ++ trust_sender_filter = 1; > + } else if (local_server) { > + /* If the user didn't request --[no-]whole-file, force > + * it on, but only if we're not batch processing. */ > +@@ -1500,6 +1502,8 @@ static int start_client(int argc, char *argv[]) > + char *dummy_host; > + int dummy_port = rsync_port; > + int i; > ++ if (filesfrom_fd < 0) > ++ add_implied_include(remote_argv[0]); > + /* For remote source, any extra source args must have either > + * the same hostname or an empty hostname. */ > + for (i = 1; i < remote_argc; i++) { > +@@ -1523,6 +1527,7 @@ static int start_client(int argc, char *argv[]) > + if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */ > + arg = "."; > + remote_argv[i] = arg; > ++ add_implied_include(arg); > + } > + } > + > +diff --git a/receiver.c b/receiver.c > +index b3a69da0..93cf8efd 100644 > +--- a/receiver.c > ++++ b/receiver.c > +@@ -593,10 +593,13 @@ int recv_files(int f_in, int f_out, char *local_name) > + if (DEBUG_GTE(RECV, 1)) > + rprintf(FINFO, "recv_files(%s)\n", fname); > + > +- if (daemon_filter_list.head && (*fname != '.' || fname[1] != '\0') > +- && check_filter(&daemon_filter_list, FLOG, fname, 0) < 0) { > +- rprintf(FERROR, "attempt to hack rsync failed.\n"); > +- exit_cleanup(RERR_PROTOCOL); > ++ if (daemon_filter_list.head && (*fname != '.' || fname[1] != '\0')) { > ++ int filt_flags = S_ISDIR(file->mode) ? NAME_IS_DIR : NAME_IS_FILE; > ++ if (check_filter(&daemon_filter_list, FLOG, fname, filt_flags) < 0) { > ++ rprintf(FERROR, "ERROR: rejecting file transfer request for daemon excluded file: %s\n", > ++ fname); > ++ exit_cleanup(RERR_PROTOCOL); > ++ } > + } > + > + #ifdef SUPPORT_XATTRS > -- > 2.35.3