passenger 5.3.1 → 5.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|