summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2019-05-10 16:12:33 +0000
committerSara Golemon <sara.golemon@mongodb.com>2019-05-30 21:45:31 +0000
commit32287881c1fdd01708a70f912f6775f0afaa5114 (patch)
treeb25f61a187fab3fdc63b0d9712edb6cde67cf6ba /src
parentb85b159f44e155ea05b456552a1cd5782a7d7850 (diff)
downloadmongo-32287881c1fdd01708a70f912f6775f0afaa5114.tar.gz
SERVER-40877 Enforce strict config file access for expansions
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/server_options_test.cpp2
-rw-r--r--src/mongo/util/net/ssl_options_test.cpp2
-rw-r--r--src/mongo/util/options_parser/options_parser.cpp107
-rw-r--r--src/mongo/util/options_parser/options_parser.h4
-rw-r--r--src/mongo/util/options_parser/options_parser_test.cpp2
5 files changed, 94 insertions, 23 deletions
diff --git a/src/mongo/db/server_options_test.cpp b/src/mongo/db/server_options_test.cpp
index 23217b7da78..0b4ace14291 100644
--- a/src/mongo/db/server_options_test.cpp
+++ b/src/mongo/db/server_options_test.cpp
@@ -76,7 +76,7 @@ MONGO_INITIALIZER(ServerLogRedirection)(mongo::InitializerContext*) {
class OptionsParserTester : public moe::OptionsParser {
public:
- Status readConfigFile(const std::string& filename, std::string* config) {
+ Status readConfigFile(const std::string& filename, std::string* config, ConfigExpand) {
if (filename != _filename) {
::mongo::StringBuilder sb;
sb << "Parser using filename: " << filename
diff --git a/src/mongo/util/net/ssl_options_test.cpp b/src/mongo/util/net/ssl_options_test.cpp
index 35699cb8d44..168cc2ff954 100644
--- a/src/mongo/util/net/ssl_options_test.cpp
+++ b/src/mongo/util/net/ssl_options_test.cpp
@@ -138,7 +138,7 @@ TEST(SSLOptions, invalidCases) {
class OptionsParserTester : public moe::OptionsParser {
public:
- Status readConfigFile(const std::string& filename, std::string* config) {
+ Status readConfigFile(const std::string& filename, std::string* config, ConfigExpand) {
if (filename != _filename) {
::mongo::StringBuilder sb;
sb << "Parser using filename: " << filename
diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp
index 400dd9e9301..e888351b52c 100644
--- a/src/mongo/util/options_parser/options_parser.cpp
+++ b/src/mongo/util/options_parser/options_parser.cpp
@@ -33,13 +33,23 @@
#include <algorithm>
#include <boost/filesystem.hpp>
+#include <boost/iostreams/device/file_descriptor.hpp>
+#include <boost/iostreams/stream.hpp>
+#include <boost/iostreams/stream_buffer.hpp>
#include <boost/program_options.hpp>
#include <cctype>
#include <cerrno>
+#include <fcntl.h>
#include <fstream>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <yaml-cpp/yaml.h>
+#ifdef _WIN32
+#include <io.h>
+#endif
+
#include "mongo/base/init.h"
#include "mongo/base/parse_number.h"
#include "mongo/base/status.h"
@@ -1351,6 +1361,30 @@ bool isYAMLConfig(const YAML::Node& config) {
}
}
+#ifndef _WIN32
+Status checkFileOwnershipAndMode(int fd, mode_t prohibit, StringData modeDesc) {
+ struct stat stats;
+
+ if (::fstat(fd, &stats) == -1) {
+ const auto& ewd = errnoWithDescription();
+ return {ErrorCodes::InvalidPath, str::stream() << "Error reading file metadata: " << ewd};
+ }
+
+ if (stats.st_uid != ::getuid()) {
+ // Must be owned by current process user.
+ return {ErrorCodes::InvalidPath, "File is not owned by current user"};
+ }
+
+ if ((stats.st_mode & prohibit) != 0) {
+ // Must not be accessible by non-owner.
+ return {ErrorCodes::InvalidPath,
+ str::stream() << "File is " << modeDesc << " by non-owner users"};
+ }
+
+ return Status::OK();
+}
+#endif
+
} // namespace
/**
@@ -1381,24 +1415,59 @@ Status OptionsParser::addDefaultValues(const OptionSection& options, Environment
* We could redesign the parser to use some kind of streaming interface, but for now this is
* simple and works for the current use case of config files which should be limited in size.
*/
-Status OptionsParser::readConfigFile(const std::string& filename, std::string* contents) {
- std::ifstream file;
- file.open(filename.c_str());
- if (file.fail()) {
- const int current_errno = errno;
- StringBuilder sb;
- sb << "Error opening config file: " << strerror(current_errno);
- return Status(ErrorCodes::InternalError, sb.str());
- }
-
+Status OptionsParser::readConfigFile(const std::string& filename,
+ std::string* contents,
+ ConfigExpand configExpand) {
// check if it's a regular file
fs::path configPath(filename);
if (!fs::is_regular_file(filename)) {
- StringBuilder sb;
- sb << "Error opening config file: " << strerror(EISDIR);
- return Status(ErrorCodes::InternalError, sb.str());
+ return {ErrorCodes::InternalError,
+ str::stream() << "Error opening config file: " << strerror(EISDIR)};
+ }
+
+#ifdef _WIN32
+ int fd = ::_open(filename.c_str(), O_RDONLY);
+#else
+ int fd = ::open(filename.c_str(), O_RDONLY);
+#endif
+
+ if (fd < 0) {
+ const auto& ewd = errnoWithDescription();
+ return {ErrorCodes::InternalError, str::stream() << "Error opening config file: " << ewd};
+ }
+
+#ifdef _WIN32
+ // The checks below are only performed on POSIX systems
+ // due to differing permission models.
+ auto fdguard = makeGuard([&fd] { ::_close(fd); });
+#else
+ auto fdguard = makeGuard([&fd] { ::close(fd); });
+
+ if (configExpand.rest) {
+ auto status = checkFileOwnershipAndMode(fd, S_IRGRP | S_IROTH, "readable"_sd);
+ if (!status.isOK()) {
+ return {status.code(),
+ str::stream() << "When using --configExpand=rest, config file must be "
+ << "exclusively readable by current process user. "
+ << status.reason()};
+ }
}
+ if (configExpand.exec) {
+ auto status = checkFileOwnershipAndMode(fd, S_IWGRP | S_IWOTH, "writable"_sd);
+ if (!status.isOK()) {
+ return {status.code(),
+ str::stream() << "When using --configExpand=exec, config file must be "
+ << "exclusively writable by current process user. "
+ << status.reason()};
+ }
+ }
+#endif
+
+ boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> fdBuf(
+ fd, boost::iostreams::file_descriptor_flags::never_close_handle);
+ std::istream file(&fdBuf);
+
// Transfer data to a stringstream
std::stringstream config;
std::string configString;
@@ -1704,18 +1773,18 @@ Status OptionsParser::run(const OptionSection& options,
return ret;
}
- std::string config_file;
- ret = readConfigFile(config_filename, &config_file);
- if (!ret.isOK()) {
- return ret;
- }
-
auto swExpand = parseConfigExpand(commandLineEnvironment);
if (!swExpand.isOK()) {
return swExpand.getStatus();
}
auto configExpand = std::move(swExpand.getValue());
+ std::string config_file;
+ ret = readConfigFile(config_filename, &config_file, configExpand);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
ret = parseConfigFile(options, config_file, &configEnvironment, configExpand);
if (!ret.isOK()) {
return ret;
diff --git a/src/mongo/util/options_parser/options_parser.h b/src/mongo/util/options_parser/options_parser.h
index dd0278c6b23..0ad3d74f6a7 100644
--- a/src/mongo/util/options_parser/options_parser.h
+++ b/src/mongo/util/options_parser/options_parser.h
@@ -156,7 +156,9 @@ private:
/** Reads the given config file into the output string. This function is virtual for
* testing purposes only. */
- virtual Status readConfigFile(const std::string& filename, std::string*);
+ virtual Status readConfigFile(const std::string& filename,
+ std::string*,
+ ConfigExpand configExpand);
};
} // namespace optionenvironment
diff --git a/src/mongo/util/options_parser/options_parser_test.cpp b/src/mongo/util/options_parser/options_parser_test.cpp
index 7229bea7673..42530ab08b6 100644
--- a/src/mongo/util/options_parser/options_parser_test.cpp
+++ b/src/mongo/util/options_parser/options_parser_test.cpp
@@ -55,7 +55,7 @@ constexpr auto OptionParserTest = moe::OptionSection::OptionParserUsageType::Opt
class OptionsParserTester : public moe::OptionsParser {
public:
- Status readConfigFile(const std::string& filename, std::string* config) {
+ Status readConfigFile(const std::string& filename, std::string* config, ConfigExpand) {
if (filename != _filename) {
::mongo::StringBuilder sb;
sb << "Parser using filename: " << filename