From 035b7c03dd7b60509813b5d8cb395e8b6fde9726 Mon Sep 17 00:00:00 2001 From: Paul Howarth Date: Jan 08 2013 12:17:02 +0000 Subject: Fix possible symlink race (CVE-2012-6095) Fix possible symlink race when applying UserOwner to newly created directory (CVE-2012-6095, #892715, http://bugs.proftpd.org/show_bug.cgi?id=3841) --- diff --git a/proftpd-1.3.4b-bug3841.patch b/proftpd-1.3.4b-bug3841.patch new file mode 100644 index 0000000..a0febdd --- /dev/null +++ b/proftpd-1.3.4b-bug3841.patch @@ -0,0 +1,505 @@ +--- contrib/mod_sftp/fxp.c 2012/12/27 22:29:39 1.134.2.7 ++++ contrib/mod_sftp/fxp.c 2012/12/28 00:03:27 1.134.2.8 +@@ -6133,7 +6133,7 @@ + (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; +--- contrib/mod_sftp/scp.c 2012/12/13 23:05:58 1.64.2.5 ++++ contrib/mod_sftp/scp.c 2012/12/28 00:03:27 1.64.2.6 +@@ -821,7 +821,7 @@ + * recursive directory uploads via SCP? + */ + +- 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, +--- include/fsio.h 2011/05/23 20:35:35 1.28 ++++ include/fsio.h 2012/12/28 00:03:26 1.28.2.2 +@@ -125,6 +125,7 @@ + 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 *); +@@ -243,6 +244,7 @@ + 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); +@@ -264,6 +266,7 @@ + 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 *); +--- modules/mod_core.c 2012/12/27 00:38:37 1.413.2.2 ++++ modules/mod_core.c 2012/12/28 00:03:27 1.413.2.3 +@@ -4643,7 +4643,8 @@ + 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): " +@@ -4652,71 +4653,6 @@ + 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)); + +--- modules/mod_xfer.c 2011/09/24 19:54:23 1.297 ++++ modules/mod_xfer.c 2012/12/26 23:33:32 1.297.2.1 +@@ -866,27 +866,25 @@ + * requested via GroupOwner. + */ + if ((session.fsuid != (uid_t) -1) && xfer_path) { +- int err = 0, iserr = 0; ++ int res, xerrno = 0; + + PRIVS_ROOT +- if (pr_fsio_chown(xfer_path, session.fsuid, session.fsgid) == -1) { +- iserr++; +- err = errno; +- } ++ res = pr_fsio_lchown(xfer_path, session.fsuid, session.fsgid); ++ xerrno = errno; + PRIVS_RELINQUISH + +- if (iserr) { +- pr_log_pri(PR_LOG_WARNING, "chown(%s) as root failed: %s", xfer_path, +- strerror(err)); ++ if (res < 0) { ++ pr_log_pri(PR_LOG_WARNING, "lchown(%s) as root failed: %s", xfer_path, ++ strerror(xerrno)); + + } else { + if (session.fsgid != (gid_t) -1) { +- pr_log_debug(DEBUG2, "root chown(%s) to uid %lu, gid %lu successful", ++ pr_log_debug(DEBUG2, "root lchown(%s) to uid %lu, gid %lu successful", + xfer_path, (unsigned long) session.fsuid, + (unsigned long) session.fsgid); + + } else { +- pr_log_debug(DEBUG2, "root chown(%s) to uid %lu successful", xfer_path, ++ pr_log_debug(DEBUG2, "root lchown(%s) to uid %lu successful", xfer_path, + (unsigned long) session.fsuid); + } + +@@ -902,16 +900,15 @@ + * root privs aren't used, the chmod() will fail because the old owner/ + * session user doesn't have the necessary privileges to do so). + */ +- iserr = 0; ++ xerrno = 0; + PRIVS_ROOT +- if (pr_fsio_chmod(xfer_path, st.st_mode) < 0) { +- iserr++; +- } ++ res = pr_fsio_chmod(xfer_path, st.st_mode); ++ xerrno = errno; + PRIVS_RELINQUISH + +- if (iserr) { ++ if (res < 0) { + pr_log_debug(DEBUG0, "root chmod(%s) to %04o failed: %s", xfer_path, +- (unsigned int) st.st_mode, strerror(errno)); ++ (unsigned int) st.st_mode, strerror(xerrno)); + + } else { + pr_log_debug(DEBUG2, "root chmod(%s) to %04o successful", xfer_path, +@@ -921,7 +918,7 @@ + + } else if ((session.fsgid != (gid_t) -1) && xfer_path) { + register unsigned int i; +- int res, use_root_privs = TRUE; ++ int res, 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++) { +@@ -937,18 +934,19 @@ + PRIVS_ROOT + } + +- res = pr_fsio_chown(xfer_path, (uid_t) -1, session.fsgid); ++ res = pr_fsio_lchown(xfer_path, (uid_t) -1, session.fsgid); ++ xerrno = errno; + + if (use_root_privs) { + PRIVS_RELINQUISH + } + +- if (res == -1) { +- pr_log_pri(PR_LOG_WARNING, "%schown(%s) failed: %s", +- use_root_privs ? "root " : "", xfer_path, strerror(errno)); ++ if (res < 0) { ++ pr_log_pri(PR_LOG_WARNING, "%slchown(%s) failed: %s", ++ use_root_privs ? "root " : "", xfer_path, strerror(xerrno)); + + } else { +- pr_log_debug(DEBUG2, "%schown(%s) to gid %lu successful", ++ pr_log_debug(DEBUG2, "%slchown(%s) to gid %lu successful", + use_root_privs ? "root " : "", xfer_path, + (unsigned long) session.fsgid); + +@@ -960,6 +958,7 @@ + } + + res = pr_fsio_chmod(xfer_path, st.st_mode); ++ xerrno = errno; + + if (use_root_privs) { + PRIVS_RELINQUISH +@@ -968,7 +967,7 @@ + if (res < 0) { + pr_log_debug(DEBUG0, "%schmod(%s) to %04o failed: %s", + use_root_privs ? "root " : "", xfer_path, (unsigned int) st.st_mode, +- strerror(errno)); ++ strerror(xerrno)); + } + } + } +--- src/fsio.c 2011/05/27 00:38:45 1.102 ++++ src/fsio.c 2012/12/29 00:11:49 1.102.2.3 +@@ -2,7 +2,7 @@ + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu +- * Copyright (C) 2001-2011 The ProFTPD Project ++ * Copyright (C) 2001-2012 The ProFTPD Project + * + * 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 +@@ -25,10 +25,11 @@ + */ + + /* ProFTPD virtual/modular file-system support +- * $Id: fsio.c,v 1.102 2011/05/27 00:38:45 castaglia Exp $ ++ * $Id: fsio.c,v 1.102.2.3 2012/12/29 00:11:49 castaglia Exp $ + */ + + #include "conf.h" ++#include "privs.h" + + #ifdef HAVE_SYS_STATVFS_H + # include +@@ -175,6 +176,10 @@ + 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. +@@ -2498,6 +2503,171 @@ + return res; + } + ++/* "safe 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) && ++ !S_ISLNK(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; +@@ -3357,6 +3527,33 @@ + 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; +@@ -4015,6 +4212,7 @@ + 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; +@@ -4096,6 +4294,12 @@ + if (fs->chown) + hooks = pstrcat(p, hooks, *hooks ? ", " : "", "chown(2)", NULL); + ++ if (fs->fchown) ++ hooks = pstrcat(p, hooks, *hooks ? ", " : "", "fchown(2)", NULL); ++ ++ if (fs->lchown) ++ hooks = pstrcat(p, hooks, *hooks ? ", " : "", "lchown(2)", NULL); ++ + if (fs->access) + hooks = pstrcat(p, hooks, *hooks ? ", " : "", "access(2)", NULL); + diff --git a/proftpd.spec b/proftpd.spec index 0a03b32..4384ebc 100644 --- a/proftpd.spec +++ b/proftpd.spec @@ -41,7 +41,7 @@ %define _hardened_build 1 #global prever rc3 -%global rpmrel 3 +%global rpmrel 4 Summary: Flexible, stable and highly-configurable FTP server Name: proftpd @@ -70,6 +70,7 @@ Patch14: proftpd-1.3.4a-bug3720.patch Patch23: proftpd-1.3.4a-bug3744.patch Patch24: proftpd-1.3.4a-bug3745.patch Patch25: proftpd-1.3.4a-bug3746.patch +Patch26: proftpd-1.3.4b-bug3841.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Requires(preun): coreutils, findutils %if %{use_systemd} @@ -232,6 +233,10 @@ cp -p %{SOURCE1} proftpd.conf # http://bugs.proftpd.org/show_bug.cgi?id=3746 %patch25 -p0 +# Fix possible symlink race when applying UserOwner to newly created directory +# http://bugs.proftpd.org/show_bug.cgi?id=3841 +%patch26 + # Avoid documentation name conflicts mv contrib/README contrib/README.contrib @@ -528,6 +533,10 @@ fi %{_mandir}/man1/ftpwho.1* %changelog +* Mon Jan 7 2013 Paul Howarth 1.3.4b-4 +- Fix possible symlink race when applying UserOwner to newly created directory + (CVE-2012-6095, #892715, http://bugs.proftpd.org/show_bug.cgi?id=3841) + * Sat Sep 22 2012 Remi Collet 1.3.4b-3 - Rebuild against libmemcached.so.11 without SASL @@ -1233,7 +1242,7 @@ fi - Removed the prefix (relocations a rarely used on non-X packages) - Gzipped the man pages -* Thu Oct 03 1999 O.Elliyasa +* Thu Oct 07 1999 O.Elliyasa - Multi package creation. Created core, standalone, inetd (&doc) package creations. Added startup script for init.d