diff -Naurp proftpd-1.3.3g/contrib/mod_sftp/fxp.c proftpd-1.3.3g.oden/contrib/mod_sftp/fxp.c --- proftpd-1.3.3g/contrib/mod_sftp/fxp.c 2011-03-19 23:02:09.000000000 +0000 +++ proftpd-1.3.3g.oden/contrib/mod_sftp/fxp.c 2013-04-05 13:38:25.000000000 +0000 @@ -5498,7 +5498,7 @@ static int fxp_handle_mkdir(struct fxp_p (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "creating directory '%s' with mode 0%o", path, (unsigned int) dir_mode); - res = pr_fsio_mkdir(path, dir_mode); + res = pr_fsio_smkdir(fxp->pool, path, dir_mode, (uid_t) -1, (gid_t) -1); if (res < 0) { const char *reason; int xerrno = errno; diff -Naurp proftpd-1.3.3g/contrib/mod_sftp/scp.c proftpd-1.3.3g.oden/contrib/mod_sftp/scp.c --- proftpd-1.3.3g/contrib/mod_sftp/scp.c 2011-02-26 02:46:45.000000000 +0000 +++ proftpd-1.3.3g.oden/contrib/mod_sftp/scp.c 2013-04-05 13:38:25.000000000 +0000 @@ -711,7 +711,7 @@ static int recv_finfo(pool *p, uint32_t if (xerrno == ENOENT) { pr_trace_msg(trace_channel, 5, "creating directory '%s'", sp->filename); - if (pr_fsio_mkdir(sp->filename, 0777) < 0) { + if (pr_fsio_smkdir(p, sp->filename, 0777, (uid_t) -1, (gid_t) -1) < 0) { xerrno = errno; (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, diff -Naurp proftpd-1.3.3g/include/fsio.h proftpd-1.3.3g.oden/include/fsio.h --- proftpd-1.3.3g/include/fsio.h 2009-11-05 17:46:54.000000000 +0000 +++ proftpd-1.3.3g.oden/include/fsio.h 2013-04-05 13:38:25.000000000 +0000 @@ -129,6 +129,7 @@ struct fs_rec { int (*fchmod)(pr_fh_t *, int, mode_t); int (*chown)(pr_fs_t *, const char *, uid_t, gid_t); int (*fchown)(pr_fh_t *, int, uid_t, gid_t); + int (*lchown)(pr_fs_t *, const char *, uid_t, gid_t); int (*access)(pr_fs_t *, const char *, int, uid_t, gid_t, array_header *); int (*faccess)(pr_fh_t *, int, uid_t, gid_t, array_header *); int (*utimes)(pr_fs_t *, const char *, struct timeval *); @@ -249,6 +250,7 @@ int pr_fsio_mkdir(const char *, mode_t); int pr_fsio_rmdir(const char *); int pr_fsio_rename(const char *, const char *); int pr_fsio_rename_canon(const char *, const char *); +int pr_fsio_smkdir(pool *, const char *, mode_t, uid_t, gid_t); int pr_fsio_unlink(const char *); int pr_fsio_unlink_canon(const char *); pr_fh_t *pr_fsio_open(const char *, int); @@ -270,6 +272,7 @@ int pr_fsio_fchmod(pr_fh_t *, mode_t); int pr_fsio_chmod_canon(const char *, mode_t); int pr_fsio_chown(const char *, uid_t, gid_t); int pr_fsio_fchown(pr_fh_t *, uid_t, gid_t); +int pr_fsio_lchown(const char *, uid_t, gid_t); int pr_fsio_chown_canon(const char *, uid_t, gid_t); int pr_fsio_chroot(const char *); int pr_fsio_access(const char *, int, uid_t, gid_t, array_header *); diff -Naurp proftpd-1.3.3g/modules/mod_core.c proftpd-1.3.3g.oden/modules/mod_core.c --- proftpd-1.3.3g/modules/mod_core.c 2011-02-21 02:36:38.000000000 +0000 +++ proftpd-1.3.3g.oden/modules/mod_core.c 2013-04-05 13:38:25.000000000 +0000 @@ -4043,7 +4043,8 @@ MODRET core_mkd(cmd_rec *cmd) { return PR_ERROR(cmd); } - if (pr_fsio_mkdir(dir, 0777) < 0) { + if (pr_fsio_smkdir(cmd->tmp_pool, dir, 0777, session.fsuid, + session.fsgid) < 0) { int xerrno = errno; (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %lu, GID %lu): " @@ -4055,71 +4056,6 @@ MODRET core_mkd(cmd_rec *cmd) { return PR_ERROR(cmd); } - /* Check to see if we need to change the ownership (user and/or group) of - * the newly created directory. - */ - if (session.fsuid != (uid_t) -1) { - int err = 0, iserr = 0; - - pr_fsio_stat(dir, &st); - - PRIVS_ROOT - if (pr_fsio_chown(dir, session.fsuid, session.fsgid) == -1) { - iserr++; - err = errno; - } - PRIVS_RELINQUISH - - if (iserr) { - pr_log_pri(PR_LOG_WARNING, "chown() as root failed: %s", strerror(err)); - - } else { - if (session.fsgid != (gid_t) -1) { - pr_log_debug(DEBUG2, "root chown(%s) to uid %lu, gid %lu successful", - dir, (unsigned long) session.fsuid, (unsigned long) session.fsgid); - - } else { - pr_log_debug(DEBUG2, "root chown(%s) to uid %lu successful", dir, - (unsigned long) session.fsuid); - } - } - - } else if (session.fsgid != (gid_t) -1) { - register unsigned int i; - int use_root_privs = TRUE; - - pr_fsio_stat(dir, &st); - - /* Check if session.fsgid is in session.gids. If not, use root privs. */ - for (i = 0; i < session.gids->nelts; i++) { - gid_t *group_ids = session.gids->elts; - - if (group_ids[i] == session.fsgid) { - use_root_privs = FALSE; - break; - } - } - - if (use_root_privs) { - PRIVS_ROOT - } - - res = pr_fsio_chown(dir, (uid_t) -1, session.fsgid); - - if (use_root_privs) { - PRIVS_RELINQUISH - } - - if (res == -1) { - pr_log_pri(PR_LOG_WARNING, "%schown() failed: %s", - use_root_privs ? "root " : "", strerror(errno)); - - } else { - pr_log_debug(DEBUG2, "%schown(%s) to gid %lu successful", - use_root_privs ? "root " : "", dir, (unsigned long) session.fsgid); - } - } - pr_response_add(R_257, _("\"%s\" - Directory successfully created"), quote_dir(cmd, dir)); diff -Naurp proftpd-1.3.3g/src/fsio.c proftpd-1.3.3g.oden/src/fsio.c --- proftpd-1.3.3g/src/fsio.c 2010-04-12 19:00:00.000000000 +0000 +++ proftpd-1.3.3g.oden/src/fsio.c 2013-04-05 13:38:25.000000000 +0000 @@ -29,6 +29,7 @@ */ #include "conf.h" +#include "privs.h" #ifdef HAVE_REGEX_H # include <regex.h> @@ -179,6 +180,10 @@ static int sys_fchown(pr_fh_t *fh, int f return fchown(fd, uid, gid); } +static int sys_lchown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) { + return lchown(path, uid, gid); +} + /* We provide our own equivalent of access(2) here, rather than using * access(2) directly, because access(2) uses the real IDs, rather than * the effective IDs, of the process. @@ -2477,6 +2482,170 @@ int pr_fsio_mkdir(const char *path, mode return res; } +/* "secure mkdir" variant of mkdir(2), uses mkdtemp(3), lchown(2), and + * rename(2) to create a directory which cannot be hijacked by a symlink + * race (hopefully) before the UserOwner/GroupOwner ownership changes are + * applied. + */ +int pr_fsio_smkdir(pool *p, const char *path, mode_t mode, uid_t uid, + gid_t gid) { + int res; + char *tmpl_path; +#ifdef HAVE_MKDTEMP + mode_t mask, *dir_umask; + char *dst_dir, *tmpl, *ptr; + size_t tmpl_len; + struct stat st; +#endif /* HAVE_MKDTEMP */ + + if (path == NULL) { + errno = EINVAL; + return -1; + } + +#ifdef HAVE_MKDTEMP + ptr = strrchr(path, '/'); + if (ptr == NULL) { + errno = EINVAL; + return -1; + } + + dst_dir = pstrndup(p, path, (ptr - path)); + res = lstat(dst_dir, &st); + if (res < 0) { + return -1; + } + + if (!S_ISDIR(st.st_mode)) { + errno = EPERM; + return -1; + } + + /* Allocate enough space for the temporary name: the length of the + * destination directory, a slash, 9 X's, 3 for the prefix, and 1 for the + * trailing NUL. + */ + tmpl_len = strlen(path) + 14; + tmpl = pcalloc(p, tmpl_len); + snprintf(tmpl, tmpl_len-1, "%s/dstXXXXXXXXX", dst_dir); + + /* Use mkdtemp(3) to create the temporary directory (in the same destination + * directory as the target path). + */ + tmpl_path = mkdtemp(tmpl); + if (tmpl_path == NULL) { + return -1; + } +#else + + res = pr_fsio_mkdir(path, mode); + if (res < 0) { + return -1; + } + + tmpl_path = pstrdup(p, path); + +#endif /* HAVE_MKDTEMP */ + + if (uid != (uid_t) -1) { + int xerrno; + + PRIVS_ROOT + res = pr_fsio_lchown(tmpl_path, uid, gid); + xerrno = errno; + PRIVS_RELINQUISH + + if (res < 0) { + pr_log_pri(PR_LOG_WARNING, "lchown(%s) as root failed: %s", tmpl_path, + strerror(xerrno)); + + } else { + if (gid != (gid_t) -1) { + pr_log_debug(DEBUG2, "root lchown(%s) to UID %lu, GID %lu successful", + tmpl_path, (unsigned long) uid, (unsigned long) gid); + + } else { + pr_log_debug(DEBUG2, "root lchown(%s) to UID %lu successful", + tmpl_path, (unsigned long) uid); + } + } + + } else if (gid != (gid_t) -1) { + register unsigned int i; + int use_root_privs = TRUE, xerrno; + + /* Check if session.fsgid is in session.gids. If not, use root privs. */ + for (i = 0; i < session.gids->nelts; i++) { + gid_t *group_ids = session.gids->elts; + + if (group_ids[i] == gid) { + use_root_privs = FALSE; + break; + } + } + + if (use_root_privs) { + PRIVS_ROOT + } + + res = pr_fsio_lchown(tmpl_path, (uid_t) -1, gid); + xerrno = errno; + + if (use_root_privs) { + PRIVS_RELINQUISH + } + + if (res < 0) { + pr_log_pri(PR_LOG_WARNING, "%slchown(%s) failed: %s", + use_root_privs ? "root " : "", tmpl_path, strerror(xerrno)); + + } else { + pr_log_debug(DEBUG2, "%slchown(%s) to GID %lu successful", + use_root_privs ? "root " : "", tmpl_path, (unsigned long) gid); + } + } + +#ifdef HAVE_MKDTEMP + /* Use chmod(2) to set the permission that we want. + * + * mkdtemp(3) creates a directory with 0700 perms; we are given the + * target mode (modulo the configured Umask). + */ + dir_umask = get_param_ptr(CURRENT_CONF, "DirUmask", FALSE); + if (dir_umask) { + mask = *dir_umask; + + } else { + mask = (mode_t) 0022; + } + + res = chmod(tmpl_path, mode & ~mask); + if (res < 0) { + int xerrno = errno; + + (void) rmdir(tmpl_path); + + errno = xerrno; + return -1; + } + + /* Use rename(2) to move the temporary directory into place at the + * target path. + */ + res = rename(tmpl_path, path); + if (res < 0) { + int xerrno = errno; + + (void) rmdir(tmpl_path); + + errno = xerrno; + return -1; + } +#endif /* HAVE_MKDTEMP */ + + return 0; +} + int pr_fsio_rmdir(const char *path) { int res; pr_fs_t *fs; @@ -3336,6 +3505,33 @@ int pr_fsio_fchown(pr_fh_t *fh, uid_t ui return res; } +int pr_fsio_lchown(const char *name, uid_t uid, gid_t gid) { + int res; + pr_fs_t *fs; + + fs = lookup_file_fs(name, NULL, FSIO_FILE_CHOWN); + if (fs == NULL) { + return -1; + } + + /* Find the first non-NULL custom lchown handler. If there are none, + * use the system chown. + */ + while (fs && fs->fs_next && !fs->lchown) { + fs = fs->fs_next; + } + + pr_trace_msg(trace_channel, 8, "using %s lchown() for path '%s'", + fs->fs_name, name); + res = (fs->lchown)(fs, name, uid, gid); + + if (res == 0) { + pr_fs_clear_cache(); + } + + return res; +} + int pr_fsio_access(const char *path, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { pr_fs_t *fs; @@ -3955,6 +4151,7 @@ int init_fs(void) { root_fs->fchmod = sys_fchmod; root_fs->chown = sys_chown; root_fs->fchown = sys_fchown; + root_fs->lchown = sys_lchown; root_fs->access = sys_access; root_fs->faccess = sys_faccess; root_fs->utimes = sys_utimes; @@ -4036,6 +4233,9 @@ static const char *get_fs_hooks_str(pool if (fs->chown) hooks = pstrcat(p, hooks, *hooks ? ", " : "", "chown(2)", NULL); + if (fs->lchown) + hooks = pstrcat(p, hooks, *hooks ? ", " : "", "lchown(2)", NULL); + if (fs->access) hooks = pstrcat(p, hooks, *hooks ? ", " : "", "access(2)", NULL);