You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by sz...@apache.org on 2023/06/30 14:57:32 UTC
[nifi-minifi-cpp] 04/08: MINIFICPP-2020 Protect MINIFI_HOME from mutual access
This is an automated email from the ASF dual-hosted git repository.
szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 73668eb1ad939ab561474ca3682b47162e9caf39
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Fri Jun 30 07:33:14 2023 +0200
MINIFICPP-2020 Protect MINIFI_HOME from mutual access
Closes #1577
Signed-off-by: Marton Szasz <sz...@apache.org>
---
libminifi/include/utils/Error.h | 26 +++++
libminifi/include/utils/FileMutex.h | 59 ++++++++++++
libminifi/include/utils/OsUtils.h | 2 +
libminifi/src/utils/Error.cpp | 37 +++++++
libminifi/src/utils/FileMutex.cpp | 171 +++++++++++++++++++++++++++++++++
libminifi/src/utils/OsUtils.cpp | 8 ++
libminifi/test/unit/FileMutexTests.cpp | 59 ++++++++++++
minifi_main/MiNiFiMain.cpp | 9 ++
8 files changed, 371 insertions(+)
diff --git a/libminifi/include/utils/Error.h b/libminifi/include/utils/Error.h
new file mode 100644
index 000000000..77ef4a6ac
--- /dev/null
+++ b/libminifi/include/utils/Error.h
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <system_error>
+
+namespace org::apache::nifi::minifi::utils {
+
+std::error_code getLastError();
+
+} // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/include/utils/FileMutex.h b/libminifi/include/utils/FileMutex.h
new file mode 100644
index 000000000..54596ead2
--- /dev/null
+++ b/libminifi/include/utils/FileMutex.h
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <filesystem>
+#include <array>
+#include <mutex>
+#include <optional>
+#include "utils/gsl.h"
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+namespace org::apache::nifi::minifi::utils {
+
+// Warning: this will write the pid of the current process into the file
+class FileMutex {
+ public:
+ explicit FileMutex(std::filesystem::path path);
+ ~FileMutex() {
+ gsl_Expects(!file_handle_.has_value());
+ }
+
+ FileMutex(const FileMutex&) = delete;
+ FileMutex(FileMutex&&) = delete;
+ FileMutex& operator=(const FileMutex&) = delete;
+ FileMutex& operator=(FileMutex&&) = delete;
+
+ void lock();
+ void unlock();
+
+ private:
+ std::filesystem::path path_;
+
+ std::mutex mtx_;
+#ifdef WIN32
+ std::optional<HANDLE> file_handle_;
+#else
+ std::optional<int> file_handle_;
+#endif
+};
+
+} // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/include/utils/OsUtils.h b/libminifi/include/utils/OsUtils.h
index a6ef83d58..a71172333 100644
--- a/libminifi/include/utils/OsUtils.h
+++ b/libminifi/include/utils/OsUtils.h
@@ -30,6 +30,8 @@ extern std::string userIdToUsername(const std::string &uid);
/// Returns physical memory usage by the current process in bytes
int64_t getCurrentProcessPhysicalMemoryUsage();
+int64_t getCurrentProcessId();
+
/// Returns physical memory usage by the system in bytes
int64_t getSystemPhysicalMemoryUsage();
diff --git a/libminifi/src/utils/Error.cpp b/libminifi/src/utils/Error.cpp
new file mode 100644
index 000000000..ba6f15e9a
--- /dev/null
+++ b/libminifi/src/utils/Error.cpp
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/Error.h"
+
+#include <cerrno>
+#include "utils/gsl.h"
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+namespace org::apache::nifi::minifi::utils {
+
+std::error_code getLastError() {
+#ifdef WIN32
+ return {gsl::narrow<int>(GetLastError()), std::system_category()};
+#else
+ return {gsl::narrow<int>(errno), std::generic_category()};
+#endif
+}
+
+} // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/src/utils/FileMutex.cpp b/libminifi/src/utils/FileMutex.cpp
new file mode 100644
index 000000000..0ae1eb213
--- /dev/null
+++ b/libminifi/src/utils/FileMutex.cpp
@@ -0,0 +1,171 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/FileMutex.h"
+
+#include <span>
+#include <iostream>
+#include "utils/gsl.h"
+#include "utils/OsUtils.h"
+#include "utils/Error.h"
+
+#ifdef WIN32
+
+namespace org::apache::nifi::minifi::utils {
+
+FileMutex::FileMutex(std::filesystem::path path): path_(std::move(path)) {}
+
+// we cannot assume the logging system to be initialized
+
+void FileMutex::lock() {
+ std::lock_guard guard(mtx_);
+ gsl_Expects(!file_handle_.has_value());
+ HANDLE handle = CreateFileA(path_.string().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ const auto err = utils::getLastError();
+ std::string pid_str = "unknown";
+ handle = CreateFileA(path_.string().c_str(), GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (handle == INVALID_HANDLE_VALUE) {
+ std::cerr << "Failed to open file to read pid: " << utils::getLastError().message() << std::endl;
+ } else {
+ std::array<char, 16> buffer;
+ size_t pid_str_size = 0;
+ DWORD read_size;
+ while (ReadFile(handle, buffer.data() + pid_str_size, buffer.size() - pid_str_size, &read_size, NULL) && read_size != 0) {
+ pid_str_size += read_size;
+ }
+ pid_str = "'" + std::string(buffer.data(), pid_str_size) + "'";
+ if (!CloseHandle(handle)) {
+ std::cerr << "Failed to close file after unsuccessful locking attempt: " << utils::getLastError().message() << std::endl;
+ }
+ }
+
+ throw std::system_error{err, "Failed to open file '" + path_.string() + "' to be locked, previous pid: " + pid_str};
+ }
+
+ const std::string pidstr = std::to_string(utils::OsUtils::getCurrentProcessId());
+ std::span<const char> buffer = pidstr;
+ while (!buffer.empty()) {
+ DWORD written;
+ if (!WriteFile(handle, buffer.data(), buffer.size(), &written, NULL)) {
+ const auto err = utils::getLastError();
+ if (!CloseHandle(file_handle_.value())) {
+ std::cerr << "Failed to close file: " << utils::getLastError().message() << std::endl;
+ }
+ throw std::system_error(err, "Failed to write pid to lock file '" + path_.string() + "'");
+ }
+ buffer = buffer.subspan(written);
+ }
+
+ file_handle_ = handle;
+}
+
+void FileMutex::unlock() {
+ std::lock_guard guard(mtx_);
+ gsl_Expects(file_handle_.has_value());
+ if (!CloseHandle(file_handle_.value())) {
+ std::cerr << "Failed to close file: " << utils::getLastError().message() << std::endl;
+ }
+ file_handle_.reset();
+}
+
+} // namespace org::apache::nifi::minifi::utils
+
+#else
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <cstring>
+
+namespace org::apache::nifi::minifi::utils {
+
+FileMutex::FileMutex(std::filesystem::path path): path_(std::move(path)) {}
+
+void FileMutex::lock() {
+ std::lock_guard guard(mtx_);
+ gsl_Expects(!file_handle_.has_value());
+ int flags = O_RDWR | O_CREAT;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+ int fd = open(path_.string().c_str(), flags, 0644);
+ if (fd < 0) {
+ throw std::system_error{utils::getLastError(), "Failed to open file '" + path_.string() + "' to be locked"};
+ }
+
+ struct flock file_lock_info{};
+ file_lock_info.l_type = F_WRLCK;
+ int value = fcntl(fd, F_SETLK, &file_lock_info);
+ if (value == -1) {
+ const auto err = utils::getLastError();
+ std::string pid_str = "unknown";
+ std::array<char, 16> buffer;
+ size_t pid_str_size = 0;
+ ssize_t ret;
+ while ((ret = read(fd, buffer.data() + pid_str_size, buffer.size() - pid_str_size)) > 0) {
+ pid_str_size += ret;
+ }
+ if (ret < 0) {
+ std::cerr << "Failed to read file content: " << utils::getLastError().message() << std::endl;
+ } else {
+ pid_str = "'" + std::string(buffer.data(), pid_str_size) + "'";
+ }
+
+ if (close(fd) == -1) {
+ std::cerr << "Failed to close file after unsuccessful locking attempt: " << utils::getLastError().message() << std::endl;
+ }
+ throw std::system_error{err, "Failed to lock file '" + path_.string() + "', previous pid: " + pid_str};
+ }
+
+ const std::string pidstr = std::to_string(utils::OsUtils::getCurrentProcessId());
+ std::span<const char> buffer = pidstr;
+ while (!buffer.empty()) {
+ ssize_t ret = write(fd, buffer.data(), buffer.size());
+ if (ret < 0) {
+ const auto err = utils::getLastError();
+ if (close(fd) == -1) {
+ std::cerr << "Failed to close file after unsuccessful pid write attempt: " << utils::getLastError().message() << std::endl;
+ }
+ throw std::system_error{err, "Failed to write pid to lock file '" + path_.string() + "'"};
+ }
+ buffer = buffer.subspan(ret);
+ }
+
+ file_handle_ = fd;
+}
+
+void FileMutex::unlock() {
+ std::lock_guard guard(mtx_);
+ gsl_Expects(file_handle_.has_value());
+ auto file_guard = gsl::finally([&] {
+ if (close(file_handle_.value()) == -1) {
+ std::cerr << "Failed to close file after unlock: " << utils::getLastError().message() << std::endl;
+ }
+ file_handle_.reset();
+ });
+ struct flock file_lock_info{};
+ file_lock_info.l_type = F_UNLCK;
+ int value = fcntl(file_handle_.value(), F_SETLK, &file_lock_info);
+ if (value == -1) {
+ throw std::system_error{utils::getLastError(), "Failed to unlock file '" + path_.string() + "'"};
+ }
+}
+
+} // namespace org::apache::nifi::minifi::utils
+
+#endif
diff --git a/libminifi/src/utils/OsUtils.cpp b/libminifi/src/utils/OsUtils.cpp
index 708ce426f..4c2fc9a00 100644
--- a/libminifi/src/utils/OsUtils.cpp
+++ b/libminifi/src/utils/OsUtils.cpp
@@ -200,6 +200,14 @@ int64_t OsUtils::getCurrentProcessPhysicalMemoryUsage() {
#endif
}
+int64_t OsUtils::getCurrentProcessId() {
+#ifdef WIN32
+ return int64_t{GetCurrentProcessId()};
+#else
+ return int64_t{getpid()};
+#endif
+}
+
int64_t OsUtils::getSystemPhysicalMemoryUsage() {
#if defined(__linux__)
const std::string available_memory_prefix = "MemAvailable:";
diff --git a/libminifi/test/unit/FileMutexTests.cpp b/libminifi/test/unit/FileMutexTests.cpp
new file mode 100644
index 000000000..c00ad4086
--- /dev/null
+++ b/libminifi/test/unit/FileMutexTests.cpp
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef NDEBUG
+#include <cassert>
+#include <cstdlib>
+#include "utils/FileMutex.h"
+#include "utils/file/FileUtils.h"
+#include "utils/OsUtils.h"
+#include "../TestBase.h"
+
+namespace minifi = org::apache::nifi::minifi;
+
+int main(int argc, char* argv[]) {
+ if (argc == 2) {
+ std::cout << "Trying to lock file a second time '" << argv[1] << "', from pid: " << utils::OsUtils::getCurrentProcessId() << std::endl;
+ minifi::utils::FileMutex mtx{argv[1]};
+ try {
+ std::unique_lock lock{mtx};
+ } catch (const std::exception& ex) {
+ std::cerr << ex.what() << std::endl;
+ throw;
+ }
+ return 0;
+ }
+
+ TestController controller;
+ auto lock_file = controller.createTempDirectory() / "LOCK";
+
+ minifi::utils::FileMutex mtx{lock_file};
+ {
+ std::cout << "Locking file the first time '" << lock_file << "', from pid: " << utils::OsUtils::getCurrentProcessId() << std::endl;
+ std::unique_lock lock{mtx};
+
+ int second_lock = std::system((utils::file::get_executable_path().string() + " " + lock_file.string()).c_str()); // NOLINT
+
+ assert(second_lock != 0);
+ }
+ // unlocked file the other process can lock now
+ {
+ int second_lock = std::system((utils::file::get_executable_path().string() + " " + lock_file.string()).c_str()); // NOLINT
+
+ assert(second_lock == 0);
+ }
+}
diff --git a/minifi_main/MiNiFiMain.cpp b/minifi_main/MiNiFiMain.cpp
index 36b4205ae..1d596f4a4 100644
--- a/minifi_main/MiNiFiMain.cpp
+++ b/minifi_main/MiNiFiMain.cpp
@@ -62,6 +62,7 @@
#include "utils/file/PathUtils.h"
#include "utils/file/FileUtils.h"
#include "utils/Environment.h"
+#include "utils/FileMutex.h"
#include "FlowController.h"
#include "AgentDocs.h"
#include "MainHelper.h"
@@ -195,6 +196,14 @@ int main(int argc, char **argv) {
// determineMinifiHome already logged everything we need
return -1;
}
+ utils::FileMutex minifi_home_mtx(minifiHome / "LOCK");
+ std::unique_lock minifi_home_lock(minifi_home_mtx, std::defer_lock);
+ try {
+ minifi_home_lock.lock();
+ } catch (const std::exception& ex) {
+ logger->log_error("Could not acquire LOCK for minifi home '%s', maybe another minifi instance is running: %s", minifiHome.string(), ex.what());
+ std::exit(1);
+ }
// chdir to MINIFI_HOME
std::error_code current_path_error;
std::filesystem::current_path(minifiHome, current_path_error);