passenger 5.3.1 → 5.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +19 -0
- data/build/cxx_tests.rb +3 -1
- data/build/support/cxx_dependency_map.rb +120 -27
- data/dev/configkit-schemas/index.json +15 -3
- data/src/agent/Core/AdminPanelConnector.h +5 -2
- data/src/agent/Core/ApplicationPool/Group/StateInspection.cpp +2 -0
- data/src/agent/Core/Config.h +2 -1
- data/src/agent/Core/Controller/Config.h +6 -1
- data/src/agent/Core/Controller/InitRequest.cpp +6 -1
- data/src/agent/Core/CoreMain.cpp +26 -60
- data/src/agent/Core/SpawningKit/DirectSpawner.h +18 -6
- data/src/agent/Core/SpawningKit/ErrorRenderer.h +8 -8
- data/src/agent/Core/SpawningKit/Handshake/Perform.h +217 -61
- data/src/agent/Core/SpawningKit/Handshake/Prepare.h +57 -8
- data/src/agent/Core/SpawningKit/Handshake/Session.h +34 -1
- data/src/agent/Core/SpawningKit/Handshake/WorkDir.h +20 -4
- data/src/agent/Core/SpawningKit/SmartSpawner.h +90 -27
- data/src/agent/ExecHelper/ExecHelperMain.cpp +3 -0
- data/src/agent/Shared/ApiAccountUtils.h +2 -2
- data/src/agent/SpawnEnvSetupper/SpawnEnvSetupperMain.cpp +14 -4
- data/src/agent/Watchdog/Config.h +2 -1
- data/src/agent/Watchdog/WatchdogMain.cpp +38 -0
- data/src/apache2_module/Hooks.cpp +1 -0
- data/src/cxx_supportlib/ConfigKit/IN_PRACTICE.md +1 -1
- data/src/cxx_supportlib/ConfigKit/README.md +1 -1
- data/src/cxx_supportlib/Constants.h +6 -1
- data/src/cxx_supportlib/FileTools/FileManip.cpp +34 -2
- data/src/cxx_supportlib/FileTools/FileManip.h +58 -1
- data/src/cxx_supportlib/FileTools/PathManip.cpp +3 -2
- data/src/cxx_supportlib/FileTools/PathSecurityCheck.cpp +99 -0
- data/src/cxx_supportlib/FileTools/PathSecurityCheck.h +69 -0
- data/src/cxx_supportlib/Utils.cpp +37 -6
- data/src/cxx_supportlib/Utils.h +6 -0
- data/src/cxx_supportlib/Utils/AsyncSignalSafeUtils.h +14 -0
- data/src/cxx_supportlib/Utils/IOUtils.cpp +10 -18
- data/src/cxx_supportlib/Utils/IOUtils.h +10 -9
- data/src/cxx_supportlib/Utils/JsonUtils.h +12 -8
- data/src/cxx_supportlib/Utils/SystemMetricsCollector.h +4 -4
- data/src/cxx_supportlib/Utils/SystemTime.h +1 -1
- data/src/cxx_supportlib/WebSocketCommandReverseServer.h +3 -3
- data/src/cxx_supportlib/oxt/system_calls.cpp +25 -1
- data/src/cxx_supportlib/oxt/system_calls.hpp +3 -1
- data/src/helper-scripts/meteor-loader.rb +115 -28
- data/src/helper-scripts/rack-preloader.rb +1 -1
- data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +4 -4
- data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +4 -4
- data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +0 -10
- data/src/nginx_module/LocationConfig/AutoGeneratedHeaderSerialization.c +0 -42
- data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +0 -6
- data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +0 -8
- data/src/nginx_module/MainConfig/AutoGeneratedCreateFunction.c +10 -0
- data/src/nginx_module/MainConfig/AutoGeneratedManifestGeneration.c +22 -0
- data/src/nginx_module/MainConfig/AutoGeneratedStruct.h +8 -0
- data/src/nginx_module/ngx_http_passenger_module.c +6 -5
- data/src/ruby_supportlib/phusion_passenger.rb +1 -1
- data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +0 -1
- data/src/ruby_supportlib/phusion_passenger/common_library.rb +3 -0
- data/src/ruby_supportlib/phusion_passenger/config/installation_utils.rb +3 -3
- data/src/ruby_supportlib/phusion_passenger/constants.rb +5 -0
- data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +4 -2
- data/src/ruby_supportlib/phusion_passenger/platform_info.rb +3 -3
- data/src/ruby_supportlib/phusion_passenger/request_handler.rb +1 -1
- data/src/ruby_supportlib/phusion_passenger/vendor/daemon_controller.rb +1 -1
- metadata +4 -2
@@ -201,6 +201,9 @@ switchGroup(uid_t uid, const struct passwd *userInfo, gid_t gid) {
|
|
201
201
|
if (ngroups <= NGROUPS_MAX) {
|
202
202
|
setgroupsCalled = true;
|
203
203
|
gidset.reset(new gid_t[ngroups]);
|
204
|
+
for (int i = 0; i < ngroups; i++) {
|
205
|
+
gidset[i] = groups[i];
|
206
|
+
}
|
204
207
|
if (setgroups(ngroups, gidset.get()) == -1) {
|
205
208
|
int e = errno;
|
206
209
|
fprintf(stderr, "ERROR: setgroups(%d, ...) failed: %s (errno=%d)\n",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
* Copyright (c) 2015-
|
3
|
+
* Copyright (c) 2015-2018 Phusion Holding B.V.
|
4
4
|
*
|
5
5
|
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
6
|
* trademarks of Phusion Holding B.V.
|
@@ -262,7 +262,7 @@ struct ApiAccount {
|
|
262
262
|
if (doc.isMember("password")) {
|
263
263
|
password = doc["password"].asString();
|
264
264
|
} else {
|
265
|
-
password = strip(
|
265
|
+
password = strip(unsafeReadFile(doc["password_file"].asString()));
|
266
266
|
}
|
267
267
|
readonly = doc["level"].asString() == "readonly";
|
268
268
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
* Copyright (c) 2012-
|
3
|
+
* Copyright (c) 2012-2018 Phusion Holding B.V.
|
4
4
|
*
|
5
5
|
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
6
|
* trademarks of Phusion Holding B.V.
|
@@ -63,8 +63,8 @@
|
|
63
63
|
#include <FileTools/FileManip.h>
|
64
64
|
#include <FileTools/PathManip.h>
|
65
65
|
#include <Utils.h>
|
66
|
-
#include <Utils/IOUtils.h>
|
67
66
|
#include <Utils/StrIntUtils.h>
|
67
|
+
#include <Core/SpawningKit/Handshake/WorkDir.h>
|
68
68
|
#include <Core/SpawningKit/Exceptions.h>
|
69
69
|
|
70
70
|
using namespace std;
|
@@ -100,7 +100,7 @@ static Json::Value
|
|
100
100
|
readArgsJson(const string &workDir) {
|
101
101
|
Json::Reader reader;
|
102
102
|
Json::Value result;
|
103
|
-
string contents =
|
103
|
+
string contents = unsafeReadFile(workDir + "/args.json");
|
104
104
|
if (reader.parse(contents, result)) {
|
105
105
|
return result;
|
106
106
|
} else {
|
@@ -359,7 +359,7 @@ lookupUserGroup(const Context &context, uid_t *uid, struct passwd **userInfo,
|
|
359
359
|
}
|
360
360
|
}
|
361
361
|
|
362
|
-
void
|
362
|
+
static void
|
363
363
|
chownNewWorkDirFiles(const Context &context, uid_t uid, gid_t gid) {
|
364
364
|
chown((context.workDir + "/response/steps/subprocess_before_first_exec/state").c_str(),
|
365
365
|
uid, gid);
|
@@ -377,6 +377,11 @@ chownNewWorkDirFiles(const Context &context, uid_t uid, gid_t gid) {
|
|
377
377
|
uid, gid);
|
378
378
|
}
|
379
379
|
|
380
|
+
static void
|
381
|
+
finalizeWorkDir(const Context &context, uid_t uid, gid_t gid) {
|
382
|
+
SpawningKit::HandshakeWorkDir::finalize(context.workDir, uid, gid);
|
383
|
+
}
|
384
|
+
|
380
385
|
static void
|
381
386
|
enterLveJail(const Context &context, const struct passwd *userInfo) {
|
382
387
|
string lveInitErr;
|
@@ -873,6 +878,8 @@ spawnEnvSetupperMain(int argc, char *argv[]) {
|
|
873
878
|
lookupUserGroup(context, &uid, &userInfo, &gid);
|
874
879
|
shell = userInfo->pw_shell;
|
875
880
|
} else {
|
881
|
+
uid = geteuid();
|
882
|
+
gid = getegid();
|
876
883
|
shell = lookupCurrentUserShell();
|
877
884
|
}
|
878
885
|
if (setUlimits(context.args)) {
|
@@ -880,6 +887,7 @@ spawnEnvSetupperMain(int argc, char *argv[]) {
|
|
880
887
|
}
|
881
888
|
if (shouldTrySwitchUser) {
|
882
889
|
chownNewWorkDirFiles(context, uid, gid);
|
890
|
+
finalizeWorkDir(context, uid, gid);
|
883
891
|
|
884
892
|
enterLveJail(context, userInfo);
|
885
893
|
switchGroup(context, uid, userInfo, gid);
|
@@ -888,6 +896,8 @@ spawnEnvSetupperMain(int argc, char *argv[]) {
|
|
888
896
|
switchUser(context, uid, userInfo);
|
889
897
|
dumpEnvvars(context.workDir);
|
890
898
|
dumpUserInfo(context.workDir);
|
899
|
+
} else {
|
900
|
+
finalizeWorkDir(context, uid, gid);
|
891
901
|
}
|
892
902
|
} else if (executedThroughShell(context)) {
|
893
903
|
recordJourneyStepEnd(context, SpawningKit::SUBPROCESS_OS_SHELL,
|
data/src/agent/Watchdog/Config.h
CHANGED
@@ -132,6 +132,7 @@ using namespace std;
|
|
132
132
|
* integration_mode string - default("standalone")
|
133
133
|
* log_level string - default("notice")
|
134
134
|
* log_target any - default({"stderr": true})
|
135
|
+
* max_instances_per_app unsigned integer - read_only
|
135
136
|
* max_pool_size unsigned integer - default(6)
|
136
137
|
* multi_app boolean - default(false),read_only
|
137
138
|
* passenger_root string required read_only
|
@@ -145,7 +146,7 @@ using namespace std;
|
|
145
146
|
* security_update_checker_interval unsigned integer - default(86400)
|
146
147
|
* security_update_checker_proxy_url string - -
|
147
148
|
* security_update_checker_url string - default("https://securitycheck.phusionpassenger.com/v1/check.json")
|
148
|
-
* server_software string - default("Phusion_Passenger/5.3.
|
149
|
+
* server_software string - default("Phusion_Passenger/5.3.2")
|
149
150
|
* setsid boolean - default(false)
|
150
151
|
* show_version_in_header boolean - default(true)
|
151
152
|
* single_app_mode_app_root string - default,read_only
|
@@ -71,6 +71,7 @@
|
|
71
71
|
#include <Constants.h>
|
72
72
|
#include <InstanceDirectory.h>
|
73
73
|
#include <FileDescriptor.h>
|
74
|
+
#include <FileTools/PathSecurityCheck.h>
|
74
75
|
#include <RandomGenerator.h>
|
75
76
|
#include <BackgroundEventLoop.h>
|
76
77
|
#include <LoggingKit/LoggingKit.h>
|
@@ -977,6 +978,39 @@ lookupDefaultUidGid(uid_t &uid, gid_t &gid) {
|
|
977
978
|
}
|
978
979
|
}
|
979
980
|
|
981
|
+
static void
|
982
|
+
warnIfInstanceDirVulnerable(const string &root) {
|
983
|
+
TRACE_POINT();
|
984
|
+
|
985
|
+
if (geteuid() != 0) {
|
986
|
+
return; // Passenger is not root, so no escalation.
|
987
|
+
}
|
988
|
+
|
989
|
+
vector<string> errors, checkErrors;
|
990
|
+
if (isPathProbablySecureForRootUse(root, errors, checkErrors)) {
|
991
|
+
if (!checkErrors.empty()) {
|
992
|
+
string message = "WARNING: unable to perform privilege escalation vulnerability detection:\n";
|
993
|
+
foreach (string line, checkErrors) {
|
994
|
+
message.append("\n - " + line);
|
995
|
+
}
|
996
|
+
P_WARN(message);
|
997
|
+
}
|
998
|
+
} else {
|
999
|
+
string message = "WARNING: potential privilege escalation vulnerability detected. " \
|
1000
|
+
PROGRAM_NAME " is running as root, and part(s) of the " SHORT_PROGRAM_NAME
|
1001
|
+
" instance directory (" + root + ") can be changed by non-root user(s):\n";
|
1002
|
+
foreach (string line, errors) {
|
1003
|
+
message.append("\n - " + line);
|
1004
|
+
}
|
1005
|
+
foreach (string line, checkErrors) {
|
1006
|
+
message.append("\n - " + line);
|
1007
|
+
}
|
1008
|
+
message.append("\n\nPlease either fix up the permissions for the insecure paths, or use" \
|
1009
|
+
" a different location for the instance dir that can only be modified by root.");
|
1010
|
+
P_WARN(message);
|
1011
|
+
}
|
1012
|
+
}
|
1013
|
+
|
980
1014
|
static void
|
981
1015
|
initializeWorkingObjects(const WorkingObjectsPtr &wo, InstanceDirToucherPtr &instanceDirToucher,
|
982
1016
|
uid_t uidBeforeLoweringPrivilege)
|
@@ -1005,6 +1039,10 @@ initializeWorkingObjects(const WorkingObjectsPtr &wo, InstanceDirToucherPtr &ins
|
|
1005
1039
|
if (watchdogConfig->get("integration_mode").asString() == "standalone") {
|
1006
1040
|
instanceOptions.properties["standalone_engine"] = watchdogConfig->get("standalone_engine").asString();
|
1007
1041
|
}
|
1042
|
+
|
1043
|
+
// check if path is safe
|
1044
|
+
warnIfInstanceDirVulnerable(watchdogConfig->get("instance_registry_dir").asString());
|
1045
|
+
|
1008
1046
|
wo->instanceDir = boost::make_shared<InstanceDirectory>(instanceOptions,
|
1009
1047
|
watchdogConfig->get("instance_registry_dir").asString());
|
1010
1048
|
wo->extraConfigToPassToSubAgents["instance_dir"] = wo->instanceDir->getPath();
|
@@ -1326,6 +1326,7 @@ public:
|
|
1326
1326
|
config["show_version_in_header"] = serverConfig.showVersionInHeader;
|
1327
1327
|
config["max_pool_size"] = serverConfig.maxPoolSize;
|
1328
1328
|
config["pool_idle_time"] = serverConfig.poolIdleTime;
|
1329
|
+
config["max_instances_per_app"] = serverConfig.maxInstancesPerApp;
|
1329
1330
|
config["response_buffer_high_watermark"] = serverConfig.responseBufferHighWatermark;
|
1330
1331
|
config["stat_throttle_rate"] = serverConfig.statThrottleRate;
|
1331
1332
|
config["turbocaching"] = serverConfig.turbocaching;
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
At the time of writing (25 Feb 2017), ConfigKit was just introduced, so these practices and patterns aren't yet used everywhere, but the long-term plan is to adopt these practices/patterns throughout the entire codebase.
|
6
6
|
|
7
|
-
<!-- MarkdownTOC
|
7
|
+
<!-- MarkdownTOC levels="1,2,3,4" autolink="true" bracket="round" -->
|
8
8
|
|
9
9
|
- [The "component" pattern](#the-component-pattern)
|
10
10
|
- [Components are composable](#components-are-composable)
|
@@ -4,7 +4,7 @@ ConfigKit is a configuration management system that lets you define configuratio
|
|
4
4
|
|
5
5
|
**Table of contents:**
|
6
6
|
|
7
|
-
<!-- MarkdownTOC
|
7
|
+
<!-- MarkdownTOC levels="1,2,3,4" autolink="true" bracket="round" -->
|
8
8
|
|
9
9
|
- [Motivations](#motivations)
|
10
10
|
- [Configuration flow from high-level to low-level with a minimum of repeated code](#configuration-flow-from-high-level-to-low-level-with-a-minimum-of-repeated-code)
|
@@ -81,7 +81,7 @@
|
|
81
81
|
#define PASSENGER_API_VERSION_MAJOR 0
|
82
82
|
#define PASSENGER_API_VERSION_MINOR 3
|
83
83
|
#define PASSENGER_DEFAULT_USER "nobody"
|
84
|
-
#define PASSENGER_VERSION "5.3.
|
84
|
+
#define PASSENGER_VERSION "5.3.2"
|
85
85
|
#define POOL_HELPER_THREAD_STACK_SIZE 262144
|
86
86
|
#define PROCESS_SHUTDOWN_TIMEOUT 60
|
87
87
|
#define PROCESS_SHUTDOWN_TIMEOUT_DISPLAY "1 minute"
|
@@ -98,6 +98,11 @@
|
|
98
98
|
#define SERVER_KIT_MAX_SERVER_ENDPOINTS 4
|
99
99
|
#define SERVER_TOKEN_NAME "Phusion_Passenger"
|
100
100
|
#define SHORT_PROGRAM_NAME "Passenger"
|
101
|
+
#define SPAWNINGKIT_MAX_ERROR_CATEGORY_SIZE 32
|
102
|
+
#define SPAWNINGKIT_MAX_JOURNEY_STEP_FILE_SIZE 32
|
103
|
+
#define SPAWNINGKIT_MAX_PROPERTIES_JSON_SIZE 32768
|
104
|
+
#define SPAWNINGKIT_MAX_SUBPROCESS_ENVDUMP_SIZE 131072
|
105
|
+
#define SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE 131072
|
101
106
|
#define SUPPORT_URL "https://www.phusionpassenger.com/support"
|
102
107
|
#define USER_NAMESPACE_DIRNAME ".passenger"
|
103
108
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
* Copyright (c) 2010-
|
3
|
+
* Copyright (c) 2010-2018 Phusion Holding B.V.
|
4
4
|
*
|
5
5
|
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
6
|
* trademarks of Phusion Holding B.V.
|
@@ -34,6 +34,7 @@
|
|
34
34
|
|
35
35
|
#include <cstdio>
|
36
36
|
#include <cerrno>
|
37
|
+
#include <limits>
|
37
38
|
|
38
39
|
#include <FileTools/FileManip.h>
|
39
40
|
#include <FileDescriptor.h>
|
@@ -44,6 +45,7 @@
|
|
44
45
|
#include <Utils.h> // parseModeString
|
45
46
|
#include <Utils/CachedFileStat.hpp>
|
46
47
|
#include <Utils/IOUtils.h>
|
48
|
+
#include <Utils/ScopeGuard.h>
|
47
49
|
|
48
50
|
namespace Passenger {
|
49
51
|
|
@@ -181,6 +183,36 @@ createFile(const string &filename, const StaticString &contents, mode_t permissi
|
|
181
183
|
}
|
182
184
|
}
|
183
185
|
|
186
|
+
string
|
187
|
+
unsafeReadFile(const string &path) {
|
188
|
+
int fd = open(path.c_str(), O_RDONLY);
|
189
|
+
if (fd != -1) {
|
190
|
+
FdGuard guard(fd, __FILE__, __LINE__);
|
191
|
+
return readAll(fd, std::numeric_limits<size_t>::max()).first;
|
192
|
+
} else {
|
193
|
+
int e = errno;
|
194
|
+
throw FileSystemException("Cannot open '" + path + "' for reading",
|
195
|
+
e, path);
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
pair<string, bool>
|
200
|
+
safeReadFile(int dirfd, const string &basename, size_t maxSize) {
|
201
|
+
if (basename.find('/') != string::npos) {
|
202
|
+
throw ArgumentException("basename may not contain slashes");
|
203
|
+
}
|
204
|
+
|
205
|
+
int fd = openat(dirfd, basename.c_str(), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
206
|
+
if (fd != -1) {
|
207
|
+
FdGuard guard(fd, __FILE__, __LINE__);
|
208
|
+
return readAll(fd, maxSize);
|
209
|
+
} else {
|
210
|
+
int e = errno;
|
211
|
+
throw FileSystemException("Cannot open '" + basename + "' for reading",
|
212
|
+
e, basename);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
184
216
|
void
|
185
217
|
makeDirTree(const string &path, const StaticString &mode, uid_t owner, gid_t group) {
|
186
218
|
struct stat buf;
|
@@ -240,7 +272,7 @@ makeDirTree(const string &path, const StaticString &mode, uid_t owner, gid_t gro
|
|
240
272
|
group = (gid_t) -1; // Don't let chown change file group.
|
241
273
|
}
|
242
274
|
do {
|
243
|
-
ret =
|
275
|
+
ret = lchown(current.c_str(), owner, group);
|
244
276
|
} while (ret == -1 && errno == EINTR);
|
245
277
|
if (ret == -1) {
|
246
278
|
char message[1024];
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
* Copyright (c) 2010-
|
3
|
+
* Copyright (c) 2010-2018 Phusion Holding B.V.
|
4
4
|
*
|
5
5
|
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
6
|
* trademarks of Phusion Holding B.V.
|
@@ -31,6 +31,7 @@
|
|
31
31
|
#include <unistd.h>
|
32
32
|
#include <cstddef>
|
33
33
|
#include <string>
|
34
|
+
#include <utility>
|
34
35
|
#include <StaticString.h>
|
35
36
|
|
36
37
|
namespace boost {
|
@@ -131,6 +132,62 @@ void createFile(const string &filename, const StaticString &contents,
|
|
131
132
|
bool overwrite = true,
|
132
133
|
const char *callerFile = NULL, unsigned int callerLine = 0);
|
133
134
|
|
135
|
+
/**
|
136
|
+
* Read all data from the given file until EOF.
|
137
|
+
* This function is "unsafe" in the sense that it lacks the security
|
138
|
+
* checks implemented by `safeReadFile()`. Read the docs for that function
|
139
|
+
* for more information.
|
140
|
+
*
|
141
|
+
* @throws SystemException
|
142
|
+
*/
|
143
|
+
string unsafeReadFile(const string &path);
|
144
|
+
|
145
|
+
/**
|
146
|
+
* Read all data from the given file until EOF.
|
147
|
+
*
|
148
|
+
* - `dirfd` is a file descriptor of the directory that contains the file
|
149
|
+
* you want read from.
|
150
|
+
* - `basename` is the basename of the file you want to read from. `basename`
|
151
|
+
* may thus not contain slashes.
|
152
|
+
* - `maxSize` is the maximum number of bytes you want to read.
|
153
|
+
*
|
154
|
+
* Returns a pair `(contents, eof)`.
|
155
|
+
*
|
156
|
+
* - `contents` is the read file contents, which is at most `maxSize` bytes.
|
157
|
+
* - `eof` indicates whether the entire file has been read. If false, then it
|
158
|
+
* means the amount of data is larger than `maxSize`.
|
159
|
+
*
|
160
|
+
* This function is "safe" in following sense:
|
161
|
+
*
|
162
|
+
* - It mitigates symbolic link attacks. `open(path)` may not be safe for
|
163
|
+
* processes running as root, because if a user controls any parts of the
|
164
|
+
* path then the user can swap one of the parent directories, or the file
|
165
|
+
* itself, with a symlink. This causes us to read an arbitrary file.
|
166
|
+
*
|
167
|
+
* This function mitigates this attack by requiring a `dirfd` and by opening
|
168
|
+
* the file with O_NOFOLLOW. The caller must ensure that `dirfd` is created
|
169
|
+
* at a time when it knows that no user controls any parts of the path to
|
170
|
+
* that directory.
|
171
|
+
*
|
172
|
+
* However, this mitigation does mean that safeReadFile() *cannot be used to
|
173
|
+
* read from a symlink*!
|
174
|
+
*
|
175
|
+
* - It mitigates DoS attacks through non-regular files which may block the
|
176
|
+
* reader, like FIFOs or block devices. For example if the path refers to a
|
177
|
+
* FIFO which a user created, and the user never opens the FIFO on the other
|
178
|
+
* side, then the open can block indefinitely.
|
179
|
+
*
|
180
|
+
* This function mitigates this attack by opening the file with O_NONBLOCK.
|
181
|
+
*
|
182
|
+
* - It mitigates DoS attacks by creating a very large file. Since we slurp
|
183
|
+
* the entire file into memory, it is a good idea if we impose a limit
|
184
|
+
* on how much data we read.
|
185
|
+
*
|
186
|
+
* @throws ArgumentException
|
187
|
+
* @throws SystemException
|
188
|
+
*/
|
189
|
+
pair<string, bool> safeReadFile(int dirfd, const string &basename, size_t maxSize);
|
190
|
+
|
134
191
|
/**
|
135
192
|
* Create the directory at the given path, creating intermediate directories
|
136
193
|
* if necessary. The created directories' permissions are exactly as specified
|
@@ -84,11 +84,12 @@ absolutizePath(const StaticString &path, const StaticString &workingDir) {
|
|
84
84
|
vector<string> components;
|
85
85
|
if (!startsWith(path, "/")) {
|
86
86
|
if (workingDir.empty()) {
|
87
|
-
char buffer[PATH_MAX];
|
88
|
-
if (getcwd(buffer,
|
87
|
+
char buffer[PATH_MAX + 1];
|
88
|
+
if (getcwd(buffer, PATH_MAX) == NULL) {
|
89
89
|
int e = errno;
|
90
90
|
throw SystemException("Unable to query current working directory", e);
|
91
91
|
}
|
92
|
+
buffer[PATH_MAX] = '\0';
|
92
93
|
split(buffer + 1, '/', components);
|
93
94
|
} else {
|
94
95
|
string absoluteWorkingDir = absolutizePath(workingDir);
|
@@ -0,0 +1,99 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2018 Phusion Holding B.V.
|
4
|
+
*
|
5
|
+
* "Passenger", "Phusion Passenger" and "Union Station" are registered
|
6
|
+
* trademarks of Phusion Holding B.V.
|
7
|
+
*
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
13
|
+
* furnished to do so, subject to the following conditions:
|
14
|
+
*
|
15
|
+
* The above copyright notice and this permission notice shall be included in
|
16
|
+
* all copies or substantial portions of the Software.
|
17
|
+
*
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
* THE SOFTWARE.
|
25
|
+
*/
|
26
|
+
|
27
|
+
#include <sys/stat.h>
|
28
|
+
#include <unistd.h>
|
29
|
+
|
30
|
+
#include <cerrno>
|
31
|
+
|
32
|
+
#include <FileTools/PathSecurityCheck.h>
|
33
|
+
#include <FileTools/PathManip.h>
|
34
|
+
#include <Utils.h>
|
35
|
+
#include <Utils/StrIntUtils.h>
|
36
|
+
|
37
|
+
namespace Passenger {
|
38
|
+
|
39
|
+
|
40
|
+
static bool
|
41
|
+
isSinglePathProbablySecureForRootUse(const string &path,
|
42
|
+
vector<string> &errors, vector<string> &checkErrors)
|
43
|
+
{
|
44
|
+
struct stat s;
|
45
|
+
int ret;
|
46
|
+
|
47
|
+
do {
|
48
|
+
ret = stat(path.c_str(), &s);
|
49
|
+
} while (ret == -1 && errno == EAGAIN);
|
50
|
+
if (ret == -1) {
|
51
|
+
int e = errno;
|
52
|
+
checkErrors.push_back("Security check skipped on " + path
|
53
|
+
+ ": stat() failed: " + strerror(e) + " (errno="
|
54
|
+
+ toString(e) + ")");
|
55
|
+
return true;
|
56
|
+
}
|
57
|
+
|
58
|
+
if (s.st_uid != 0) {
|
59
|
+
errors.push_back(path + " is not secure: it can be modified by user "
|
60
|
+
+ getUserName(s.st_uid));
|
61
|
+
return false;
|
62
|
+
}
|
63
|
+
|
64
|
+
if (s.st_mode & S_ISVTX) {
|
65
|
+
return true;
|
66
|
+
}
|
67
|
+
|
68
|
+
if (s.st_mode & S_IWGRP) {
|
69
|
+
errors.push_back(path + " is not secure: it can be modified by group "
|
70
|
+
+ getGroupName(s.st_gid));
|
71
|
+
return false;
|
72
|
+
}
|
73
|
+
|
74
|
+
if (s.st_mode & S_IWOTH) {
|
75
|
+
errors.push_back(path + " is not secure: it can be modified by anybody");
|
76
|
+
return false;
|
77
|
+
}
|
78
|
+
|
79
|
+
return true;
|
80
|
+
}
|
81
|
+
|
82
|
+
bool
|
83
|
+
isPathProbablySecureForRootUse(const StaticString &path, vector<string> &errors,
|
84
|
+
vector<string> &checkErrors)
|
85
|
+
{
|
86
|
+
string fullPath = absolutizePath(path);
|
87
|
+
bool result = true;
|
88
|
+
|
89
|
+
while (!fullPath.empty() && fullPath != "/") {
|
90
|
+
result = isSinglePathProbablySecureForRootUse(fullPath, errors, checkErrors)
|
91
|
+
&& result;
|
92
|
+
fullPath = extractDirName(fullPath);
|
93
|
+
}
|
94
|
+
|
95
|
+
return result;
|
96
|
+
}
|
97
|
+
|
98
|
+
|
99
|
+
} // namespace Passenger
|