[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