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.
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
  };