From mboxrd@z Thu Jan 1 00:00:00 1970 From: Michael Tremer To: development@lists.ipfire.org Subject: Re: [PATCH] rsync: Patch CVE-2022-29154 Date: Fri, 05 Aug 2022 17:05:21 +0100 Message-ID: <97A8705A-3A02-418C-9959-41AD82E3D3CA@ipfire.org> In-Reply-To: <95da7fde-2fe4-0c93-b881-232e143a5d26@ipfire.org> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============5659693715593393803==" List-Id: --===============5659693715593393803== Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Reviewed-by: Michael Tremer Thank you. > On 5 Aug 2022, at 13:00, Peter M=C3=BCller wro= te: >=20 > https://www.openwall.com/lists/oss-security/2022/08/02/1 >=20 > Signed-off-by: Peter M=C3=BCller > --- > 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 >=20 > diff --git a/lfs/rsync b/lfs/rsync > index f40c28ce2..c27258929 100644 > --- a/lfs/rsync > +++ b/lfs/rsync > @@ -34,7 +34,7 @@ DL_FROM =3D $(URL_IPFIRE) > DIR_APP =3D $(DIR_SRC)/$(THISAPP) > TARGET =3D $(DIR_INFO)/$(THISAPP) > PROG =3D rsync > -PAK_VER =3D 14 > +PAK_VER =3D 15 >=20 > DEPS =3D >=20 > @@ -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 >=20 > + # Fix for CVE-2022-29154 > + cd $(DIR_APP) && patch -Np1 < $(DIR_SRC)/src/patches/rsync-CVE-2022-29154= .patch > + > cd $(DIR_APP) && ./configure \ > --prefix=3D/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; > +=20 > ++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 =3D { .debug_type =3D "" }; > + filter_rule_list cvs_filter_list =3D { .debug_type =3D " [global CVS]" }; > + filter_rule_list daemon_filter_list =3D { .debug_type =3D " [daemon]" }; > ++filter_rule_list implied_filter_list =3D { .debug_type =3D " [implied]" }; > +=20 > + int saw_xattr_filter =3D 0; > ++int trust_sender_filter =3D 0; > +=20 > + /* 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_ > + } > + } > +=20 > ++/* Each arg the client sends to the remote sender turns into an implied i= nclude > ++ * 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 =3D 0, backslash_cnt =3D 0; > ++ int slash_cnt =3D 1; /* We know we're adding a leading slash. */ > ++ const char *cp; > ++ char *p; > ++ if (old_style_args || list_only || filesfrom_host !=3D NULL) > ++ return; > ++ if (relative_paths) { > ++ cp =3D strstr(arg, "/./"); > ++ if (cp) > ++ arg =3D cp+3; > ++ } else { > ++ if ((cp =3D strrchr(arg, '/')) !=3D NULL) > ++ arg =3D cp + 1; > ++ } > ++ arg_len =3D strlen(arg); > ++ if (arg_len) { > ++ if (strpbrk(arg, "*[?")) { > ++ /* We need to add room to escape backslashes if wildcard chars are pre= sent. */ > ++ cp =3D arg; > ++ while ((cp =3D strchr(cp, '\\')) !=3D NULL) { > ++ arg_len++; > ++ cp++; > ++ } > ++ saw_wild =3D 1; > ++ } > ++ arg_len++; /* Leave room for the prefixed slash */ > ++ rule =3D new0(filter_rule); > ++ if (!implied_filter_list.head) > ++ implied_filter_list.head =3D implied_filter_list.tail =3D rule; > ++ else { > ++ rule->next =3D implied_filter_list.head; > ++ implied_filter_list.head =3D rule; > ++ } > ++ rule->rflags =3D FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0); > ++ p =3D rule->pattern =3D new_array(char, arg_len + 1); > ++ *p++ =3D '/'; > ++ cp =3D arg; > ++ while (*cp) { > ++ switch (*cp) { > ++ case '\\': > ++ backslash_cnt++; > ++ if (saw_wild) > ++ *p++ =3D '\\'; > ++ *p++ =3D *cp++; > ++ break; > ++ case '/': > ++ if (p[-1] =3D=3D '/') /* This is safe because of the initial slash. */ > ++ break; > ++ if (relative_paths) { > ++ filter_rule const *ent; > ++ int found =3D 0; > ++ *p =3D '\0'; > ++ for (ent =3D implied_filter_list.head; ent; ent =3D ent->next) { > ++ if (ent !=3D rule && strcmp(ent->pattern, rule->pattern) =3D=3D 0) > ++ found =3D 1; > ++ } > ++ if (!found) { > ++ filter_rule *R_rule =3D new0(filter_rule); > ++ R_rule->rflags =3D FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0= ); > ++ R_rule->pattern =3D strdup(rule->pattern); > ++ R_rule->u.slash_cnt =3D slash_cnt; > ++ R_rule->next =3D implied_filter_list.head; > ++ implied_filter_list.head =3D R_rule; > ++ } > ++ } > ++ slash_cnt++; > ++ *p++ =3D *cp++; > ++ break; > ++ default: > ++ *p++ =3D *cp++; > ++ break; > ++ } > ++ } > ++ *p =3D '\0'; > ++ rule->u.slash_cnt =3D slash_cnt; > ++ arg =3D (const char *)rule->pattern; > ++ } > ++ > ++ if (recurse || xfer_dirs) { > ++ /* Now create a rule with an added "/" & "**" or "*" at the end */ > ++ rule =3D new0(filter_rule); > ++ if (recurse) > ++ rule->rflags =3D FILTRULE_INCLUDE | FILTRULE_WILD | FILTRULE_WILD2; > ++ else > ++ rule->rflags =3D 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 esca= ped. */ > ++ p =3D rule->pattern =3D new_array(char, arg_len + backslash_cnt + 3 + = 1); > ++ cp =3D arg; > ++ while (*cp) { > ++ if (*cp =3D=3D '\\') > ++ *p++ =3D '\\'; > ++ *p++ =3D *cp++; > ++ } > ++ } else { > ++ p =3D rule->pattern =3D new_array(char, arg_len + 3 + 1); > ++ if (arg_len) { > ++ memcpy(p, arg, arg_len); > ++ p +=3D arg_len; > ++ } > ++ } > ++ if (p[-1] !=3D '/') > ++ *p++ =3D '/'; > ++ *p++ =3D '*'; > ++ if (recurse) > ++ *p++ =3D '*'; > ++ *p =3D '\0'; > ++ rule->u.slash_cnt =3D slash_cnt + 1; > ++ rule->next =3D implied_filter_list.head; > ++ implied_filter_list.head =3D rule; > ++ } > ++} > ++ > + /* This frees any non-inherited items, leaving just inherited items on th= e list. */ > + static void pop_filter_list(filter_rule_list *listp) > + { > +@@ -718,7 +845,7 @@ static void report_filter_result(enum logcode code, ch= ar 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!=3D's'][!(ent->rflags & FILTRULE_INCLUDE)], > ++ w, actions[*w=3D=3D'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 **rules= tr_ptr, > + } > + switch (ch) { > + case ':': > ++ trust_sender_filter =3D 1; > + rule->rflags |=3D 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]; > +=20 > + extern struct chmod_mode_struct *chmod_modes; > +=20 > +-extern filter_rule_list filter_list; > +-extern filter_rule_list daemon_filter_list; > ++extern filter_rule_list filter_list, implied_filter_list, daemon_filter_l= ist; > +=20 > + #ifdef ICONV_OPTION > + extern int filesfrom_convert; > +@@ -986,6 +986,19 @@ static struct file_struct *recv_file_entry(int f, str= uct file_list *flist, int x > + exit_cleanup(RERR_UNSUPPORTED); > + } > +=20 > ++ if (*thisname !=3D '.' || thisname[1] !=3D '\0') { > ++ int filt_flags =3D S_ISDIR(mode) ? NAME_IS_DIR : NAME_IS_FILE; > ++ if (!trust_sender_filter /* a per-dir filter rule means we must trust t= he sender's filtering */ > ++ && filter_list.head && check_filter(&filter_list, FINFO, thisname, fil= t_flags) < 0) { > ++ rprintf(FERROR, "ERROR: rejecting excluded file-list name: %s\n", this= name); > ++ exit_cleanup(RERR_PROTOCOL); > ++ } > ++ if (implied_filter_list.head && check_filter(&implied_filter_list, FINF= O, thisname, filt_flags) <=3D 0) { > ++ rprintf(FERROR, "ERROR: rejecting unrequested file-list name: %s\n", t= hisname); > ++ 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 !=3D eob) { > + if (*s++ =3D=3D '\0') { > + ff_xb.len =3D 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 =3D ff_xb.buf + ff_xb.pos; > + char *t =3D ff_xb.buf; > + char *eob =3D f + len; > ++ char *cur =3D t; > + /* Eliminate any multi-'\0' runs. */ > + while (f !=3D eob) { > + if (!(*t++ =3D *f++)) { > ++ add_implied_include(cur); > ++ cur =3D t; > + while (f !=3D eob && *f =3D=3D '\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; > +=20 > + uid_t our_uid; > + gid_t our_gid; > +@@ -635,6 +636,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *us= er, char **remote_argv, in > + #ifdef ICONV_CONST > + setup_iconv(); > + #endif > ++ trust_sender_filter =3D 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 =3D 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 =3D 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 =3D "."; > + remote_argv[i] =3D arg; > ++ add_implied_include(arg); > + } > + } > +=20 > +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); > +=20 > +- if (daemon_filter_list.head && (*fname !=3D '.' || fname[1] !=3D '\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 !=3D '.' || fname[1] !=3D '\0'))= { > ++ int filt_flags =3D 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 ex= cluded file: %s\n", > ++ fname); > ++ exit_cleanup(RERR_PROTOCOL); > ++ } > + } > +=20 > + #ifdef SUPPORT_XATTRS > --=20 > 2.35.3 --===============5659693715593393803==--