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 <[email protected]>
AuthorDate: Fri Jun 30 07:33:14 2023 +0200

    MINIFICPP-2020 Protect MINIFI_HOME from mutual access
    
    Closes #1577
    Signed-off-by: Marton Szasz <[email protected]>
---
 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);

Reply via email to