}
#endif
- if (*thisname)
- clean_fname(thisname, 0);
+ if (*thisname
+ && (clean_fname(thisname, CFN_REFUSE_DOT_DOT_DIRS) < 0 || (!relative_paths && *thisname == '/'))) {
+ rprintf(FERROR, "ABORTING due to unsafe pathname from sender: %s\n", thisname);
+ exit_cleanup(RERR_PROTOCOL);
+ }
if (sanitize_paths)
sanitize_path(thisname, thisname, "", 0, SP_DEFAULT);
}
/* The --relative option sends paths with a leading slash, so we need
- * to specify the strip_root option here. We also want to ensure that
- * a non-relative transfer doesn't have any leading slashes or it might
- * cause the client a security issue. */
- flist_sort_and_clean(flist, 1);
+ * to specify the strip_root option here. We rejected leading slashes
+ * for a non-relative transfer in recv_file_entry(). */
+ flist_sort_and_clean(flist, relative_paths);
if (protocol_version < 30) {
/* Recv the io_error flag */
#define CFN_KEEP_TRAILING_SLASH (1<<1)
#define CFN_DROP_TRAILING_DOT_DIR (1<<2)
#define CFN_COLLAPSE_DOT_DOT_DIRS (1<<3)
+#define CFN_REFUSE_DOT_DOT_DIRS (1<<4)
#define SP_DEFAULT 0
#define SP_KEEP_DOT_DIRS (1<<0)
* CFN_KEEP_TRAILING_SLASH is flagged, and will also collapse ".." elements
* (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged. If the
* resulting name would be empty, returns ".". */
-unsigned int clean_fname(char *name, int flags)
+int clean_fname(char *name, int flags)
{
char *limit = name - 1, *t = name, *f = name;
int anchored;
if (!name)
return 0;
+#define DOT_IS_DOT_DOT_DIR(bp) (bp[1] == '.' && (bp[2] == '/' || !bp[2]))
+
if ((anchored = *f == '/') != 0) {
*t++ = *f++;
#ifdef __CYGWIN__
} else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') {
*t++ = *f++;
*t++ = *f++;
- }
+ } else if (flags & CFN_REFUSE_DOT_DOT_DIRS && *f == '.' && DOT_IS_DOT_DOT_DIR(f))
+ return -1;
while (*f) {
/* discard extra slashes */
if (*f == '/') {
if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR)
break;
/* collapse ".." dirs */
- if (flags & CFN_COLLAPSE_DOT_DOT_DIRS
- && f[1] == '.' && (f[2] == '/' || !f[2])) {
+ if (flags & (CFN_COLLAPSE_DOT_DOT_DIRS|CFN_REFUSE_DOT_DOT_DIRS) && DOT_IS_DOT_DOT_DIR(f)) {
char *s = t - 1;
+ if (flags & CFN_REFUSE_DOT_DOT_DIRS)
+ return -1;
if (s == name && anchored) {
f += 2;
continue;
*t++ = '.';
*t = '\0';
+#undef DOT_IS_DOT_DOT_DIR
+
return t - name;
}