If the receiver gets a filename with a leading slash (w/o --relative)
and/or a filename with an embedded ".." dir in the path, it dies with
an error (rather than continuing). Those invalid paths should never
happen in reality, so just reject someone trying to pull a fast one.
- 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);
if (sanitize_paths)
sanitize_path(thisname, thisname, "", 0, SP_DEFAULT);
}
/* The --relative option sends paths with a leading slash, so we need
}
/* 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 */
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_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)
#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 ".". */
* 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;
{
char *limit = name - 1, *t = name, *f = name;
int anchored;
+#define DOT_IS_DOT_DOT_DIR(bp) (bp[1] == '.' && (bp[2] == '/' || !bp[2]))
+
if ((anchored = *f == '/') != 0) {
*t++ = *f++;
#ifdef __CYGWIN__
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_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 == '/') {
while (*f) {
/* discard extra slashes */
if (*f == '/') {
if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR)
break;
/* collapse ".." dirs */
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)) {
+ if (flags & CFN_REFUSE_DOT_DOT_DIRS)
+ return -1;
if (s == name && anchored) {
f += 2;
continue;
if (s == name && anchored) {
f += 2;
continue;
+#undef DOT_IS_DOT_DOT_DIR
+