[proftpd] Fix possible symlink race (CVE-2012-6095)

Paul Howarth pghmcfc at fedoraproject.org
Tue Jan 8 12:18:28 UTC 2013


commit 035b7c03dd7b60509813b5d8cb395e8b6fde9726
Author: Paul Howarth <paul at city-fan.org>
Date:   Tue Jan 8 12:17:02 2013 +0000

    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)

 proftpd-1.3.4b-bug3841.patch |  505 ++++++++++++++++++++++++++++++++++++++++++
 proftpd.spec                 |   13 +-
 2 files changed, 516 insertions(+), 2 deletions(-)
---
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 <macgyver at tos.net>
+- * 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 <sys/statvfs.h>
+@@ -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 <paul at city-fan.org> 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 <remi at fedoraproject.org> 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 <osman at Cable.EU.org>
+* Thu Oct 07 1999 O.Elliyasa <osman at Cable.EU.org>
 - Multi package creation.
   Created core, standalone, inetd (&doc) package creations.
   Added startup script for init.d


More information about the scm-commits mailing list