From f57b0e74f61bf9e4a5f83982eb8dfae6d93bb40a Mon Sep 17 00:00:00 2001 From: Leonardo Grasso Date: Wed, 8 Apr 2026 15:40:14 +0200 Subject: [PATCH] fix(config): prevent plugin library path traversal via relative paths Signed-off-by: Leonardo Grasso --- .../falco/test_configuration_schema.cpp | 35 +++++++++++++++++++ userspace/falco/configuration.h | 17 +++++++++ 2 files changed, 52 insertions(+) diff --git a/unit_tests/falco/test_configuration_schema.cpp b/unit_tests/falco/test_configuration_schema.cpp index 85da84c5..0556221f 100644 --- a/unit_tests/falco/test_configuration_schema.cpp +++ b/unit_tests/falco/test_configuration_schema.cpp @@ -146,6 +146,41 @@ plugins: EXPECT_EQ(falco_config.m_plugins[0].m_init_config, ""); } +TEST(Configuration, plugin_library_path_traversal) { + falco_configuration falco_config; + config_loaded_res res; + + // A relative path that stays within the plugins dir should succeed. + std::string config = R"( +plugins: + - name: myplugin + library_path: libmyplugin.so +)"; + EXPECT_NO_THROW(res = falco_config.init_from_content(config, {})); + EXPECT_VALIDATION_STATUS(res, yaml_helper::validation_ok); + // The resolved path must start with the plugins dir. + EXPECT_TRUE(sinsp_utils::startswith(falco_config.m_plugins[0].m_library_path, + FALCO_ENGINE_PLUGINS_DIR)); + + // A relative path with ".." that escapes the plugins dir must be rejected. + config = R"( +plugins: + - name: evil + library_path: ../../tmp/evil.so +)"; + EXPECT_THROW(falco_config.init_from_content(config, {}), std::exception); + + // Absolute paths bypass the prefix logic and are allowed as-is. + config = R"( +plugins: + - name: myplugin + library_path: /opt/falco/plugins/libmyplugin.so +)"; + EXPECT_NO_THROW(res = falco_config.init_from_content(config, {})); + EXPECT_VALIDATION_STATUS(res, yaml_helper::validation_ok); + EXPECT_EQ(falco_config.m_plugins[0].m_library_path, "/opt/falco/plugins/libmyplugin.so"); +} + TEST(Configuration, schema_yaml_helper_validator) { yaml_helper conf; falco_configuration falco_config; diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 68baea7d..0ab5b670 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -31,6 +31,7 @@ limitations under the License. #include #include #include +#include #include "config_falco.h" #include "yaml_helper.h" @@ -403,6 +404,22 @@ struct convert { if(!rhs.m_library_path.empty() && rhs.m_library_path.at(0) != '/') { // prepend share dir if path is not absolute rhs.m_library_path = std::string(FALCO_ENGINE_PLUGINS_DIR) + rhs.m_library_path; + // Canonicalize to resolve ".." components and verify + // the resulting path stays within the plugins directory. + auto canonical_str = std::filesystem::weakly_canonical(rhs.m_library_path).string(); + auto plugins_dir_str = + std::filesystem::weakly_canonical(FALCO_ENGINE_PLUGINS_DIR).string(); + if(!plugins_dir_str.empty() && plugins_dir_str.back() != '/') { + plugins_dir_str += '/'; + } + if(canonical_str.compare(0, plugins_dir_str.size(), plugins_dir_str) != 0) { + throw YAML::Exception(node["library_path"].Mark(), + "plugin library_path '" + + node["library_path"].as() + + "' resolves outside the plugins directory (" + + std::string(FALCO_ENGINE_PLUGINS_DIR) + ")"); + } + rhs.m_library_path = canonical_str; } if(node["init_config"] && !node["init_config"].IsNull()) {