CVE-2014-1832 (published 2015-02-19T15:59Z)

Phusion Passenger 4.0.37 allows local users to write to certain files and directories via a symlink attack on (1) control_process.pid or a (2) generation-* file.  NOTE: this vulnerability exists because of an incomplete fix for CVE-2014-1831.

Severity: LOW
CVSS v2 base score: 2.1 (AV:L/AC:L/Au:N/C:N/I:P/A:N)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/phusion/passenger
commit_details: repo_url: https://github.com/phusion/passenger
author: Hongli Lai (Phusion)
author_date: 2014-01-29 14:19:25+01:00
msg: Fix a symlink-related security vulnerability.

The fix in commit 34b10878 and contained a small attack time window in
between two filesystem operations. This has been fixed.
repository: repo_url: https://github.com/phusion/passenger
repo_name: phusion/passenger
description: A fast and robust web server and application server for Ruby, Python and Node.js
repo_language: C++
owner: phusion
file_changes: filename: NEWS
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,21 @@
+Release 4.0.38
+--------------
+
+ * Fixed a symlink-related security vulnerability.
+
+   Urgency: low
+   Scope: local exploit
+   Summary: writing files to arbitrary directory by hijacking temp directories
+   Affected versions: 4.0.37
+   Fixed versions: 4.0.38
+
+   Description:
+   This issue is related to the security issue as mentioned in the 4.0.37
+   release notes. The previous fix was incomplete, and still has a
+   (albeit smaller) small attack time window in between two filesystem
+   checks. This attack window is now gone.
+
+
 Release 4.0.37
 --------------
 

programming_language: Markdown
filename: ServerInstanceDir.h
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 /*
  *  Phusion Passenger - https://www.phusionpassenger.com/
- *  Copyright (c) 2010-2013 Phusion
+ *  Copyright (c) 2010-2014 Phusion
  *
  *  "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
  *
@@ -193,6 +193,9 @@ private:
 	
 	void initialize(const string &path, bool owner) {
 		TRACE_POINT();
+		struct stat buf;
+		int ret;
+
 		this->path  = path;
 		this->owner = owner;
 		
@@ -212,18 +215,25 @@ private:
 		 * rights though, because we want admin tools to be able to list the available
 		 * generations no matter what user they're running as.
 		 */
+
+		do {
+			ret = lstat(path.c_str(), &buf);
+		} while (ret == -1 && errno == EAGAIN);
 		if (owner) {
-			switch (getFileTypeNoFollowSymlinks(path)) {
-			case FT_NONEXISTANT:
+			if (ret == 0) {
+				if (S_ISDIR(buf.st_mode)) {
+					verifyDirectoryPermissions(path, buf);
+				} else {
+					throw RuntimeException("'" + path + "' already exists, and is not a directory");
+				}
+			} else if (errno == ENOENT) {
 				createDirectory(path);
-				break;
-			case FT_DIRECTORY:
-				verifyDirectoryPermissions(path);
-				break;
-			default:
-				throw RuntimeException("'" + path + "' already exists, and is not a directory");
+			} else {
+				int e = errno;
+				throw FileSystemException("Cannot lstat '" + path + "'",
+					e, path);
 			}
-		} else if (getFileType(path) != FT_DIRECTORY) {
+		} else if (!S_ISDIR(buf.st_mode)) {
 			throw RuntimeException("Server instance directory '" + path +
 				"' does not exist");
 		}
@@ -259,14 +269,10 @@ private:
 	 * so that an attacker cannot pre-create a directory with too liberal
 	 * permissions.
 	 */
-	void verifyDirectoryPermissions(const string &path) {
+	void verifyDirectoryPermissions(const string &path, struct stat &buf) {
 		TRACE_POINT();
-		struct stat buf;
 
-		if (stat(path.c_str(), &buf) == -1) {
-			int e = errno;
-			throw FileSystemException("Cannot stat() " + path, e, path);
-		} else if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) {
+		if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) {
 			throw RuntimeException("Tried to reuse existing server instance directory " +
 				path + ", but it has wrong permissions");
 		} else if (buf.st_uid != geteuid() || buf.st_gid != getegid()) {

programming_language: C++
filename: Utils.cpp
change_type: ModificationType.MODIFY
diff: @@ -143,35 +143,6 @@ getFileType(const StaticString &filename, CachedFileStat *cstat, unsigned int th
 	}
 }
 
-FileType
-getFileTypeNoFollowSymlinks(const StaticString &filename) {
-	struct stat buf;
-	int ret;
-	
-	ret = lstat(filename.c_str(), &buf);
-	if (ret == 0) {
-		if (S_ISREG(buf.st_mode)) {
-			return FT_REGULAR;
-		} else if (S_ISDIR(buf.st_mode)) {
-			return FT_DIRECTORY;
-		} else if (S_ISLNK(buf.st_mode)) {
-			return FT_SYMLINK;
-		} else {
-			return FT_OTHER;
-		}
-	} else {
-		if (errno == ENOENT) {
-			return FT_NONEXISTANT;
-		} else {
-			int e = errno;
-			string message("Cannot lstat '");
-			message.append(filename);
-			message.append("'");
-			throw FileSystemException(message, e, filename);
-		}
-	}
-}
-
 void
 createFile(const string &filename, const StaticString &contents, mode_t permissions, uid_t owner,
 	gid_t group, bool overwrite)

programming_language: C++
filename: Utils.h
change_type: ModificationType.MODIFY
diff: @@ -65,8 +65,6 @@ typedef enum {
 	FT_REGULAR,
 	/** A directory. */
 	FT_DIRECTORY,
-	/** A symlink. Only returned by getFileTypeNoFollowSymlinks(), not by getFileType(). */
-	FT_SYMLINK,
 	/** Something else, e.g. a pipe or a socket. */
 	FT_OTHER
 } FileType;
@@ -123,10 +121,6 @@ bool fileExists(const StaticString &filename, CachedFileStat *cstat = 0,
  */
 FileType getFileType(const StaticString &filename, CachedFileStat *cstat = 0,
                      unsigned int throttleRate = 0);
-/**
- * Like getFileType(), but does not follow symlinks.
- */
-FileType getFileTypeNoFollowSymlinks(const StaticString &filename);
 
 /**
  * Create the given file with the given contents, permissions and ownership.

programming_language: C++