From cfb52270623a583fcec885201cdd037c975f6eb7 Mon Sep 17 00:00:00 2001 From: Leonardo Grasso Date: Tue, 24 Mar 2026 12:50:53 +0100 Subject: [PATCH] fix(userspace): add backslash escaping to `-o` key-path parser Signed-off-by: Leonardo Grasso --- unit_tests/falco/test_configuration.cpp | 52 +++++++++++++++++++++++++ userspace/engine/yaml_helper.h | 37 +++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/unit_tests/falco/test_configuration.cpp b/unit_tests/falco/test_configuration.cpp index 5162a864..c785be67 100644 --- a/unit_tests/falco/test_configuration.cpp +++ b/unit_tests/falco/test_configuration.cpp @@ -102,6 +102,58 @@ TEST(Configuration, modify_yaml_fields) { ASSERT_EQ(conf.get_scalar(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("annotations.kubernetes\\.io/role", "none").c_str(), + "master"); + + /* Key containing literal bracket */ + ASSERT_STREQ(conf.get_scalar("annotations.example\\[0]", "none").c_str(), + "bracket_val"); + + /* Multiple escaped dots */ + ASSERT_STREQ(conf.get_scalar("nested.level1\\.with\\.dots.inner", "none").c_str(), + "value"); + + /* Key containing literal backslash */ + ASSERT_STREQ(conf.get_scalar("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("annotations.kubernetes\\.io/role", "worker"); + ASSERT_STREQ(conf.get_scalar("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("annotations\\", "none")); + + /* Invalid escape sequence */ + EXPECT_ANY_THROW(conf.get_scalar("annotations\\x", "none")); +} + TEST(Configuration, configuration_webserver_ip) { falco_configuration falco_config; diff --git a/userspace/engine/yaml_helper.h b/userspace/engine/yaml_helper.h index 667e8be5..05f88a06 100644 --- a/userspace/engine/yaml_helper.h +++ b/userspace/engine/yaml_helper.h @@ -369,17 +369,23 @@ private: * nested nodes, with arbitrary depth. The key string follows * this regular language: * - * Key := NodeKey ('.' NodeKey)* - * NodeKey := (any)+ ('[' (integer)+? ']')* + * Key := NodeKey ('.' NodeKey)* + * NodeKey := (Char | EscSeq)+ ('[' (integer)+? ']')* + * EscSeq := '\' ('.' | '[' | '\') * * 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 { @@ -387,6 +393,33 @@ 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 != '[') {