mirror of
https://github.com/falcosecurity/falco.git
synced 2026-04-11 22:43:18 +00:00
Compare commits
2 Commits
fix/cli-o-
...
fix/plugin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de93866101 | ||
|
|
f57b0e74f6 |
@@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
# Copyright (C) 2026 The Falco Authors.
|
||||
#
|
||||
# Licensed 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
|
||||
@@ -134,10 +134,17 @@ if(NOT DEFINED FALCO_COMPONENT_NAME)
|
||||
endif()
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX
|
||||
/usr
|
||||
CACHE PATH "Default install path" FORCE
|
||||
)
|
||||
if(WIN32)
|
||||
set(CMAKE_INSTALL_PREFIX
|
||||
"C:/Program Files/${CMAKE_PROJECT_NAME}"
|
||||
CACHE PATH "Default install path" FORCE
|
||||
)
|
||||
else()
|
||||
set(CMAKE_INSTALL_PREFIX
|
||||
/usr
|
||||
CACHE PATH "Default install path" FORCE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMD_MAKE make)
|
||||
|
||||
@@ -102,58 +102,6 @@ TEST(Configuration, modify_yaml_fields) {
|
||||
ASSERT_EQ(conf.get_scalar<bool>(key, false), true);
|
||||
}
|
||||
|
||||
static std::string escaped_keys_yaml =
|
||||
"annotations:\n"
|
||||
" kubernetes.io/role: master\n"
|
||||
" example[0]: bracket_val\n"
|
||||
"nested:\n"
|
||||
" level1.with.dots:\n"
|
||||
" inner: value\n"
|
||||
"escape_test:\n"
|
||||
" back\\slash: bslash_val\n";
|
||||
|
||||
TEST(Configuration, escaped_keys_read) {
|
||||
yaml_helper conf;
|
||||
conf.load_from_string(escaped_keys_yaml);
|
||||
|
||||
/* Key containing literal dots */
|
||||
ASSERT_STREQ(conf.get_scalar<std::string>("annotations.kubernetes\\.io/role", "none").c_str(),
|
||||
"master");
|
||||
|
||||
/* Key containing literal bracket */
|
||||
ASSERT_STREQ(conf.get_scalar<std::string>("annotations.example\\[0]", "none").c_str(),
|
||||
"bracket_val");
|
||||
|
||||
/* Multiple escaped dots */
|
||||
ASSERT_STREQ(conf.get_scalar<std::string>("nested.level1\\.with\\.dots.inner", "none").c_str(),
|
||||
"value");
|
||||
|
||||
/* Key containing literal backslash */
|
||||
ASSERT_STREQ(conf.get_scalar<std::string>("escape_test.back\\\\slash", "none").c_str(),
|
||||
"bslash_val");
|
||||
}
|
||||
|
||||
TEST(Configuration, escaped_keys_write) {
|
||||
yaml_helper conf;
|
||||
conf.load_from_string(escaped_keys_yaml);
|
||||
|
||||
/* Modify a value accessed via escaped key */
|
||||
conf.set_scalar<std::string>("annotations.kubernetes\\.io/role", "worker");
|
||||
ASSERT_STREQ(conf.get_scalar<std::string>("annotations.kubernetes\\.io/role", "none").c_str(),
|
||||
"worker");
|
||||
}
|
||||
|
||||
TEST(Configuration, escaped_keys_errors) {
|
||||
yaml_helper conf;
|
||||
conf.load_from_string(escaped_keys_yaml);
|
||||
|
||||
/* Trailing backslash */
|
||||
EXPECT_ANY_THROW(conf.get_scalar<std::string>("annotations\\", "none"));
|
||||
|
||||
/* Invalid escape sequence */
|
||||
EXPECT_ANY_THROW(conf.get_scalar<std::string>("annotations\\x", "none"));
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_webserver_ip) {
|
||||
falco_configuration falco_config;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
Copyright (C) 2026 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -146,6 +146,57 @@ 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Traversal via "./" prefix followed by ".." must also be rejected.
|
||||
config = R"(
|
||||
plugins:
|
||||
- name: evil
|
||||
library_path: ./../../tmp/evil.so
|
||||
)";
|
||||
EXPECT_THROW(falco_config.init_from_content(config, {}), std::exception);
|
||||
|
||||
// Nested traversal that descends then escapes must be rejected.
|
||||
config = R"(
|
||||
plugins:
|
||||
- name: evil
|
||||
library_path: subdir/../../../tmp/evil.so
|
||||
)";
|
||||
EXPECT_THROW(falco_config.init_from_content(config, {}), std::exception);
|
||||
|
||||
#ifndef _WIN32
|
||||
// Absolute paths bypass the prefix logic and are allowed as-is.
|
||||
// This test uses a Unix absolute path syntax.
|
||||
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");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Configuration, schema_yaml_helper_validator) {
|
||||
yaml_helper conf;
|
||||
falco_configuration falco_config;
|
||||
|
||||
@@ -369,23 +369,17 @@ private:
|
||||
* nested nodes, with arbitrary depth. The key string follows
|
||||
* this regular language:
|
||||
*
|
||||
* Key := NodeKey ('.' NodeKey)*
|
||||
* NodeKey := (Char | EscSeq)+ ('[' (integer)+? ']')*
|
||||
* EscSeq := '\' ('.' | '[' | '\')
|
||||
* Key := NodeKey ('.' NodeKey)*
|
||||
* NodeKey := (any)+ ('[' (integer)+? ']')*
|
||||
*
|
||||
* If can_append is true, an empty NodeKey will append a new entry
|
||||
* to the sequence, it is rejected otherwise.
|
||||
*
|
||||
* Backslash escaping allows literal dots, brackets, and backslashes
|
||||
* in key names, following the Helm --set conventions, e.g.:
|
||||
* falco -o 'annotations.kubernetes\.io/role=master'
|
||||
*
|
||||
* Some examples of accepted key strings:
|
||||
* - NodeName
|
||||
* - ListValue[3].subvalue
|
||||
* - MatrixValue[1][3]
|
||||
* - value1.subvalue2.subvalue3
|
||||
* - annotations.kubernetes\.io/role
|
||||
*/
|
||||
void get_node(YAML::Node& ret, const std::string& key, bool can_append = false) const {
|
||||
try {
|
||||
@@ -393,33 +387,6 @@ private:
|
||||
ret.reset(m_root);
|
||||
for(std::string::size_type i = 0; i < key.size(); ++i) {
|
||||
char c = key[i];
|
||||
|
||||
// Handle backslash escaping
|
||||
if(c == '\\') {
|
||||
if(i + 1 >= key.size()) {
|
||||
throw std::runtime_error("Parsing error: trailing backslash at pos " +
|
||||
std::to_string(i));
|
||||
}
|
||||
char next = key[i + 1];
|
||||
if(next != '.' && next != '[' && next != '\\') {
|
||||
throw std::runtime_error("Parsing error: invalid escape sequence '\\" +
|
||||
std::string(1, next) + "' at pos " +
|
||||
std::to_string(i));
|
||||
}
|
||||
nodeKey += next;
|
||||
++i;
|
||||
// If this escaped char is the last in the key, shift now
|
||||
if(i == key.size() - 1) {
|
||||
if(nodeKey.empty()) {
|
||||
throw std::runtime_error("Parsing error: unexpected character at pos " +
|
||||
std::to_string(i));
|
||||
}
|
||||
ret.reset(ret[nodeKey]);
|
||||
nodeKey.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bool should_shift = c == '.' || c == '[' || i == key.size() - 1;
|
||||
|
||||
if(c != '.' && c != '[') {
|
||||
|
||||
@@ -155,7 +155,7 @@ void options::define(cxxopts::Options& opts)
|
||||
("markdown", "DEPRECATED: use --format markdown instead. Print output in Markdown format when used in conjunction with --list or --list-events options. It has no effect when used with other options.", cxxopts::value<bool>(markdown))
|
||||
("format", "Print output in the specified <format> when used in conjunction with --list or --list-events options. Valid values are 'text', 'markdown', or 'json'. It has no effect when used with other options. Cannot be used together with --markdown.", cxxopts::value(format), "<format>")
|
||||
("N", "Only print field names when used in conjunction with the --list option. It has no effect when used with other options.", cxxopts::value(names_only)->default_value("false"))
|
||||
("o,option", "Set the value of option <opt> to <val>. Overrides values in the configuration file. <opt> can be identified using its location in the configuration file using dot notation. Elements of list entries can be accessed via square brackets []. Use backslash (\\) to escape literal dots or brackets in key names.\n E.g. base.id=val\n base.subvalue.subvalue2=val\n base.list[1]=val\n base.dotted\\.key=val", cxxopts::value(cmdline_config_options), "<opt>=<val>")
|
||||
("o,option", "Set the value of option <opt> to <val>. Overrides values in the configuration file. <opt> can be identified using its location in the configuration file using dot notation. Elements of list entries can be accessed via square brackets [].\n E.g. base.id = val\n base.subvalue.subvalue2 = val\n base.list[1]=val", cxxopts::value(cmdline_config_options), "<opt>=<val>")
|
||||
("plugin-info", "Print info for the plugin specified by <plugin_name> and exit.\nThis includes all descriptive information like name and author, along with the\nschema format for the init configuration and a list of suggested open parameters.\n<plugin_name> can be the plugin's name or its configured 'library_path'.", cxxopts::value(print_plugin_info), "<plugin_name>")
|
||||
("p,print", "DEPRECATED: use -o append_output... instead. Print additional information in the rule's output.\nUse -pc or -pcontainer to append container details to syscall events.\nUse -pk or -pkubernetes to add both container and Kubernetes details to syscall events.\nThe details will be directly appended to the rule's output.\nAlternatively, use -p <output_format> for a custom format. In this case, the given <output_format> will be appended to the rule's output without any replacement to all events, including plugin events.", cxxopts::value(print_additional), "<output_format>")
|
||||
("P,pidfile", "Write PID to specified <pid_file> path. By default, no PID file is created.", cxxopts::value(pidfilename)->default_value(""), "<pid_file>")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2025 The Falco Authors.
|
||||
Copyright (C) 2026 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ limitations under the License.
|
||||
#include <set>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
#include "config_falco.h"
|
||||
#include "yaml_helper.h"
|
||||
@@ -400,9 +401,35 @@ struct convert<falco_configuration::plugin_config> {
|
||||
return false;
|
||||
}
|
||||
rhs.m_library_path = node["library_path"].as<std::string>();
|
||||
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;
|
||||
if(!rhs.m_library_path.empty() &&
|
||||
!std::filesystem::path(rhs.m_library_path).is_absolute() &&
|
||||
rhs.m_library_path.at(0) != '/') {
|
||||
// Relative path: resolve against the plugins directory
|
||||
// and verify the result stays within it.
|
||||
auto full_path = std::filesystem::path(FALCO_ENGINE_PLUGINS_DIR) / rhs.m_library_path;
|
||||
// lexically_normal resolves . and .. purely lexically,
|
||||
// without filesystem access (unlike weakly_canonical which
|
||||
// leaves .. unresolved for non-existent path components).
|
||||
auto normalized = full_path.lexically_normal();
|
||||
auto plugins_dir = std::filesystem::path(FALCO_ENGINE_PLUGINS_DIR).lexically_normal();
|
||||
auto rel = normalized.lexically_relative(plugins_dir);
|
||||
if(rel.empty()) {
|
||||
throw YAML::Exception(node["library_path"].Mark(),
|
||||
"plugin library_path '" +
|
||||
node["library_path"].as<std::string>() +
|
||||
"' resolves outside the plugins directory (" +
|
||||
std::string(FALCO_ENGINE_PLUGINS_DIR) + ")");
|
||||
}
|
||||
for(const auto& component : rel) {
|
||||
if(component == "..") {
|
||||
throw YAML::Exception(node["library_path"].Mark(),
|
||||
"plugin library_path '" +
|
||||
node["library_path"].as<std::string>() +
|
||||
"' resolves outside the plugins directory (" +
|
||||
std::string(FALCO_ENGINE_PLUGINS_DIR) + ")");
|
||||
}
|
||||
}
|
||||
rhs.m_library_path = normalized.string();
|
||||
}
|
||||
|
||||
if(node["init_config"] && !node["init_config"].IsNull()) {
|
||||
|
||||
Reference in New Issue
Block a user