Skip to content

Commit 9442805

Browse files
committed
Fix a symlink-related security vulnerability.
The fix in commit 34b1087 and contained a small attack time window in between two filesystem operations. This has been fixed.
1 parent 2dcb730 commit 9442805

File tree

4 files changed

+40
-51
lines changed

4 files changed

+40
-51
lines changed

NEWS

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
Release 4.0.38
2+
--------------
3+
4+
* Fixed a symlink-related security vulnerability.
5+
6+
Urgency: low
7+
Scope: local exploit
8+
Summary: writing files to arbitrary directory by hijacking temp directories
9+
Affected versions: 4.0.37
10+
Fixed versions: 4.0.38
11+
12+
Description:
13+
This issue is related to the security issue as mentioned in the 4.0.37
14+
release notes. The previous fix was incomplete, and still has a
15+
(albeit smaller) small attack time window in between two filesystem
16+
checks. This attack window is now gone.
17+
18+
119
Release 4.0.37
220
--------------
321

ext/common/ServerInstanceDir.h

+22-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Phusion Passenger - https://www.phusionpassenger.com/
3-
* Copyright (c) 2010-2013 Phusion
3+
* Copyright (c) 2010-2014 Phusion
44
*
55
* "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
66
*
@@ -193,6 +193,9 @@ class ServerInstanceDir: public noncopyable {
193193

194194
void initialize(const string &path, bool owner) {
195195
TRACE_POINT();
196+
struct stat buf;
197+
int ret;
198+
196199
this->path = path;
197200
this->owner = owner;
198201

@@ -212,18 +215,25 @@ class ServerInstanceDir: public noncopyable {
212215
* rights though, because we want admin tools to be able to list the available
213216
* generations no matter what user they're running as.
214217
*/
218+
219+
do {
220+
ret = lstat(path.c_str(), &buf);
221+
} while (ret == -1 && errno == EAGAIN);
215222
if (owner) {
216-
switch (getFileTypeNoFollowSymlinks(path)) {
217-
case FT_NONEXISTANT:
223+
if (ret == 0) {
224+
if (S_ISDIR(buf.st_mode)) {
225+
verifyDirectoryPermissions(path, buf);
226+
} else {
227+
throw RuntimeException("'" + path + "' already exists, and is not a directory");
228+
}
229+
} else if (errno == ENOENT) {
218230
createDirectory(path);
219-
break;
220-
case FT_DIRECTORY:
221-
verifyDirectoryPermissions(path);
222-
break;
223-
default:
224-
throw RuntimeException("'" + path + "' already exists, and is not a directory");
231+
} else {
232+
int e = errno;
233+
throw FileSystemException("Cannot lstat '" + path + "'",
234+
e, path);
225235
}
226-
} else if (getFileType(path) != FT_DIRECTORY) {
236+
} else if (!S_ISDIR(buf.st_mode)) {
227237
throw RuntimeException("Server instance directory '" + path +
228238
"' does not exist");
229239
}
@@ -259,14 +269,10 @@ class ServerInstanceDir: public noncopyable {
259269
* so that an attacker cannot pre-create a directory with too liberal
260270
* permissions.
261271
*/
262-
void verifyDirectoryPermissions(const string &path) {
272+
void verifyDirectoryPermissions(const string &path, struct stat &buf) {
263273
TRACE_POINT();
264-
struct stat buf;
265274

266-
if (stat(path.c_str(), &buf) == -1) {
267-
int e = errno;
268-
throw FileSystemException("Cannot stat() " + path, e, path);
269-
} else if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) {
275+
if (buf.st_mode != (S_IFDIR | parseModeString("u=rwx,g=rx,o=rx"))) {
270276
throw RuntimeException("Tried to reuse existing server instance directory " +
271277
path + ", but it has wrong permissions");
272278
} else if (buf.st_uid != geteuid() || buf.st_gid != getegid()) {

ext/common/Utils.cpp

-29
Original file line numberDiff line numberDiff line change
@@ -143,35 +143,6 @@ getFileType(const StaticString &filename, CachedFileStat *cstat, unsigned int th
143143
}
144144
}
145145

146-
FileType
147-
getFileTypeNoFollowSymlinks(const StaticString &filename) {
148-
struct stat buf;
149-
int ret;
150-
151-
ret = lstat(filename.c_str(), &buf);
152-
if (ret == 0) {
153-
if (S_ISREG(buf.st_mode)) {
154-
return FT_REGULAR;
155-
} else if (S_ISDIR(buf.st_mode)) {
156-
return FT_DIRECTORY;
157-
} else if (S_ISLNK(buf.st_mode)) {
158-
return FT_SYMLINK;
159-
} else {
160-
return FT_OTHER;
161-
}
162-
} else {
163-
if (errno == ENOENT) {
164-
return FT_NONEXISTANT;
165-
} else {
166-
int e = errno;
167-
string message("Cannot lstat '");
168-
message.append(filename);
169-
message.append("'");
170-
throw FileSystemException(message, e, filename);
171-
}
172-
}
173-
}
174-
175146
void
176147
createFile(const string &filename, const StaticString &contents, mode_t permissions, uid_t owner,
177148
gid_t group, bool overwrite)

ext/common/Utils.h

-6
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ typedef enum {
6565
FT_REGULAR,
6666
/** A directory. */
6767
FT_DIRECTORY,
68-
/** A symlink. Only returned by getFileTypeNoFollowSymlinks(), not by getFileType(). */
69-
FT_SYMLINK,
7068
/** Something else, e.g. a pipe or a socket. */
7169
FT_OTHER
7270
} FileType;
@@ -123,10 +121,6 @@ bool fileExists(const StaticString &filename, CachedFileStat *cstat = 0,
123121
*/
124122
FileType getFileType(const StaticString &filename, CachedFileStat *cstat = 0,
125123
unsigned int throttleRate = 0);
126-
/**
127-
* Like getFileType(), but does not follow symlinks.
128-
*/
129-
FileType getFileTypeNoFollowSymlinks(const StaticString &filename);
130124

131125
/**
132126
* Create the given file with the given contents, permissions and ownership.

0 commit comments

Comments
 (0)