From: Oleksandr Stepanov -X (ostepano - SOFTSERVE INC at Cisco) Date: Thu, 18 Sep 2025 19:27:45 +0000 (+0000) Subject: Pull request #4895: appid: add setUserDetectorDataItem lua detector API X-Git-Tag: 3.9.6.0~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d5f66d31f3f5482f9077e1f20fd9fb03beef0c07;p=thirdparty%2Fsnort3.git Pull request #4895: appid: add setUserDetectorDataItem lua detector API Merge in SNORT/snort3 from ~OSTEPANO/snort3:user_data_lua to master Squashed commit of the following: commit 37c1d2245679348f43b571307d9bb50a4ae96e91 Author: Oleksandr Stepanov Date: Thu Sep 4 10:34:36 2025 -0400 appid: add setUserDetectorDataItem lua detector API --- diff --git a/src/network_inspectors/appid/app_info_table.cc b/src/network_inspectors/appid/app_info_table.cc index 10e83554b..15977471a 100644 --- a/src/network_inspectors/appid/app_info_table.cc +++ b/src/network_inspectors/appid/app_info_table.cc @@ -721,7 +721,7 @@ void AppInfoManager::load_odp_config(OdpContext& odp_ctxt, const char* path) const std::string user_table(conf_val); const std::string user_key(token); const std::string user_value(token2); - odp_ctxt.get_user_data_map().add_user_data(user_table, user_key, user_value); + odp_ctxt.get_user_data_map().add_user_data(user_table, user_key, user_value, false); } else ParseWarning(WARN_CONF, "appid: unsupported configuration: %s\n", conf_key); diff --git a/src/network_inspectors/appid/lua_detector_api.cc b/src/network_inspectors/appid/lua_detector_api.cc index bc28f9302..017631e34 100644 --- a/src/network_inspectors/appid/lua_detector_api.cc +++ b/src/network_inspectors/appid/lua_detector_api.cc @@ -3344,6 +3344,37 @@ static int get_user_detector_data_item(lua_State *L) return 1; } +static int set_user_detector_data_item(lua_State *L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + const char* table = lua_tostring(L, 2); + if (!table) + { + APPID_LOG(nullptr, TRACE_ERROR_LEVEL, "appid: Invalid detector data table string in %s.\n", + ud->get_detector()->get_name().c_str()); + return 0; + } + const char* key = lua_tostring(L, 3); + if (!key) + { + APPID_LOG(nullptr, TRACE_ERROR_LEVEL, "appid: Invalid detector data key string in %s.\n", + ud->get_detector()->get_name().c_str()); + return 0; + } + + const char* item = lua_tostring(L, 4); + if (!item) + { + APPID_LOG(nullptr, TRACE_ERROR_LEVEL, "appid: Invalid detector data item string in %s.\n", + ud->get_detector()->get_name().c_str()); + return 0; + } + + int result = ud->get_odp_ctxt().get_user_data_map().add_user_data(table, key, item, true) ? 1 : 0; + + return result; +} + static const luaL_Reg detector_methods[] = { /* Obsolete API names. No longer use these! They are here for backward @@ -3466,6 +3497,7 @@ static const luaL_Reg detector_methods[] = { "getHttpTunneledPort", get_http_tunneled_port }, { "getUserDetectorDataItem", get_user_detector_data_item }, + { "setUserDetectorDataItem", set_user_detector_data_item }, /* CIP registration */ {"addCipConnectionClass", detector_add_cip_connection_class}, diff --git a/src/network_inspectors/appid/lua_detector_module.cc b/src/network_inspectors/appid/lua_detector_module.cc index 6f3f5c4ec..8d8911392 100644 --- a/src/network_inspectors/appid/lua_detector_module.cc +++ b/src/network_inspectors/appid/lua_detector_module.cc @@ -227,6 +227,7 @@ LuaDetectorManager::~LuaDetectorManager() void LuaDetectorManager::initialize(const SnortConfig* sc) { + ctxt.get_odp_ctxt().get_user_data_map().set_configuration_completed(false); activate_lua_detectors(sc); if (SnortConfig::log_verbose()) @@ -515,6 +516,7 @@ void LuaDetectorManager::activate_lua_detectors(const SnortConfig* sc) lua_settop(L, 0); ++lo; } + ctxt.get_odp_ctxt().get_user_data_map().set_configuration_completed(true); } void ControlLuaDetectorManager::process_detector_file(char* detector_file_path, bool is_custom) { diff --git a/src/network_inspectors/appid/test/CMakeLists.txt b/src/network_inspectors/appid/test/CMakeLists.txt index aac350793..77dbe7495 100644 --- a/src/network_inspectors/appid/test/CMakeLists.txt +++ b/src/network_inspectors/appid/test/CMakeLists.txt @@ -48,6 +48,10 @@ add_cpputest( appid_eve_process_event_handler_test SOURCES $ ) +add_cpputest( user_data_map_test + SOURCES ../user_data_map.cc +) + add_cpputest( tp_lib_handler_test SOURCES tp_lib_handler_test.cc diff --git a/src/network_inspectors/appid/test/user_data_map_test.cc b/src/network_inspectors/appid/test/user_data_map_test.cc new file mode 100644 index 000000000..125d8fe0a --- /dev/null +++ b/src/network_inspectors/appid/test/user_data_map_test.cc @@ -0,0 +1,169 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2018-2025 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// user_data_map_test.cc author Oleksandr Stepanov + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main/thread.h" + +#include "../user_data_map.h" + +#include +#include + +static uint appid_log_call_count = 0; +void appid_log(const snort::Packet*, unsigned char, char const*, ...) { appid_log_call_count++; } + +SThreadType test_thread_type = SThreadType::STHREAD_TYPE_MAIN; + +namespace snort +{ + SThreadType get_thread_type() + { + return test_thread_type; + } +} + +TEST_GROUP(user_data_map_test) +{ + UserDataMap* test_user_data_map; + + void setup() override + { + test_user_data_map = new UserDataMap(); + test_user_data_map->set_configuration_completed(false); + } + + void teardown() override + { + test_user_data_map->set_configuration_completed(false); + delete test_user_data_map; + } +}; + +TEST(user_data_map_test, add_and_get_user_data) +{ + const std::string table = "test_table"; + const std::string key = "test_key"; + const std::string value = "test_value"; + + bool added = test_user_data_map->add_user_data(table, key, value); + CHECK_TRUE(added); + + const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, key); + STRCMP_EQUAL(value.c_str(), retrieved_value); +} + +TEST(user_data_map_test, add_duplicate_key_without_override) +{ + appid_log_call_count = 0; + const std::string table = "test_table"; + const std::string key = "test_key"; + const std::string value1 = "test_value1"; + const std::string value2 = "test_value2"; + + bool first_add = test_user_data_map->add_user_data(table, key, value1); + CHECK_TRUE(first_add); + + bool second_add = test_user_data_map->add_user_data(table, key, value2); + CHECK_EQUAL(1, appid_log_call_count); + CHECK_FALSE(second_add); + + auto get_value = test_user_data_map->get_user_data_value_str(table, key); + STRCMP_EQUAL(get_value, value1.c_str()); +} + +TEST(user_data_map_test, add_duplicate_key_with_override) +{ + const std::string table = "test_table"; + const std::string key = "test_key"; + const std::string value1 = "test_value1"; + const std::string value2 = "test_value2"; + + bool first_add = test_user_data_map->add_user_data(table, key, value1); + CHECK_TRUE(first_add); + + bool second_add = test_user_data_map->add_user_data(table, key, value2, true); + const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, key); + CHECK_TRUE(second_add); + STRCMP_EQUAL(value2.c_str(), retrieved_value); +} + +TEST(user_data_map_test, get_nonexistent_key) +{ + const std::string table = "test_table"; + const std::string key = "test_key"; + const std::string value = "test_value"; + + test_user_data_map->add_user_data(table, key, value); + + const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, "some_other_key"); + CHECK_TRUE(retrieved_value == nullptr); +} + +TEST(user_data_map_test, get_from_nonexistent_table) +{ + const std::string table = "nonexistent_table"; + const std::string key = "test_key"; + + const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, key); + CHECK_TRUE(retrieved_value == nullptr); +} + +TEST(user_data_map_test, add_user_data_from_non_main_thread_before_configuration_completed) +{ + test_thread_type = SThreadType::STHREAD_TYPE_PACKET; + appid_log_call_count = 0; + + const std::string table = "test_table"; + const std::string key = "test_key"; + const std::string value = "test_value"; + + bool added = test_user_data_map->add_user_data(table, key, value); + CHECK_FALSE(added); + CHECK_EQUAL(0, appid_log_call_count); + + test_thread_type = SThreadType::STHREAD_TYPE_MAIN; +} + +TEST(user_data_map_test, add_user_data_from_non_main_thread_after_configuration_completed) +{ + test_thread_type = SThreadType::STHREAD_TYPE_PACKET; + appid_log_call_count = 0; + + const std::string table = "test_table"; + const std::string key = "test_key"; + const std::string value = "test_value"; + + test_user_data_map->set_configuration_completed(true); + bool added = test_user_data_map->add_user_data(table, key, value); + CHECK_FALSE(added); + CHECK_EQUAL(1, appid_log_call_count); + + test_thread_type = SThreadType::STHREAD_TYPE_MAIN; +} + +int main(int argc, char** argv) +{ + int rc = CommandLineTestRunner::RunAllTests(argc, argv); + + return rc; +} diff --git a/src/network_inspectors/appid/user_data_map.cc b/src/network_inspectors/appid/user_data_map.cc index 0ef923446..c185ff862 100644 --- a/src/network_inspectors/appid/user_data_map.cc +++ b/src/network_inspectors/appid/user_data_map.cc @@ -24,23 +24,44 @@ #include "user_data_map.h" +#include "main/thread.h" + +static THREAD_LOCAL bool configuration_completed; + UserDataMap::~UserDataMap() { user_data_maps.clear(); } -void UserDataMap::add_user_data(const std::string& table, const std::string& key, - const std::string& item) +bool UserDataMap::add_user_data(const std::string &table, const std::string &key, + const std::string &item, bool override_existing) { - if (user_data_maps.find(table) != user_data_maps.end()) + + if (snort::get_thread_type() != SThreadType::STHREAD_TYPE_MAIN) { - if (user_data_maps[table].find(key) != user_data_maps[table].end()) - { - APPID_LOG(nullptr, TRACE_WARNING_LEVEL,"ignoring duplicate key %s in table %s", + if (configuration_completed) + APPID_LOG(nullptr, TRACE_WARNING_LEVEL, "AppId: ignoring user data with key %s in table %s from non-main thread\n", key.c_str(), table.c_str()); - return; + return false; + } + + auto table_it = user_data_maps.find(table); + if (table_it != user_data_maps.end()) + { + if (override_existing) + { + table_it->second[key] = item; + } + else + { + auto insert_result = table_it->second.try_emplace(key, item); + if (insert_result.second == false) + { + APPID_LOG(nullptr, TRACE_WARNING_LEVEL, "AppId: ignoring duplicate key %s in table %s\n", + key.c_str(), table.c_str()); + return false; + } } - user_data_maps[table][key] = item; } else { @@ -48,16 +69,27 @@ void UserDataMap::add_user_data(const std::string& table, const std::string& key user_map[key] = item; user_data_maps[table] = user_map; } + + return true; } const char* UserDataMap::get_user_data_value_str(const std::string& table, const std::string& key) { - if (user_data_maps.find(table) != user_data_maps.end() and - user_data_maps[table].find(key) != user_data_maps[table].end()) + auto table_it = user_data_maps.find(table); + if (table_it != user_data_maps.end()) { - return user_data_maps[table][key].c_str(); + auto key_it = table_it->second.find(key); + if (key_it != table_it->second.end()) + { + return key_it->second.c_str(); + } } - else - return nullptr; + + return nullptr; +} + +void UserDataMap::set_configuration_completed(bool completed) +{ + configuration_completed = completed; } diff --git a/src/network_inspectors/appid/user_data_map.h b/src/network_inspectors/appid/user_data_map.h index 7f4ef20fa..d2b207462 100644 --- a/src/network_inspectors/appid/user_data_map.h +++ b/src/network_inspectors/appid/user_data_map.h @@ -23,9 +23,9 @@ /* User Data Map uses an unordered map to store arbitrary user-defined key value pairs * used in lua detectors. Mappings are loaded from appid.conf or userappid.conf using a - * key that is hardcoded in the detector. The user supplies the value. At runtime, if the lua - * detector's conditions are met during validation, the lua detector can use its key to - * retrieve the customer data. + * key that is hardcoded in the detector or loaded from lua detectors that utilize setUserDetectorDataItem API. + * The user supplies the value. At runtime, if the lua detector's conditions are met during validation, + * the lua detector can use its key to retrieve the customer data. */ #include @@ -42,9 +42,11 @@ class UserDataMap { public: ~UserDataMap(); - void add_user_data(const std::string& table, const std::string& key, - const std::string& item); + bool add_user_data(const std::string& table, const std::string& key, + const std::string& item, bool override_existing = false); const char* get_user_data_value_str(const std::string& table, const std::string& key); + + void set_configuration_completed(bool completed); private: UserDataMaps user_data_maps; };