diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2019-05-10 16:12:33 +0000 |
---|---|---|
committer | Sara Golemon <sara.golemon@mongodb.com> | 2019-05-30 21:45:31 +0000 |
commit | 32287881c1fdd01708a70f912f6775f0afaa5114 (patch) | |
tree | b25f61a187fab3fdc63b0d9712edb6cde67cf6ba /src | |
parent | b85b159f44e155ea05b456552a1cd5782a7d7850 (diff) | |
download | mongo-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.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/options_parser/options_parser.cpp | 107 | ||||
-rw-r--r-- | src/mongo/util/options_parser/options_parser.h | 4 | ||||
-rw-r--r-- | src/mongo/util/options_parser/options_parser_test.cpp | 2 |
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 |