diff --git a/doc/sphinx/arm/ctrl-channel.rst b/doc/sphinx/arm/ctrl-channel.rst index a9757c21af..652fa525af 100644 --- a/doc/sphinx/arm/ctrl-channel.rst +++ b/doc/sphinx/arm/ctrl-channel.rst @@ -532,6 +532,12 @@ An example command invocation looks like this: } } +.. note:: + + As of Kea 2.7.9, the config file file may only be written to the same + directory as the config file used when starting Kea (passed as a ``-c`` + argument). + .. isccmd:: leases-reclaim .. _command-leases-reclaim: diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index a5c9371fe8..9cf49e47cb 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -290,11 +290,18 @@ ControlledDhcpv4Srv::commandConfigWriteHandler(const string&, // filename parameter was not specified, so let's use whatever we remember // from the command-line filename = getConfigFile(); - } - - if (filename.empty()) { - return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename." - "Please specify filename explicitly.")); + if (filename.empty()) { + return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename." + "Please specify filename explicitly.")); + } + } else { + try { + checkWriteConfigFile(filename); + } catch (const isc::Exception& ex) { + return (createAnswer(CONTROL_RESULT_ERROR, + string("not allowed to write config into ") + + filename)); + } } // Ok, it's time to write the file. diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 31e78bed49..4e56c0ca90 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -179,9 +179,9 @@ private: /// current configuration to disk. This command takes one optional /// parameter called filename. If specified, the current configuration /// will be written to that file. If not specified, the file used during - /// Kea start-up will be used. To avoid any exploits, the path is - /// always relative and .. is not allowed in the filename. This is - /// a security measure against exploiting file writes remotely. + /// Kea start-up will be used. To avoid any exploits, the target + /// directory must be the same as a security measure against + /// exploiting file writes remotely. /// /// @param command (ignored) /// @param args may contain optional string argument filename diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index cf5a0d3bf0..304c083b8f 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -293,11 +293,18 @@ ControlledDhcpv6Srv::commandConfigWriteHandler(const string&, // filename parameter was not specified, so let's use whatever we remember // from the command-line filename = getConfigFile(); - } - - if (filename.empty()) { - return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename." - "Please specify filename explicitly.")); + if (filename.empty()) { + return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename." + "Please specify filename explicitly.")); + } + } else { + try { + checkWriteConfigFile(filename); + } catch (const isc::Exception& ex) { + return (createAnswer(CONTROL_RESULT_ERROR, + string("not allowed to write config into ") + + filename)); + } } // Ok, it's time to write the file. diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index d2b5b73484..764a7c5834 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -179,9 +179,9 @@ private: /// current configuration to disk. This command takes one optional /// parameter called filename. If specified, the current configuration /// will be written to that file. If not specified, the file used during - /// Kea start-up will be used. To avoid any exploits, the path is - /// always relative and .. is not allowed in the filename. This is - /// a security measure against exploiting file writes remotely. + /// Kea start-up will be used. To avoid any exploits, the target + /// directory must be the same as a security measure against + /// exploiting file writes remotely. /// /// @param command (ignored) /// @param args may contain optional string argument filename diff --git a/src/lib/process/d_controller.cc b/src/lib/process/d_controller.cc index b4f5781a35..a535ffb65b 100644 --- a/src/lib/process/d_controller.cc +++ b/src/lib/process/d_controller.cc @@ -508,13 +508,21 @@ DControllerBase::configWriteHandler(const std::string&, "Unable to determine filename." "Please specify filename explicitly.")); } + } else { + try { + checkWriteConfigFile(filename); + } catch (const isc::Exception& ex) { + return (createAnswer(CONTROL_RESULT_ERROR, + std::string("not allowed to write config into ") + + filename)); + } } // Ok, it's time to write the file. size_t size = 0; - ElementPtr cfg = process_->getCfgMgr()->getContext()->toElement(); try { + ElementPtr cfg = process_->getCfgMgr()->getContext()->toElement(); size = writeConfigFile(filename, cfg); } catch (const isc::Exception& ex) { return (createAnswer(CONTROL_RESULT_ERROR, diff --git a/src/lib/process/d_controller.h b/src/lib/process/d_controller.h index cb55b667a6..3ea9558be4 100644 --- a/src/lib/process/d_controller.h +++ b/src/lib/process/d_controller.h @@ -283,9 +283,9 @@ public: /// current configuration to disk. This command takes one optional /// parameter called filename. If specified, the current configuration /// will be written to that file. If not specified, the file used during - /// Kea start-up will be used. To avoid any exploits, the path is - /// always relative and .. is not allowed in the filename. This is - /// a security measure against exploiting file writes remotely. + /// Kea start-up will be used. To avoid any exploits, the target + /// directory must be the same as a security measure against + /// exploiting file writes remotely. /// /// @param command (ignored) /// @param args may contain optional string argument filename diff --git a/src/lib/process/daemon.cc b/src/lib/process/daemon.cc index 7069e1619e..fdd66128df 100644 --- a/src/lib/process/daemon.cc +++ b/src/lib/process/daemon.cc @@ -125,6 +125,28 @@ Daemon::checkConfigFile() const { } } +void +Daemon::checkWriteConfigFile(std::string& file) { + Path path(file); + // from checkConfigFile(). + if (path.stem().empty()) { + isc_throw(isc::BadValue, "config file:" << file + << " is missing file name"); + } + Path current(config_file_); + if (current.parentPath() == path.parentPath()) { + // Same paths! + return; + } + if (path.parentPath().empty()) { + // Note the current path can't be empty here. + file = current.parentPath() + "/" + file; + return; + } + isc_throw(isc::BadValue, "file " << file << " must be in the same " + << "directory as the config file (" << config_file_ << "'"); +} + std::string Daemon::getProcName() { return (proc_name_); diff --git a/src/lib/process/daemon.h b/src/lib/process/daemon.h index 99b841c9d9..37c3fc1422 100644 --- a/src/lib/process/daemon.h +++ b/src/lib/process/daemon.h @@ -139,6 +139,15 @@ public: /// @throw BadValue when the configuration file name is bad. void checkConfigFile() const; + /// @brief Checks the to-be-written configuration file name. + /// + /// @note As a side effect prepend the current config file path + /// when the name does not contain a slash. + /// + /// @param[in][out] file Reference to the TBW configuration file name. + /// @throw BadValue when not in the same directory. + void checkWriteConfigFile(std::string& file); + /// @brief Writes current configuration to specified file /// /// This method writes the current configuration to specified file.