summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/rsync/files/CVE-2022-29154.patch
blob: 61e4e03254ed14a40248f296d4f28e6c7655ad8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
From b7231c7d02cfb65d291af74ff66e7d8c507ee871 Mon Sep 17 00:00:00 2001
From: Wayne Davison <wayne@opencoder.net>
Date: Sun, 31 Jul 2022 16:55:34 -0700
Subject: [PATCH] Some extra file-list safety checks.

CVE-2022-29154 rsync: remote arbitrary files write inside the

Upstream-Status: Backport from [https://git.samba.org/?p=rsync.git;a=patch;h=b7231c7d02cfb65d291af74ff66e7d8c507ee871]
CVE:CVE-2022-29154
Signed-off-by: Vivek Kumbhar <vkumbhar@mvista.com>
---
 exclude.c  | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 flist.c    |  17 ++++++-
 io.c       |   4 ++
 main.c     |   7 ++-
 receiver.c |  11 +++--
 5 files changed, 158 insertions(+), 8 deletions(-)

diff --git a/exclude.c b/exclude.c
index 7989fb3..e146e96 100644
--- a/exclude.c
+++ b/exclude.c
@@ -26,16 +26,21 @@ 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 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;
@@ -43,8 +48,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)
@@ -293,6 +300,123 @@ 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 (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)
 {
@@ -721,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);
 	}
@@ -894,6 +1018,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 499440c..630d685 100644
--- a/flist.c
+++ b/flist.c
@@ -70,6 +70,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;
@@ -80,8 +81,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;
@@ -904,6 +904,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 c04dbd5..698a7da 100644
--- a/io.c
+++ b/io.c
@@ -415,6 +415,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'. */
@@ -446,9 +447,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 ee9630f..6ec56e7 100644
--- a/main.c
+++ b/main.c
@@ -78,6 +78,7 @@ extern BOOL flist_receiving_enabled;
 extern BOOL shutting_down;
 extern int backup_dir_len;
 extern int basis_dir_cnt;
+extern int trust_sender_filter;
 extern struct stats stats;
 extern char *stdout_format;
 extern char *logfile_format;
@@ -93,7 +94,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;
@@ -534,6 +535,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. */
@@ -1358,6 +1360,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++) {
@@ -1381,6 +1385,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 d6a48f1..c0aa893 100644
--- a/receiver.c
+++ b/receiver.c
@@ -577,10 +577,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.30.2