X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/e9357a2deb1adbeb8dd50f90f959a3ecd5f88806..ecc7623e7faf75f6ba3dd7b5a416c52e2346ac7d:/util.c diff --git a/util.c b/util.c index db0275a0..12b88edd 100644 --- a/util.c +++ b/util.c @@ -1,8 +1,10 @@ -/* -*- c-file-style: "linux" -*- +/* + * Utility routines used in rsync. * - * Copyright (C) 1996-2000 by Andrew Tridgell - * Copyright (C) Paul Mackerras 1996 - * Copyright (C) 2001, 2002 by Martin Pool + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003, 2004, 2005, 2006 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,17 +16,11 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * @file - * - * Utilities used in rsync - **/ - #include "rsync.h" extern int verbose; @@ -33,17 +29,18 @@ extern int module_id; extern int modify_window; extern int relative_paths; extern int human_readable; +extern unsigned int module_dirlen; extern mode_t orig_umask; extern char *partial_dir; extern struct filter_list_struct server_filter_list; int sanitize_paths = 0; +char curr_dir[MAXPATHLEN]; +unsigned int curr_dir_len; +int curr_dir_depth; /* This is only set for a sanitizing daemon. */ - -/** - * Set a fd into nonblocking mode - **/ +/* Set a fd into nonblocking mode. */ void set_nonblocking(int fd) { int val; @@ -56,9 +53,7 @@ void set_nonblocking(int fd) } } -/** - * Set a fd into blocking mode - **/ +/* Set a fd into blocking mode. */ void set_blocking(int fd) { int val; @@ -184,28 +179,29 @@ int mkdir_defmode(char *fname) return ret; } -/** - Create any necessary directories in fname. Unfortunately we don't know - what perms to give the directory when this is called so we need to rely - on the umask -**/ +/* Create any necessary directories in fname. Any missing directories are + * created with default permissions. */ int create_directory_path(char *fname) { char *p; + int ret = 0; while (*fname == '/') fname++; while (strncmp(fname, "./", 2) == 0) fname += 2; + umask(orig_umask); p = fname; while ((p = strchr(p,'/')) != NULL) { - *p = 0; - mkdir_defmode(fname); - *p = '/'; - p++; + *p = '\0'; + if (do_mkdir(fname, ACCESSPERMS) < 0 && errno != EEXIST) + ret = -1; + *p++ = '/'; } - return 0; + umask(0); + + return ret; } /** @@ -548,7 +544,7 @@ static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr, s = "."; if (sanitize_paths) - s = sanitize_path(NULL, s, "", 0); + s = sanitize_path(NULL, s, "", 0, NULL); else s = strdup(s); @@ -679,7 +675,7 @@ int count_dir_elements(const char *p) int cnt = 0, new_component = 1; while (*p) { if (*p++ == '/') - new_component = 1; + new_component = (*p != '.' || (p[1] != '/' && p[1] != '\0')); else if (new_component) { new_component = 0; cnt++; @@ -754,22 +750,29 @@ unsigned int clean_fname(char *name, BOOL collapse_dot_dot) * The rootdir string contains a value to use in place of a leading slash. * Specify NULL to get the default of lp_path(module_id). * - * If depth is >= 0, it is a count of how many '..'s to allow at the start - * of the path. Use -1 to allow unlimited depth. + * The depth var is a count of how many '..'s to allow at the start of the + * path. If symlink is set, combine its value with the "p" value to get + * the target path, and **return NULL if any '..'s try to escape**. * * We also clean the path in a manner similar to clean_fname() but with a - * few differences: + * few differences: * * Turns multiple adjacent slashes into a single slash, gets rid of "." dir * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and * ALWAYS collapses ".." elements (except for those at the start of the * string up to "depth" deep). If the resulting name would be empty, * change it into a ".". */ -char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) +char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, + const char *symlink) { - char *start, *sanp; + char *start, *sanp, *save_dest = dest; int rlen = 0, leave_one_dotdir = relative_paths; + if (symlink && *symlink == '/') { + p = symlink; + symlink = ""; + } + if (dest != p) { int plen = strlen(p); if (*p == '/') { @@ -792,7 +795,18 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) } start = sanp = dest + rlen; - while (*p != '\0') { + while (1) { + if (*p == '\0') { + if (!symlink || !*symlink) + break; + while (sanp != start && sanp[-1] != '/') { + /* strip last element */ + sanp--; + } + /* Append a relative symlink */ + p = symlink; + symlink = ""; + } /* discard leading or extra slashes */ if (*p == '/') { p++; @@ -814,6 +828,11 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { /* ".." component followed by slash or end */ if (depth <= 0 || sanp != start) { + if (symlink && sanp == start) { + if (!save_dest) + free(dest); + return NULL; + } p += 2; if (sanp != start) { /* back up sanp one level */ @@ -842,14 +861,88 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) return dest; } -char curr_dir[MAXPATHLEN]; -unsigned int curr_dir_len; +/* If sanitize_paths is not set, this works exactly the same as do_stat(). + * Otherwise, we verify that no symlink takes us outside the module path. + * If we encounter an escape attempt, we return a symlink's stat info! */ +int safe_stat(const char *fname, STRUCT_STAT *stp) +{ +#ifdef SUPPORT_LINKS + char tmpbuf[MAXPATHLEN], linkbuf[MAXPATHLEN], *mod_path; + int i, llen, mod_path_len; -/** - * Like chdir(), but it keeps track of the current directory (in the + if (!sanitize_paths) + return do_stat(fname, stp); + + mod_path = lp_path(module_id); + mod_path_len = strlen(mod_path); + + for (i = 0; i < 16; i++) { +#ifdef DEBUG + if (*fname == '/') + assert(strncmp(fname, mod_path, mod_path_len) == 0 && fname[mod_path_len] == '/'); +#endif + if (do_lstat(fname, stp) < 0) + return -1; + if (!S_ISLNK(stp->st_mode)) + return 0; + if ((llen = readlink(fname, linkbuf, sizeof linkbuf - 1)) < 0) + return -1; + linkbuf[llen] = '\0'; + if (*fname == '/') + fname += mod_path_len; + if (!(fname = sanitize_path(tmpbuf, fname, mod_path, curr_dir_depth, linkbuf))) + break; + } + + return 0; /* Leave *stp set to the last symlink. */ +#else + return do_stat(fname, stp); +#endif +} + +void die_on_unsafe_path(char *path, int strip_filename) +{ +#ifdef SUPPORT_LINKS + char *final_slash, *p; + STRUCT_STAT st; + + if (!path) + return; + if (strip_filename) { + if (!(final_slash = strrchr(path, '/'))) + return; + *final_slash = '\0'; + } else + final_slash = NULL; + + p = path; + if (*p == '/') + p += module_dirlen + 1; + while (*p) { + if ((p = strchr(p, '/')) != NULL) + *p = '\0'; + if (safe_stat(path, &st) < 0) { + *p++ = '/'; + goto done; + } + if (S_ISLNK(st.st_mode)) { + rprintf(FERROR, "Unsafe path: %s\n", path); + exit_cleanup(RERR_SYNTAX); + } + if (!p) + break; + *p++ = '/'; + } + + done: + if (final_slash) + *final_slash = '/'; +#endif +} + +/* Like chdir(), but it keeps track of the current directory (in the * global "curr_dir"), and ensures that the path size doesn't overflow. - * Also cleans the path using the clean_fname() function. - **/ + * Also cleans the path using the clean_fname() function. */ int push_dir(char *dir) { static int initialised; @@ -884,6 +977,11 @@ int push_dir(char *dir) } curr_dir_len = clean_fname(curr_dir, 1); + if (sanitize_paths) { + if (module_dirlen > curr_dir_len) + module_dirlen = curr_dir_len; + curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); + } return 1; } @@ -900,6 +998,8 @@ int pop_dir(char *dir) curr_dir_len = strlcpy(curr_dir, dir, sizeof curr_dir); if (curr_dir_len >= sizeof curr_dir) curr_dir_len = sizeof curr_dir - 1; + if (sanitize_paths) + curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); return 1; } @@ -966,13 +1066,11 @@ char *partial_dir_fname(const char *fname) if ((int)pathjoin(t, sz, partial_dir, fn) >= sz) return NULL; if (server_filter_list.head) { - static int len; - if (!len) - len = strlen(partial_dir); - t[len] = '\0'; + t = strrchr(partial_fname, '/'); + *t = '\0'; if (check_filter(&server_filter_list, partial_fname, 1) < 0) return NULL; - t[len] = '/'; + *t = '/'; if (check_filter(&server_filter_list, partial_fname, 0) < 0) return NULL; } @@ -998,6 +1096,8 @@ int handle_partial_dir(const char *fname, int create) if (create) { STRUCT_STAT st; int statret = do_lstat(dir, &st); + if (sanitize_paths && *partial_dir != '/') + die_on_unsafe_path(dir, 1); /* lstat handles last element */ if (statret == 0 && !S_ISDIR(st.st_mode)) { if (do_unlink(dir) < 0) return 0;