passenger 5.3.1 → 5.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +19 -0
  3. data/build/cxx_tests.rb +3 -1
  4. data/build/support/cxx_dependency_map.rb +120 -27
  5. data/dev/configkit-schemas/index.json +15 -3
  6. data/src/agent/Core/AdminPanelConnector.h +5 -2
  7. data/src/agent/Core/ApplicationPool/Group/StateInspection.cpp +2 -0
  8. data/src/agent/Core/Config.h +2 -1
  9. data/src/agent/Core/Controller/Config.h +6 -1
  10. data/src/agent/Core/Controller/InitRequest.cpp +6 -1
  11. data/src/agent/Core/CoreMain.cpp +26 -60
  12. data/src/agent/Core/SpawningKit/DirectSpawner.h +18 -6
  13. data/src/agent/Core/SpawningKit/ErrorRenderer.h +8 -8
  14. data/src/agent/Core/SpawningKit/Handshake/Perform.h +217 -61
  15. data/src/agent/Core/SpawningKit/Handshake/Prepare.h +57 -8
  16. data/src/agent/Core/SpawningKit/Handshake/Session.h +34 -1
  17. data/src/agent/Core/SpawningKit/Handshake/WorkDir.h +20 -4
  18. data/src/agent/Core/SpawningKit/SmartSpawner.h +90 -27
  19. data/src/agent/ExecHelper/ExecHelperMain.cpp +3 -0
  20. data/src/agent/Shared/ApiAccountUtils.h +2 -2
  21. data/src/agent/SpawnEnvSetupper/SpawnEnvSetupperMain.cpp +14 -4
  22. data/src/agent/Watchdog/Config.h +2 -1
  23. data/src/agent/Watchdog/WatchdogMain.cpp +38 -0
  24. data/src/apache2_module/Hooks.cpp +1 -0
  25. data/src/cxx_supportlib/ConfigKit/IN_PRACTICE.md +1 -1
  26. data/src/cxx_supportlib/ConfigKit/README.md +1 -1
  27. data/src/cxx_supportlib/Constants.h +6 -1
  28. data/src/cxx_supportlib/FileTools/FileManip.cpp +34 -2
  29. data/src/cxx_supportlib/FileTools/FileManip.h +58 -1
  30. data/src/cxx_supportlib/FileTools/PathManip.cpp +3 -2
  31. data/src/cxx_supportlib/FileTools/PathSecurityCheck.cpp +99 -0
  32. data/src/cxx_supportlib/FileTools/PathSecurityCheck.h +69 -0
  33. data/src/cxx_supportlib/Utils.cpp +37 -6
  34. data/src/cxx_supportlib/Utils.h +6 -0
  35. data/src/cxx_supportlib/Utils/AsyncSignalSafeUtils.h +14 -0
  36. data/src/cxx_supportlib/Utils/IOUtils.cpp +10 -18
  37. data/src/cxx_supportlib/Utils/IOUtils.h +10 -9
  38. data/src/cxx_supportlib/Utils/JsonUtils.h +12 -8
  39. data/src/cxx_supportlib/Utils/SystemMetricsCollector.h +4 -4
  40. data/src/cxx_supportlib/Utils/SystemTime.h +1 -1
  41. data/src/cxx_supportlib/WebSocketCommandReverseServer.h +3 -3
  42. data/src/cxx_supportlib/oxt/system_calls.cpp +25 -1
  43. data/src/cxx_supportlib/oxt/system_calls.hpp +3 -1
  44. data/src/helper-scripts/meteor-loader.rb +115 -28
  45. data/src/helper-scripts/rack-preloader.rb +1 -1
  46. data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +4 -4
  47. data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +4 -4
  48. data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +0 -10
  49. data/src/nginx_module/LocationConfig/AutoGeneratedHeaderSerialization.c +0 -42
  50. data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +0 -6
  51. data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +0 -8
  52. data/src/nginx_module/MainConfig/AutoGeneratedCreateFunction.c +10 -0
  53. data/src/nginx_module/MainConfig/AutoGeneratedManifestGeneration.c +22 -0
  54. data/src/nginx_module/MainConfig/AutoGeneratedStruct.h +8 -0
  55. data/src/nginx_module/ngx_http_passenger_module.c +6 -5
  56. data/src/ruby_supportlib/phusion_passenger.rb +1 -1
  57. data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +0 -1
  58. data/src/ruby_supportlib/phusion_passenger/common_library.rb +3 -0
  59. data/src/ruby_supportlib/phusion_passenger/config/installation_utils.rb +3 -3
  60. data/src/ruby_supportlib/phusion_passenger/constants.rb +5 -0
  61. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +4 -2
  62. data/src/ruby_supportlib/phusion_passenger/platform_info.rb +3 -3
  63. data/src/ruby_supportlib/phusion_passenger/request_handler.rb +1 -1
  64. data/src/ruby_supportlib/phusion_passenger/vendor/daemon_controller.rb +1 -1
  65. metadata +4 -2
@@ -267,6 +267,10 @@
267
267
  "read_only" : true,
268
268
  "type" : "string"
269
269
  },
270
+ "max_instances_per_app" : {
271
+ "read_only" : true,
272
+ "type" : "unsigned integer"
273
+ },
270
274
  "min_spare_clients" : {
271
275
  "default_value" : 0,
272
276
  "has_default_value" : "static",
@@ -289,7 +293,7 @@
289
293
  "type" : "unsigned integer"
290
294
  },
291
295
  "server_software" : {
292
- "default_value" : "Phusion_Passenger/5.3.1",
296
+ "default_value" : "Phusion_Passenger/5.3.2",
293
297
  "has_default_value" : "static",
294
298
  "type" : "string"
295
299
  },
@@ -724,6 +728,10 @@
724
728
  "has_default_value" : "static",
725
729
  "type" : "any"
726
730
  },
731
+ "max_instances_per_app" : {
732
+ "read_only" : true,
733
+ "type" : "unsigned integer"
734
+ },
727
735
  "max_pool_size" : {
728
736
  "default_value" : 6,
729
737
  "has_default_value" : "static",
@@ -787,7 +795,7 @@
787
795
  "type" : "string"
788
796
  },
789
797
  "server_software" : {
790
- "default_value" : "Phusion_Passenger/5.3.1",
798
+ "default_value" : "Phusion_Passenger/5.3.2",
791
799
  "has_default_value" : "static",
792
800
  "type" : "string"
793
801
  },
@@ -1441,6 +1449,10 @@
1441
1449
  "has_default_value" : "static",
1442
1450
  "type" : "any"
1443
1451
  },
1452
+ "max_instances_per_app" : {
1453
+ "read_only" : true,
1454
+ "type" : "unsigned integer"
1455
+ },
1444
1456
  "max_pool_size" : {
1445
1457
  "default_value" : 6,
1446
1458
  "has_default_value" : "static",
@@ -1505,7 +1517,7 @@
1505
1517
  "type" : "string"
1506
1518
  },
1507
1519
  "server_software" : {
1508
- "default_value" : "Phusion_Passenger/5.3.1",
1520
+ "default_value" : "Phusion_Passenger/5.3.2",
1509
1521
  "has_default_value" : "static",
1510
1522
  "type" : "string"
1511
1523
  },
@@ -35,6 +35,7 @@
35
35
  #include <boost/bind.hpp>
36
36
  #include <boost/foreach.hpp>
37
37
 
38
+ #include <limits>
38
39
  #include <string>
39
40
  #include <vector>
40
41
 
@@ -45,6 +46,7 @@
45
46
  #include <Core/ApplicationPool/Pool.h>
46
47
  #include <Core/Controller.h>
47
48
  #include <ProcessManagement/Ruby.h>
49
+ #include <FileTools/FileManip.h>
48
50
  #include <Utils.h>
49
51
  #include <Utils/StrIntUtils.h>
50
52
  #include <Utils/IOUtils.h>
@@ -486,7 +488,8 @@ private:
486
488
  _exit(1);
487
489
  } else {
488
490
  pipe.second.close();
489
- string out = readAll(pipe.first);
491
+ string out = readAll(pipe.first,
492
+ std::numeric_limits<size_t>::max()).first;
490
493
  LoggingKit::context->saveMonitoredFileLog(key, f.c_str(), f.size(),
491
494
  out.data(), out.size());
492
495
  pipe.first.close();
@@ -572,7 +575,7 @@ private:
572
575
  Json::Value doc;
573
576
  Json::Reader reader;
574
577
 
575
- if (!reader.parse(readAll(instanceDir + "/properties.json"), doc)) {
578
+ if (!reader.parse(unsafeReadFile(instanceDir + "/properties.json"), doc)) {
576
579
  throw RuntimeException("Cannot parse " + instanceDir + "/properties.json: "
577
580
  + reader.getFormattedErrorMessages());
578
581
  }
@@ -70,6 +70,8 @@ Group::processLowerLimitsSatisfied() const {
70
70
  */
71
71
  bool
72
72
  Group::processUpperLimitsReached() const {
73
+ // check maxInstances limit as set by Enterprise (OSS maxInstancesPerApp piggybacks on this,
74
+ // see InitRequest.cpp)
73
75
  return options.maxProcesses != 0 && capacityUsed() >= options.maxProcesses;
74
76
  }
75
77
 
@@ -140,6 +140,7 @@ using namespace std;
140
140
  * integration_mode string - default("standalone")
141
141
  * log_level string - default("notice")
142
142
  * log_target any - default({"stderr": true})
143
+ * max_instances_per_app unsigned integer - read_only
143
144
  * max_pool_size unsigned integer - default(6)
144
145
  * multi_app boolean - default(false),read_only
145
146
  * passenger_root string required read_only
@@ -153,7 +154,7 @@ using namespace std;
153
154
  * security_update_checker_interval unsigned integer - default(86400)
154
155
  * security_update_checker_proxy_url string - -
155
156
  * security_update_checker_url string - default("https://securitycheck.phusionpassenger.com/v1/check.json")
156
- * server_software string - default("Phusion_Passenger/5.3.1")
157
+ * server_software string - default("Phusion_Passenger/5.3.2")
157
158
  * show_version_in_header boolean - default(true)
158
159
  * single_app_mode_app_root string - default,read_only
159
160
  * single_app_mode_app_type string - read_only
@@ -107,11 +107,12 @@ parseControllerBenchmarkMode(const StaticString &mode) {
107
107
  * default_user string - default("nobody")
108
108
  * graceful_exit boolean - default(true)
109
109
  * integration_mode string - default("standalone"),read_only
110
+ * max_instances_per_app unsigned integer - read_only
110
111
  * min_spare_clients unsigned integer - default(0)
111
112
  * multi_app boolean - default(true),read_only
112
113
  * request_freelist_limit unsigned integer - default(1024)
113
114
  * response_buffer_high_watermark unsigned integer - default(134217728)
114
- * server_software string - default("Phusion_Passenger/5.3.1")
115
+ * server_software string - default("Phusion_Passenger/5.3.2")
115
116
  * show_version_in_header boolean - default(true)
116
117
  * start_reading_after_accept boolean - default(true)
117
118
  * stat_throttle_rate unsigned integer - default(10)
@@ -127,6 +128,8 @@ private:
127
128
  void initialize() {
128
129
  using namespace ConfigKit;
129
130
 
131
+ add("max_instances_per_app", UINT_TYPE, OPTIONAL | READ_ONLY);
132
+
130
133
  add("thread_number", UINT_TYPE, REQUIRED | READ_ONLY);
131
134
  add("multi_app", BOOL_TYPE, OPTIONAL | READ_ONLY, true);
132
135
  add("turbocaching", BOOL_TYPE, OPTIONAL | READ_ONLY, true);
@@ -307,6 +310,7 @@ public:
307
310
  unsigned int responseBufferHighWatermark;
308
311
  StaticString integrationMode;
309
312
  StaticString serverLogName;
313
+ unsigned int maxInstancesPerApp;
310
314
  ControllerBenchmarkMode benchmarkMode: 3;
311
315
  bool singleAppMode: 1;
312
316
  bool userSwitching: 1;
@@ -324,6 +328,7 @@ public:
324
328
  responseBufferHighWatermark(config["response_buffer_high_watermark"].asUInt()),
325
329
  integrationMode(psg_pstrdup(pool, config["integration_mode"].asString())),
326
330
  serverLogName(createServerLogName()),
331
+ maxInstancesPerApp(config["max_instances_per_app"].asUInt()),
327
332
  benchmarkMode(parseControllerBenchmarkMode(config["benchmark_mode"].asString())),
328
333
  singleAppMode(!config["multi_app"].asBool()),
329
334
  userSwitching(config["user_switching"].asBool()),
@@ -358,7 +358,6 @@ Controller::createNewPoolOptions(Client *client, Request *req,
358
358
  fillPoolOption(req, options.user, "!~PASSENGER_USER");
359
359
  fillPoolOption(req, options.group, "!~PASSENGER_GROUP");
360
360
  fillPoolOption(req, options.minProcesses, "!~PASSENGER_MIN_PROCESSES");
361
- fillPoolOption(req, options.maxProcesses, "!~PASSENGER_MAX_PROCESSES");
362
361
  fillPoolOption(req, options.spawnMethod, "!~PASSENGER_SPAWN_METHOD");
363
362
  fillPoolOption(req, options.startCommand, "!~PASSENGER_START_COMMAND");
364
363
  fillPoolOptionSecToMsec(req, options.startTimeout, "!~PASSENGER_START_TIMEOUT");
@@ -372,6 +371,12 @@ Controller::createNewPoolOptions(Client *client, Request *req,
372
371
  fillPoolOption(req, options.fileDescriptorUlimit, "!~PASSENGER_APP_FILE_DESCRIPTOR_ULIMIT");
373
372
  fillPoolOption(req, options.raiseInternalError, "!~PASSENGER_RAISE_INTERNAL_ERROR");
374
373
  fillPoolOption(req, options.lveMinUid, "!~PASSENGER_LVE_MIN_UID");
374
+
375
+ // maxProcesses is configured per-application by the (Enterprise) maxInstances option (and thus passed
376
+ // via request headers). In OSS the max processes can also be configured, but on a global level
377
+ // (i.e. the same for all apps) using the maxInstancesPerApp option. As an easy implementation shortcut
378
+ // we apply maxInstancesPerApp to options.maxProcesses (which can be overridden by Enterprise).
379
+ options.maxProcesses = mainConfig.maxInstancesPerApp;
375
380
  /******************/
376
381
 
377
382
  boost::shared_ptr<Options> optionsCopy = boost::make_shared<Options>(options);
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2010-2017 Phusion Holding B.V.
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.
@@ -38,6 +38,7 @@
38
38
 
39
39
  #include <boost/config.hpp>
40
40
  #include <boost/scoped_ptr.hpp>
41
+ #include <boost/foreach.hpp>
41
42
  #include <sys/types.h>
42
43
  #include <sys/socket.h>
43
44
  #include <sys/stat.h>
@@ -86,10 +87,10 @@
86
87
  #include <ResourceLocator.h>
87
88
  #include <BackgroundEventLoop.cpp>
88
89
  #include <FileTools/FileManip.h>
90
+ #include <FileTools/PathSecurityCheck.h>
89
91
  #include <Exceptions.h>
90
92
  #include <Utils.h>
91
93
  #include <Utils/Timer.h>
92
- #include <Utils/IOUtils.h>
93
94
  #include <Utils/MessageIO.h>
94
95
  #include <Utils/VariantMap.h>
95
96
  #include <Core/OptionParser.h>
@@ -239,7 +240,7 @@ initializePrivilegedWorkingObjects() {
239
240
  if (password.isString()) {
240
241
  wo->controllerSecureHeadersPassword = password.asString();
241
242
  } else if (password.isObject()) {
242
- wo->controllerSecureHeadersPassword = strip(readAll(password["path"].asString()));
243
+ wo->controllerSecureHeadersPassword = strip(unsafeReadFile(password["path"].asString()));
243
244
  }
244
245
  }
245
246
 
@@ -885,50 +886,6 @@ prestartWebApps() {
885
886
  );
886
887
  }
887
888
 
888
- /**
889
- * See warnIfPassengerRootVulnerable()
890
- */
891
- static void
892
- warnIfPathVulnerable(const char *path, string &warnings) {
893
- struct stat pathStat;
894
-
895
- if (stat(path, &pathStat) == -1) {
896
- P_DEBUG("Vulnerability check skipped: stat error on " << path << " (errno: " << errno << ")");
897
- return; // fatal: we need that stat for both checks below
898
- }
899
-
900
- // Non-root ownership
901
- struct passwd pathOwner;
902
- struct passwd *pwdResult;
903
-
904
- boost::shared_array<char> strings;
905
- long stringsBufSize = std::max<long>(1024 * 128, sysconf(_SC_GETPW_R_SIZE_MAX));
906
- strings.reset(new char[stringsBufSize]);
907
- errno = 0;
908
- if (getpwuid_r(pathStat.st_uid, &pathOwner, strings.get(), stringsBufSize, &pwdResult) == -1) {
909
- P_DEBUG("Vulnerability check (owner) skipped: getpwuid_r error on " << path << " (owner UID: " <<
910
- pathStat.st_uid << ", errno: " << errno << ")");
911
- } else if (pwdResult == NULL) {
912
- P_DEBUG("Vulnerability check (owner) skipped: getpwuid_r empty on " << path << " (owner UID: " <<
913
- pathStat.st_uid << ", errno: " << errno << ")");
914
- } else if (pathOwner.pw_uid != 0) {
915
- warnings.append("\nThe path \"");
916
- warnings.append(path);
917
- warnings.append("\" can be modified by user \"");
918
- warnings.append(pathOwner.pw_name);
919
- warnings.append("\" (or applications running as that user)."
920
- " Change the owner of the path to root, or avoid running " SHORT_PROGRAM_NAME " as root.");
921
- }
922
-
923
- // World writeable access rights
924
- if ((pathStat.st_mode & S_IWOTH) != 0) {
925
- warnings.append("\nThe path \"");
926
- warnings.append(path);
927
- warnings.append("\" is writeable by any user (or application)."
928
- " Limit write access on the path to only the root user/group.");
929
- }
930
- }
931
-
932
889
  /*
933
890
  * Emit a warning (log) if the Passenger root dir (and/or its parents) can be modified by non-root users
934
891
  * while Passenger was run as root (because non-root users can then tamper with something running as root).
@@ -948,19 +905,28 @@ warnIfPassengerRootVulnerable() {
948
905
  }
949
906
 
950
907
  string root = workingObjects->resourceLocator.getInstallSpec();
951
- string checkPath = absolutizePath(root);
952
- // Check the Passenger root and all dirs above it for ownership and world-writeability
953
- string warnings;
954
- while (!checkPath.empty() && checkPath != "/") {
955
- warnIfPathVulnerable(checkPath.c_str(), warnings);
956
-
957
- checkPath = extractDirName(checkPath);
958
- }
959
- if (!warnings.empty()) {
960
- P_WARN("WARNING: potential privilege escalation vulnerability. "
961
- PROGRAM_NAME " is running as root, and part(s) of the "
962
- SHORT_PROGRAM_NAME " root path (" << root
963
- << ") can be changed by non-root user(s):" << warnings);
908
+ vector<string> errors, checkErrors;
909
+ if (isPathProbablySecureForRootUse(root, errors, checkErrors)) {
910
+ if (!checkErrors.empty()) {
911
+ string message = "WARNING: unable to perform privilege escalation vulnerability detection:\n";
912
+ foreach (string line, checkErrors) {
913
+ message.append("\n - " + line);
914
+ }
915
+ P_WARN(message);
916
+ }
917
+ } else {
918
+ string message = "WARNING: potential privilege escalation vulnerability detected. " \
919
+ PROGRAM_NAME " is running as root, and part(s) of the " SHORT_PROGRAM_NAME
920
+ " root path (" + root + ") can be changed by non-root user(s):\n";
921
+ foreach (string line, errors) {
922
+ message.append("\n - " + line);
923
+ }
924
+ foreach (string line, checkErrors) {
925
+ message.append("\n - " + line);
926
+ }
927
+ message.append("\n\nPlease either fix up the permissions for the insecure paths, or install "
928
+ SHORT_PROGRAM_NAME " in a different location that can only be modified by root.");
929
+ P_WARN(message);
964
930
  }
965
931
  }
966
932
 
@@ -37,9 +37,11 @@
37
37
  #include <LoggingKit/LoggingKit.h>
38
38
  #include <LveLoggingDecorator.h>
39
39
  #include <Utils/IOUtils.h>
40
+ #include <Utils/AsyncSignalSafeUtils.h>
40
41
 
41
42
  #include <limits.h> // for PTHREAD_STACK_MIN
42
43
  #include <pthread.h>
44
+ #include <unistd.h>
43
45
  #include <adhoc_lve.h>
44
46
 
45
47
  namespace Passenger {
@@ -150,8 +152,11 @@ private:
150
152
 
151
153
  pid_t pid = syscalls::fork();
152
154
  if (pid == 0) {
153
- purgeStdio(stdout);
154
- purgeStdio(stderr);
155
+ int e;
156
+ char buf[1024];
157
+ const char *end = buf + sizeof(buf);
158
+ namespace ASSU = AsyncSignalSafeUtils;
159
+
155
160
  resetSignalHandlersAndMask();
156
161
  disableMallocDebugging();
157
162
  int stdinCopy = dup2(stdinChannel.first, 3);
@@ -160,6 +165,7 @@ private:
160
165
  dup2(stdoutAndErrCopy, 1);
161
166
  dup2(stdoutAndErrCopy, 2);
162
167
  closeAllFileDescriptors(2, true);
168
+
163
169
  execlp(agentFilename.c_str(),
164
170
  agentFilename.c_str(),
165
171
  "spawn-env-setupper",
@@ -167,10 +173,16 @@ private:
167
173
  "--before",
168
174
  NULL);
169
175
 
170
- int e = errno;
171
- fprintf(stderr, "Cannot execute \"%s\": %s (errno=%d)\n",
172
- agentFilename.c_str(), strerror(e), e);
173
- fflush(stderr);
176
+ char *pos = buf;
177
+ e = errno;
178
+ pos = ASSU::appendData(pos, end, "Cannot execute \"");
179
+ pos = ASSU::appendData(pos, end, agentFilename.data(), agentFilename.size());
180
+ pos = ASSU::appendData(pos, end, "\": ");
181
+ pos = ASSU::appendData(pos, end, ASSU::limitedStrerror(e));
182
+ pos = ASSU::appendData(pos, end, " (errno=");
183
+ pos = ASSU::appendInteger<int, 10>(pos, end, e);
184
+ pos = ASSU::appendData(pos, end, ")\n");
185
+ ASSU::printError(buf, pos - buf);
174
186
  _exit(1);
175
187
 
176
188
  } else if (pid == -1) {
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2014-2017 Phusion Holding B.V.
3
+ * Copyright (c) 2014-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,8 +34,8 @@
34
34
 
35
35
  #include <Constants.h>
36
36
  #include <StaticString.h>
37
+ #include <FileTools/FileManip.h>
37
38
  #include <Utils/Template.h>
38
- #include <Utils/IOUtils.h>
39
39
  #include <Core/SpawningKit/Context.h>
40
40
  #include <Core/SpawningKit/Exceptions.h>
41
41
 
@@ -61,8 +61,8 @@ public:
61
61
  string htmlFile = templatesDir + "/with_details/src/index.html.template";
62
62
  string cssFile = templatesDir + "/with_details/dist/styles.css";
63
63
  string jsFile = templatesDir + "/with_details/dist/bundle.js";
64
- string cssContent = readAll(cssFile);
65
- string jsContent = readAll(jsFile);
64
+ string cssContent = unsafeReadFile(cssFile);
65
+ string jsContent = unsafeReadFile(jsFile);
66
66
 
67
67
  Json::Value spec;
68
68
  spec["program_name"] = PROGRAM_NAME;
@@ -85,7 +85,7 @@ public:
85
85
  params.set("TITLE", "Web application could not be started");
86
86
  params.set("SPEC", specContent);
87
87
 
88
- return Template::apply(readAll(htmlFile), params);
88
+ return Template::apply(unsafeReadFile(htmlFile), params);
89
89
  }
90
90
 
91
91
  string renderWithoutDetails(const SpawningKit::SpawnException &e) const {
@@ -93,8 +93,8 @@ public:
93
93
  string htmlFile = templatesDir + "/without_details/src/index.html.template";
94
94
  string cssFile = templatesDir + "/without_details/dist/styles.css";
95
95
  string jsFile = templatesDir + "/without_details/dist/bundle.js";
96
- string cssContent = readAll(cssFile);
97
- string jsContent = readAll(jsFile);
96
+ string cssContent = unsafeReadFile(cssFile);
97
+ string jsContent = unsafeReadFile(jsFile);
98
98
 
99
99
  params.set("CSS", cssContent);
100
100
  params.set("JS", jsContent);
@@ -106,7 +106,7 @@ public:
106
106
  params.set("PROGRAM_WEBSITE", PROGRAM_WEBSITE);
107
107
  params.set("PROGRAM_AUTHOR", PROGRAM_AUTHOR);
108
108
 
109
- return Template::apply(readAll(htmlFile), params);
109
+ return Template::apply(unsafeReadFile(htmlFile), params);
110
110
  }
111
111
  };
112
112
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2016-2017 Phusion Holding B.V.
3
+ * Copyright (c) 2016-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.
@@ -48,6 +48,8 @@
48
48
  #include <Constants.h>
49
49
  #include <Exceptions.h>
50
50
  #include <FileDescriptor.h>
51
+ #include <FileTools/FileManip.h>
52
+ #include <FileTools/PathManip.h>
51
53
  #include <Utils.h>
52
54
  #include <Utils/ScopeGuard.h>
53
55
  #include <Utils/SystemTime.h>
@@ -146,7 +148,8 @@ private:
146
148
  TRACE_POINT();
147
149
  try {
148
150
  string path = session.responseDir + "/finish";
149
- int fd = syscalls::open(path.c_str(), O_RDONLY);
151
+ int fd = syscalls::openat(session.responseDirFd, "finish",
152
+ O_RDONLY | O_NOFOLLOW);
150
153
  if (fd == -1) {
151
154
  int e = errno;
152
155
  throw FileSystemException("Error opening FIFO " + path,
@@ -370,6 +373,7 @@ private:
370
373
  }
371
374
 
372
375
  void loadResultPropertiesFromResponseDir(bool socketsRequired) {
376
+ TRACE_POINT();
373
377
  Result &result = session.result;
374
378
  string path = session.responseDir + "/properties.json";
375
379
  Json::Reader reader;
@@ -377,19 +381,29 @@ private:
377
381
  vector<string> errors;
378
382
 
379
383
  // We already checked whether properties.json exists before invoking
380
- // this method, so if readAll() fails then we can't be sure that
384
+ // this method, so if safeReadFile() fails then we can't be sure that
381
385
  // it's an application problem. This is why we want the SystemException
382
386
  // to propagate to higher layers so that there it can be turned into
383
387
  // a generic filesystem-related or IO-related SpawnException, as opposed
384
388
  // to one about this problem specifically.
385
389
 
386
- if (!reader.parse(readAll(path), doc)) {
390
+ UPDATE_TRACE_POINT();
391
+ pair<string, bool> jsonContent = safeReadFile(session.responseDirFd, "properties.json",
392
+ SPAWNINGKIT_MAX_PROPERTIES_JSON_SIZE);
393
+ if (!jsonContent.second) {
394
+ errors.push_back("Error parsing " + path + ": file bigger than "
395
+ + toString(SPAWNINGKIT_MAX_PROPERTIES_JSON_SIZE) + " bytes");
396
+ throwSpawnExceptionBecauseOfResultValidationErrors(vector<string>(),
397
+ errors);
398
+ }
399
+ if (!reader.parse(jsonContent.first, doc)) {
387
400
  errors.push_back("Error parsing " + path + ": " +
388
401
  reader.getFormattedErrorMessages());
389
402
  throwSpawnExceptionBecauseOfResultValidationErrors(vector<string>(),
390
403
  errors);
391
404
  }
392
405
 
406
+ UPDATE_TRACE_POINT();
393
407
  validateResultPropertiesFile(doc, socketsRequired, errors);
394
408
  if (!errors.empty()) {
395
409
  errors.insert(errors.begin(), "The following errors were detected in "
@@ -402,6 +416,7 @@ private:
402
416
  return;
403
417
  }
404
418
 
419
+ UPDATE_TRACE_POINT();
405
420
  Json::Value::iterator it, end = doc["sockets"].end();
406
421
  for (it = doc["sockets"].begin(); it != end; it++) {
407
422
  const Json::Value &socketDoc = *it;
@@ -423,44 +438,50 @@ private:
423
438
  void validateResultPropertiesFile(const Json::Value &doc, bool socketsRequired,
424
439
  vector<string> &errors) const
425
440
  {
441
+ TRACE_POINT();
426
442
  if (!doc.isMember("sockets")) {
427
443
  if (socketsRequired) {
428
444
  errors.push_back("'sockets' must be specified");
429
445
  }
430
- } else if (!doc["sockets"].isArray()) {
446
+ return;
447
+ }
448
+ if (!doc["sockets"].isArray()) {
431
449
  errors.push_back("'sockets' must be an array");
432
- } else {
433
- if (socketsRequired && doc["sockets"].empty()) {
434
- errors.push_back("'sockets' must be non-empty");
435
- return;
436
- }
437
-
438
- Json::Value::const_iterator it, end = doc["sockets"].end();
439
- for (it = doc["sockets"].begin(); it != end; it++) {
440
- const Json::Value &socketDoc = *it;
450
+ return;
451
+ }
452
+ if (socketsRequired && doc["sockets"].empty()) {
453
+ errors.push_back("'sockets' must be non-empty");
454
+ return;
455
+ }
441
456
 
442
- if (!socketDoc.isObject()) {
443
- errors.push_back("'sockets[" + toString(it.index())
444
- + "]' must be an object");
445
- continue;
446
- }
457
+ UPDATE_TRACE_POINT();
458
+ Json::Value::const_iterator it, end = doc["sockets"].end();
459
+ for (it = doc["sockets"].begin(); it != end; it++) {
460
+ const Json::Value &socketDoc = *it;
447
461
 
448
- validateResultPropertiesFileSocketField(socketDoc,
449
- "address", Json::stringValue, it.index(),
450
- true, true, errors);
451
- validateResultPropertiesFileSocketField(socketDoc,
452
- "protocol", Json::stringValue, it.index(),
453
- true, true, errors);
454
- validateResultPropertiesFileSocketField(socketDoc,
455
- "description", Json::stringValue, it.index(),
456
- false, true, errors);
457
- validateResultPropertiesFileSocketField(socketDoc,
458
- "concurrency", Json::intValue, it.index(),
459
- true, false, errors);
460
- validateResultPropertiesFileSocketField(socketDoc,
461
- "accept_http_requests", Json::booleanValue, it.index(),
462
- false, false, errors);
462
+ if (!socketDoc.isObject()) {
463
+ errors.push_back("'sockets[" + toString(it.index())
464
+ + "]' must be an object");
465
+ continue;
463
466
  }
467
+
468
+ validateResultPropertiesFileSocketField(socketDoc,
469
+ "address", Json::stringValue, it.index(),
470
+ true, true, errors);
471
+ validateResultPropertiesFileSocketField(socketDoc,
472
+ "protocol", Json::stringValue, it.index(),
473
+ true, true, errors);
474
+ validateResultPropertiesFileSocketField(socketDoc,
475
+ "description", Json::stringValue, it.index(),
476
+ false, true, errors);
477
+ validateResultPropertiesFileSocketField(socketDoc,
478
+ "concurrency", Json::intValue, it.index(),
479
+ true, false, errors);
480
+ validateResultPropertiesFileSocketField(socketDoc,
481
+ "accept_http_requests", Json::booleanValue, it.index(),
482
+ false, false, errors);
483
+ validateResultPropertiesFileSocketAddress(socketDoc,
484
+ it.index(), errors);
464
485
  }
465
486
  }
466
487
 
@@ -497,6 +518,93 @@ private:
497
518
  }
498
519
  }
499
520
 
521
+ void validateResultPropertiesFileSocketAddress(const Json::Value &doc,
522
+ unsigned int index, vector<string> &errors) const
523
+ {
524
+ TRACE_POINT();
525
+ if (!doc["address"].isString()
526
+ || getSocketAddressType(doc["address"].asString()) != SAT_UNIX)
527
+ {
528
+ return;
529
+ }
530
+
531
+ string filename = parseUnixSocketAddress(doc["address"].asString());
532
+
533
+ if (filename.empty()) {
534
+ errors.push_back("'sockets[" + toString(index)
535
+ + "].address' contains an empty Unix domain socket filename");
536
+ return;
537
+ }
538
+
539
+ if (filename[0] != '/') {
540
+ errors.push_back("'sockets[" + toString(index)
541
+ + "].address' when referring to a Unix domain socket, must be"
542
+ " an absolute path (given path: " + filename + ")");
543
+ return;
544
+ }
545
+
546
+ // If any of the parent directories is writable by a normal user
547
+ // (Joe) that is not the app's user (Jane), then Joe can swap that
548
+ // directory with something else, with contents controlled by Joe.
549
+ // That way, Joe can cause Passenger to connect to (and forward
550
+ // Jane's traffic to) a process that does not actually belong to
551
+ // Jane.
552
+ //
553
+ // To mitigate this risk, we insist that the socket be placed in a
554
+ // directory that we know is safe (instanceDir + "/apps.s").
555
+ // We don't rely on isPathProbablySecureForRootUse() because that
556
+ // function cannot be 100% sure that it is correct.
557
+
558
+ UPDATE_TRACE_POINT();
559
+ // instanceDir is only empty in tests
560
+ if (!session.context->instanceDir.empty()) {
561
+ StaticString actualDir = extractDirNameStatic(filename);
562
+ string expectedDir = session.context->instanceDir + "/apps.s";
563
+ if (actualDir != expectedDir) {
564
+ errors.push_back("'sockets[" + toString(index)
565
+ + "].address', when referring to a Unix domain socket,"
566
+ " must be an absolute path to a file in '"
567
+ + expectedDir + "' (given path: " + filename + ")");
568
+ return;
569
+ }
570
+ }
571
+
572
+ UPDATE_TRACE_POINT();
573
+ struct stat s;
574
+ int ret;
575
+ do {
576
+ ret = lstat(filename.c_str(), &s);
577
+ } while (ret == -1 && errno == EAGAIN);
578
+
579
+ if (ret == -1) {
580
+ int e = errno;
581
+ if (e == EEXIST) {
582
+ errors.push_back("'sockets[" + toString(index)
583
+ + "].address' refers to a non-existant Unix domain"
584
+ " socket file (given path: " + filename + ")");
585
+ return;
586
+ } else {
587
+ throw FileSystemException("Cannot stat " + filename,
588
+ e, filename);
589
+ }
590
+ }
591
+
592
+ // We only check the UID, not the GID, because the socket
593
+ // may be automatically made with a different GID than
594
+ // the creating process's due to the setgid bit being set
595
+ // the directory that contains the socket. Furthermore,
596
+ // on macOS it seems that all directories behave as if
597
+ // they have the setgid bit set.
598
+
599
+ UPDATE_TRACE_POINT();
600
+ if (s.st_uid != session.uid) {
601
+ errors.push_back("'sockets[" + toString(index)
602
+ + "].address', when referring to a Unix domain socket file,"
603
+ " must be owned by user " + getUserName(session.uid)
604
+ + " (actual owner: " + getUserName(s.st_uid) + ")");
605
+ }
606
+ }
607
+
500
608
  bool resultHasSocketWithPreloaderProtocol() const {
501
609
  const vector<Result::Socket> &sockets = session.result.sockets;
502
610
  vector<Result::Socket>::const_iterator it, end = sockets.end();
@@ -553,6 +661,7 @@ private:
553
661
  SpawnException e(INTERNAL_ERROR, session.journey, config);
554
662
  e.setSubprocessPid(pid);
555
663
  e.setStdoutAndErrData(getStdoutErrData());
664
+ loadBasicInfoFromEnvDumpDir(e);
556
665
  loadAnnotationsFromEnvDumpDir(e);
557
666
 
558
667
  if (config->wrapperSuppliedByThirdParty) {
@@ -614,6 +723,7 @@ private:
614
723
  SpawnException e(INTERNAL_ERROR, session.journey, config);
615
724
  e.setSubprocessPid(pid);
616
725
  e.setStdoutAndErrData(getStdoutErrData());
726
+ loadBasicInfoFromEnvDumpDir(e);
617
727
  loadAnnotationsFromEnvDumpDir(e);
618
728
 
619
729
  e.setSummary("Error spawning the web application: the application"
@@ -660,6 +770,7 @@ private:
660
770
  SpawnException e(INTERNAL_ERROR, session.journey, config);
661
771
  e.setSubprocessPid(pid);
662
772
  e.setStdoutAndErrData(getStdoutErrData());
773
+ loadBasicInfoFromEnvDumpDir(e);
663
774
  loadAnnotationsFromEnvDumpDir(e);
664
775
 
665
776
  if (config->wrapperSuppliedByThirdParty) {
@@ -731,6 +842,7 @@ private:
731
842
  SpawnException e(INTERNAL_ERROR, session.journey, config);
732
843
  e.setSubprocessPid(pid);
733
844
  e.setStdoutAndErrData(getStdoutErrData());
845
+ loadBasicInfoFromEnvDumpDir(e);
734
846
  loadAnnotationsFromEnvDumpDir(e);
735
847
 
736
848
  e.setSummary("Error spawning the web application: the application"
@@ -773,6 +885,8 @@ private:
773
885
  e.setSubprocessPid(pid);
774
886
  e.setStdoutAndErrData(getStdoutErrData());
775
887
  e.setAdvancedProblemDetails(toString(internalFieldErrors));
888
+ loadBasicInfoFromEnvDumpDir(e);
889
+ loadAnnotationsFromEnvDumpDir(e);
776
890
 
777
891
  e.setSummary("Error spawning the web application:"
778
892
  " a bug in " SHORT_PROGRAM_NAME " caused the"
@@ -802,12 +916,23 @@ private:
802
916
  } else if (!config->genericApp && config->startsUsingWrapper) {
803
917
  UPDATE_TRACE_POINT();
804
918
  loadJourneyStateFromResponseDir();
805
- session.journey.setStepErrored(SUBPROCESS_WRAPPER_PREPARATION, true);
919
+ switch (session.journey.getType()) {
920
+ case SPAWN_DIRECTLY:
921
+ case START_PRELOADER:
922
+ session.journey.setStepErrored(SUBPROCESS_WRAPPER_PREPARATION, true);
923
+ break;
924
+ case SPAWN_THROUGH_PRELOADER:
925
+ session.journey.setStepErrored(SUBPROCESS_PREPARE_AFTER_FORKING_FROM_PRELOADER, true);
926
+ break;
927
+ default:
928
+ P_BUG("Unknown journey type " << (int) session.journey.getType());
929
+ }
806
930
 
807
931
  SpawnException e(INTERNAL_ERROR, session.journey, config);
808
932
  e.setSubprocessPid(pid);
809
933
  e.setStdoutAndErrData(getStdoutErrData());
810
934
  e.setAdvancedProblemDetails(toString(appSuppliedFieldErrors));
935
+ loadBasicInfoFromEnvDumpDir(e);
811
936
  loadAnnotationsFromEnvDumpDir(e);
812
937
 
813
938
  if (config->wrapperSuppliedByThirdParty) {
@@ -883,6 +1008,7 @@ private:
883
1008
  e.setAdvancedProblemDetails(toString(appSuppliedFieldErrors));
884
1009
  e.setSubprocessPid(pid);
885
1010
  e.setStdoutAndErrData(getStdoutErrData());
1011
+ loadBasicInfoFromEnvDumpDir(e);
886
1012
  loadAnnotationsFromEnvDumpDir(e);
887
1013
 
888
1014
  message = "<p>The " PROGRAM_NAME " application server tried"
@@ -922,13 +1048,15 @@ private:
922
1048
  ErrorCategory inferErrorCategoryFromResponseDir(ErrorCategory defaultValue) const {
923
1049
  TRACE_POINT();
924
1050
  if (fileExists(session.responseDir + "/error/category")) {
925
- string value = strip(readAll(session.responseDir + "/error/category"));
1051
+ string value = strip(safeReadFile(session.responseErrorDirFd,
1052
+ "category", SPAWNINGKIT_MAX_ERROR_CATEGORY_SIZE).first);
926
1053
  ErrorCategory category = stringToErrorCategory(value);
927
1054
 
928
1055
  if (category == UNKNOWN_ERROR_CATEGORY) {
929
1056
  SpawnException e(INTERNAL_ERROR, session.journey, config);
930
1057
  e.setStdoutAndErrData(getStdoutErrData());
931
1058
  e.setSubprocessPid(pid);
1059
+ loadBasicInfoFromEnvDumpDir(e);
932
1060
  loadAnnotationsFromEnvDumpDir(e);
933
1061
 
934
1062
  if (!config->genericApp && config->startsUsingWrapper) {
@@ -1047,18 +1175,24 @@ private:
1047
1175
  continue;
1048
1176
  }
1049
1177
 
1178
+ map<JourneyStep, int>::const_iterator it = session.stepDirFds.find(step);
1179
+ if (it == session.stepDirFds.end()) {
1180
+ P_BUG("No fd opened for step " << stepString);
1181
+ }
1182
+
1050
1183
  loadJourneyStateFromResponseDirForSpecificStep(
1051
- session, pid, stdoutAndErrCapturer, step, stepDir);
1184
+ session, pid, stdoutAndErrCapturer, step, stepDir, it->second);
1052
1185
  }
1053
1186
  }
1054
1187
 
1055
1188
  static void loadJourneyStateFromResponseDirForSpecificStep(HandshakeSession &session,
1056
1189
  pid_t pid, const BackgroundIOCapturerPtr &stdoutAndErrCapturer,
1057
- JourneyStep step, const string &stepDir)
1190
+ JourneyStep step, const string &stepDir, int stepDirFd)
1058
1191
  {
1059
1192
  TRACE_POINT_WITH_DATA(journeyStepToString(step).data());
1060
1193
  string summary;
1061
- string value = strip(readAll(stepDir + "/state"));
1194
+ string value = strip(safeReadFile(stepDirFd, "state",
1195
+ SPAWNINGKIT_MAX_JOURNEY_STEP_FILE_SIZE).first);
1062
1196
  JourneyStepState state = stringToJourneyStepState(value);
1063
1197
  const Config *config = session.config;
1064
1198
 
@@ -1095,6 +1229,7 @@ private:
1095
1229
  SpawnException e(INTERNAL_ERROR, session.journey, config);
1096
1230
  e.setStdoutAndErrData(getStdoutErrData(stdoutAndErrCapturer));
1097
1231
  e.setSubprocessPid(pid);
1232
+ loadBasicInfoFromEnvDumpDir(e, session);
1098
1233
  loadAnnotationsFromEnvDumpDir(e, session);
1099
1234
 
1100
1235
  if (!config->genericApp && config->startsUsingWrapper) {
@@ -1187,6 +1322,7 @@ private:
1187
1322
  SpawnException e(INTERNAL_ERROR, session.journey, config);
1188
1323
  e.setStdoutAndErrData(getStdoutErrData(stdoutAndErrCapturer));
1189
1324
  e.setSubprocessPid(pid);
1325
+ loadBasicInfoFromEnvDumpDir(e, session);
1190
1326
  loadAnnotationsFromEnvDumpDir(e, session);
1191
1327
 
1192
1328
  if (!config->genericApp && config->startsUsingWrapper) {
@@ -1274,13 +1410,15 @@ private:
1274
1410
 
1275
1411
  UPDATE_TRACE_POINT();
1276
1412
  if (fileExists(stepDir + "/begin_time_monotonic")) {
1277
- value = readAll(stepDir + "/begin_time_monotonic");
1413
+ value = safeReadFile(stepDirFd, "begin_time_monotonic",
1414
+ SPAWNINGKIT_MAX_JOURNEY_STEP_FILE_SIZE).first;
1278
1415
  MonotonicTimeUsec beginTimeMonotonic = atof(value.c_str()) * 1000000;
1279
1416
  P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
1280
1417
  << ": monotonic begin time is \"" << cEscapeString(value) << "\"");
1281
1418
  session.journey.setStepBeginTime(step, beginTimeMonotonic);
1282
1419
  } else if (fileExists(stepDir + "/begin_time")) {
1283
- value = readAll(stepDir + "/begin_time");
1420
+ value = safeReadFile(stepDirFd, "begin_time",
1421
+ SPAWNINGKIT_MAX_JOURNEY_STEP_FILE_SIZE).first;
1284
1422
  unsigned long long beginTime = atof(value.c_str()) * 1000000;
1285
1423
  MonotonicTimeUsec beginTimeMonotonic = usecTimestampToMonoTime(beginTime);
1286
1424
  P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
@@ -1294,13 +1432,15 @@ private:
1294
1432
 
1295
1433
  UPDATE_TRACE_POINT();
1296
1434
  if (fileExists(stepDir + "/end_time_monotonic")) {
1297
- value = readAll(stepDir + "/end_time_monotonic");
1435
+ value = safeReadFile(stepDirFd, "end_time_monotonic",
1436
+ SPAWNINGKIT_MAX_JOURNEY_STEP_FILE_SIZE).first;
1298
1437
  MonotonicTimeUsec endTimeMonotonic = atof(value.c_str()) * 1000000;
1299
1438
  P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
1300
1439
  << ": monotonic end time is \"" << cEscapeString(value) << "\"");
1301
1440
  session.journey.setStepEndTime(step, endTimeMonotonic);
1302
1441
  } else if (fileExists(stepDir + "/end_time")) {
1303
- value = readAll(stepDir + "/end_time");
1442
+ value = safeReadFile(stepDirFd, "end_time",
1443
+ SPAWNINGKIT_MAX_JOURNEY_STEP_FILE_SIZE).first;
1304
1444
  unsigned long long endTime = atof(value.c_str()) * 1000000;
1305
1445
  MonotonicTimeUsec endTimeMonotonic = usecTimestampToMonoTime(endTime);
1306
1446
  P_DEBUG("[App " << pid << " journey] Step " << journeyStepToString(step)
@@ -1330,40 +1470,51 @@ private:
1330
1470
  void loadSubprocessErrorMessagesAndEnvDump(SpawnException &e) const {
1331
1471
  TRACE_POINT();
1332
1472
  const string &responseDir = session.responseDir;
1333
- const string &envDumpDir = session.envDumpDir;
1334
1473
 
1335
1474
  if (fileExists(responseDir + "/error/summary")) {
1336
- e.setSummary(strip(readAll(responseDir + "/error/summary")));
1475
+ e.setSummary(strip(safeReadFile(session.responseErrorDirFd, "summary",
1476
+ SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE).first));
1337
1477
  }
1338
1478
 
1339
1479
  if (e.getAdvancedProblemDetails().empty()
1340
1480
  && fileExists(responseDir + "/error/advanced_problem_details"))
1341
1481
  {
1342
- e.setAdvancedProblemDetails(strip(readAll(responseDir
1343
- + "/error/advanced_problem_details")));
1482
+ e.setAdvancedProblemDetails(strip(safeReadFile(session.responseErrorDirFd,
1483
+ "advanced_problem_details", SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE).first));
1344
1484
  }
1345
1485
 
1346
1486
  if (fileExists(responseDir + "/error/problem_description.html")) {
1347
- e.setProblemDescriptionHTML(readAll(responseDir + "/error/problem_description.html"));
1487
+ e.setProblemDescriptionHTML(safeReadFile(session.responseErrorDirFd,
1488
+ "problem_description.html", SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE).first);
1348
1489
  } else if (fileExists(responseDir + "/error/problem_description.txt")) {
1349
- e.setProblemDescriptionHTML(escapeHTML(strip(readAll(
1350
- responseDir + "/error/problem_description.txt"))));
1490
+ e.setProblemDescriptionHTML(escapeHTML(strip(safeReadFile(session.responseErrorDirFd,
1491
+ "problem_description.txt", SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE).first)));
1351
1492
  }
1352
1493
 
1353
1494
  if (fileExists(responseDir + "/error/solution_description.html")) {
1354
- e.setSolutionDescriptionHTML(readAll(responseDir + "/error/solution_description.html"));
1495
+ e.setSolutionDescriptionHTML(safeReadFile(session.responseErrorDirFd,
1496
+ "solution_description.html", SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE).first);
1355
1497
  } else if (fileExists(responseDir + "/error/solution_description.txt")) {
1356
- e.setSolutionDescriptionHTML(escapeHTML(strip(readAll(
1357
- responseDir + "/error/solution_description.txt"))));
1498
+ e.setSolutionDescriptionHTML(escapeHTML(strip(safeReadFile(session.responseErrorDirFd,
1499
+ "solution_description.txt", SPAWNINGKIT_MAX_SUBPROCESS_ERROR_MESSAGE_SIZE).first)));
1358
1500
  }
1359
1501
 
1502
+ loadBasicInfoFromEnvDumpDir(e);
1503
+ loadAnnotationsFromEnvDumpDir(e);
1504
+ }
1505
+
1506
+ void loadBasicInfoFromEnvDumpDir(SpawnException &e) const {
1507
+ loadBasicInfoFromEnvDumpDir(e, session);
1508
+ }
1509
+
1510
+ static void loadBasicInfoFromEnvDumpDir(SpawnException &e, HandshakeSession &session) {
1360
1511
  string envvars, userInfo, ulimits;
1361
- loadBasicInfoFromEnvDumpDir(envDumpDir, envvars, userInfo, ulimits);
1512
+
1513
+ loadBasicInfoFromEnvDumpDir(session.envDumpDir, session.envDumpDirFd,
1514
+ envvars, userInfo, ulimits);
1362
1515
  e.setSubprocessEnvvars(envvars);
1363
1516
  e.setSubprocessUserInfo(userInfo);
1364
1517
  e.setSubprocessUlimits(ulimits);
1365
-
1366
- loadAnnotationsFromEnvDumpDir(e);
1367
1518
  }
1368
1519
 
1369
1520
  static void doClosedir(DIR *dir) {
@@ -1387,7 +1538,9 @@ private:
1387
1538
  while ((ent = readdir(dir)) != NULL) {
1388
1539
  if (ent->d_name[0] != '.') {
1389
1540
  e.setAnnotation(ent->d_name, strip(
1390
- Passenger::readAll(path + "/" + ent->d_name)));
1541
+ safeReadFile(session.envDumpAnnotationsDirFd,
1542
+ ent->d_name, SPAWNINGKIT_MAX_SUBPROCESS_ENVDUMP_SIZE).first
1543
+ ));
1391
1544
  }
1392
1545
  }
1393
1546
  }
@@ -1629,16 +1782,19 @@ public:
1629
1782
  }
1630
1783
 
1631
1784
  static void loadBasicInfoFromEnvDumpDir(const string &envDumpDir,
1632
- string &envvars, string &userInfo, string &ulimits)
1785
+ int envDumpDirFd, string &envvars, string &userInfo, string &ulimits)
1633
1786
  {
1634
1787
  if (fileExists(envDumpDir + "/envvars")) {
1635
- envvars = readAll(envDumpDir + "/envvars");
1788
+ envvars = safeReadFile(envDumpDirFd, "envvars",
1789
+ SPAWNINGKIT_MAX_SUBPROCESS_ENVDUMP_SIZE).first;
1636
1790
  }
1637
1791
  if (fileExists(envDumpDir + "/user_info")) {
1638
- userInfo = readAll(envDumpDir + "/user_info");
1792
+ userInfo = safeReadFile(envDumpDirFd, "user_info",
1793
+ SPAWNINGKIT_MAX_SUBPROCESS_ENVDUMP_SIZE).first;
1639
1794
  }
1640
1795
  if (fileExists(envDumpDir + "/ulimits")) {
1641
- ulimits = readAll(envDumpDir + "/ulimits");
1796
+ ulimits = safeReadFile(envDumpDirFd, "ulimits",
1797
+ SPAWNINGKIT_MAX_SUBPROCESS_ENVDUMP_SIZE).first;
1642
1798
  }
1643
1799
  }
1644
1800
  };