active = { max_responses = 1, min_interval = 5 }
+==== Whitelist
+
+When Snort is run with the --warn-conf-strict option, warnings will be
+generated for all Lua tables present in the configuration files that do
+not map to Snort module names. Like with other warnings, these will
+upgraded to errors when Snort is run in pedantic mode.
+
+To dynamically add exceptions that should bypass this strict validation,
+two Lua functions are made available to be called during the evaluation
+of Snort configuration files: snort_whitelist_append() and
+snort_whitelist_add_prefix(). Each function takes a whitespace-delimited
+list, the former a list of exact table names and the latter a list of table
+name prefixes to allow.
+
+Examples:
+snort_whitelist_append("table1 table2")
+snort_whitelist_add_prefix("local_ foobar_")
+
+The accumulated contents of the whitelist (both exact and prefix) will be
+dumped when Snort is run in verbose mode (-v).
+
==== Rules
Rules determine what Snort is looking for. They can be put directly in
{ type = 'HWP', id = 323, category = 'Executables', msg = 'Hangul word processor file', rev = 1, version = '3.0', magic = { { content = '| 48 57 50 20 44 6F 63 75 6D 65 6E 74 20 46 69 6C 65 |', offset = 0, }, }, },
{ type = 'SWF', id = 324, category = 'Multimedia', msg = 'Flash file', rev = 1, magic = { { content = '| 5A 57 53 |', offset = 0}, }, },
}
+
+snort_whitelist_append("file_magic")
icmp_sweep = icmp_low_sweep,
}
+---------------------------------------------------------------------------
+-- default whitelist
+---------------------------------------------------------------------------
+default_whitelist =
+[[
+ ftp_command_specs default_ftp_server smtp_default_alt_max_command_lines
+ default_smtp http_methods sip_methods telnet_commands default_wizard
+ default_references default_classifications gtp_v0_msg gtp_v1_msg gtp_v2_msg
+ gtp_v0_info gtp_v1_info gtp_v2_info default_gtp tcp_low_ports
+ tcp_low_decoy tcp_low_sweep tcp_low_dist tcp_med_ports
+ tcp_med_decoy tcp_med_sweep tcp_med_dist tcp_hi_ports tcp_hi_decoy
+ tcp_hi_sweep tcp_hi_dist udp_low_ports udp_low_decoy udp_low_sweep
+ udp_low_dist udp_med_ports udp_med_decoy udp_med_sweep udp_med_dist
+ udp_hi_ports udp_hi_decoy udp_hi_sweep udp_hi_dist ip_low_proto
+ ip_low_decoy ip_low_sweep ip_low_dist ip_med_proto ip_med_decoy
+ ip_med_sweep ip_med_dist ip_hi_proto ip_hi_decoy ip_hi_sweep
+ ip_hi_dist icmp_low_sweep icmp_med_sweep icmp_hi_sweep
+ default_hi_port_scan default_med_port_scan default_low_port_scan
+]]
+
+snort_whitelist_append(default_whitelist)
+
enum WarningGroup
{
- WARN_DAQ, WARN_CONF, WARN_VARS, WARN_SYMBOLS, WARN_SCRIPTS,
- WARN_HOSTS, WARN_RULES, WARN_FLOWBITS, WARN_PLUGINS,
+ WARN_DAQ, WARN_CONF, WARN_CONF_STRICT, WARN_VARS,
+ WARN_SYMBOLS, WARN_SCRIPTS, WARN_HOSTS, WARN_RULES,
+ WARN_FLOWBITS, WARN_PLUGINS,
#ifdef PIGLET
WARN_PIGLET,
#endif
#include "managers/module_manager.h"
#include "parser/parse_conf.h"
#include "parser/parser.h"
+#include "utils/stats.h"
using namespace snort;
using namespace std;
// helper functions
//-------------------------------------------------------------------------
+string Shell::fatal;
+std::stack<Shell*> Shell::current_shells;
+
// FIXIT-M Shell::panic() works on Linux but on OSX we can't throw from lua
// to C++. unprotected lua calls could be wrapped in a pcall to ensure lua
// panics don't kill the process. or we can not use lua for the shell. :(
-
-string Shell::fatal;
-
[[noreturn]] int Shell::panic(lua_State* L)
{
fatal = lua_tostring(L, -1);
throw runtime_error(fatal);
}
+Shell* Shell::get_current_shell()
+{
+ if ( !current_shells.empty() )
+ return current_shells.top();
+
+ return nullptr;
+}
+
+bool Shell::is_whitelisted(const std::string& key)
+{
+ Shell* sh = Shell::get_current_shell();
+
+ if ( !sh )
+ return false;
+
+ const Whitelist& whitelist = sh->get_whitelist();
+ const Whitelist& whitelist_prefixes = sh->get_whitelist_prefixes();
+
+ for ( const auto& prefix : whitelist_prefixes )
+ {
+ if (key.compare(0, prefix.length(), prefix) == 0)
+ return true;
+ }
+
+ if ( whitelist.find(key) != whitelist.end() )
+ return true;
+
+ return false;
+}
+
+void Shell::whitelist_append(const char* keyword, bool is_prefix)
+{
+ Shell* sh = Shell::get_current_shell();
+
+ if ( !sh )
+ return;
+
+ sh->whitelist_update(keyword, is_prefix);
+}
+
// FIXIT-L shell --pause should stop before loading config so Lua state
// can be examined and modified.
FatalError("can't init overrides: %s\n", lua_tostring(L, -1));
}
-static void run_config(lua_State* L, const char* t)
-{
- Lua::ManageStack ms(L);
-
- lua_getglobal(L, "snort_config");
- lua_getglobal(L, t);
-
- assert(lua_isfunction(L, -2));
-
- if ( lua_pcall(L, 1, 1, 0) )
- {
- const char* err = lua_tostring(L, -1);
- FatalError("%s\n", err);
- }
-}
-
-static bool config_lua(
- lua_State* L, const char* file, string& s, const char* tweaks, bool is_fatal)
-{
- if ( file && *file )
- if (!load_config(L, file, tweaks, is_fatal))
- return false;
-
- if ( !s.empty() )
- load_string(L, s.c_str());
-
- run_config(L, "_G");
-
- return true;
-}
//-------------------------------------------------------------------------
// public methods
if ( !lua )
FatalError("Lua state instantiation failed\n");
+ current_shells.push(this);
+
lua_atpanic(lua, Shell::panic);
luaL_openlibs(lua);
if ( load_defaults )
load_string(lua, ModuleManager::get_lua_coreinit());
+
+ current_shells.pop();
}
Shell::~Shell()
push_parse_location(code, path.c_str(), file.c_str(), 0);
- if ( !config_lua(lua, path.c_str(), overrides, sc->tweaks.c_str(), is_fatal) )
+ current_shells.push(this);
+
+ if (!path.empty() and !load_config(lua, path.c_str(), sc->tweaks.c_str(), is_fatal))
+ {
+ current_shells.pop();
return false;
+ }
+
+ if ( !overrides.empty() )
+ load_string(lua, overrides.c_str());
+
+ if ( SnortConfig::log_verbose() )
+ print_whitelist();
+
+ load_string(lua, ModuleManager::get_lua_finalize());
+
+ clear_whitelist();
+ current_shells.pop();
set_default_policy(sc);
ModuleManager::set_config(nullptr);
loaded = true;
pop_parse_location();
+
return true;
}
}
}
+//-------------------------------------------------------------------------
+// private methods
+//-------------------------------------------------------------------------
+
+void Shell::print_whitelist() const
+{
+ std::string output;
+ if ( !whitelist.empty() )
+ {
+ output = "Lua Whitelist Keywords for " + file + ":";
+ LogMessage("\t%s\n",output.c_str());
+ for ( const auto& wl : whitelist )
+ LogMessage("\t\t%s\n", wl.c_str());
+ }
+
+ if ( !whitelist_prefixes.empty() )
+ {
+ output = "Lua Whitelist Prefixes for " + file + ":";
+ LogMessage("\t%s\n",output.c_str());
+ for ( const auto& wlp : whitelist_prefixes )
+ LogMessage("\t\t%s\n", wlp.c_str());
+ }
+}
+
+void Shell::whitelist_update(const char* s, bool is_prefix)
+{
+ Whitelist* wlist = nullptr;
+ if ( is_prefix )
+ wlist = &whitelist_prefixes;
+ else
+ wlist = &whitelist;
+
+ if ( s )
+ wlist->emplace(s);
+}
+
// Shell encapsulates a Lua state. There is one for each policy file.
+#include <set>
+#include <stack>
#include <string>
struct lua_State;
class Shell
{
public:
+ typedef std::set<std::string> Whitelist;
+
Shell(const char* file = nullptr, bool load_defaults = false);
~Shell();
bool get_loaded() const
{ return loaded; }
+public:
+ static bool is_whitelisted(const std::string& key);
+ static void whitelist_append(const char* keyword, bool is_prefix);
+
private:
[[noreturn]] static int panic(lua_State*);
+ static Shell* get_current_shell();
+
+private:
static std::string fatal;
+ static std::stack<Shell*> current_shells;
+
+private:
+ void clear_whitelist()
+ {
+ whitelist.clear();
+ whitelist_prefixes.clear();
+ }
+
+ const Whitelist& get_whitelist() const
+ { return whitelist; }
+
+ const Whitelist& get_whitelist_prefixes() const
+ { return whitelist_prefixes; }
+
+ void print_whitelist() const;
+ void whitelist_update(const char* keyword, bool is_prefix);
private:
bool loaded;
std::string file;
std::string parse_from;
std::string overrides;
+ Whitelist whitelist;
+ Whitelist whitelist_prefixes;
};
#endif
return nullptr;
}
+ if ( SnortConfig::log_verbose() )
+ InspectorManager::print_config(sc);
+
if ((sc->file_mask != 0) && (sc->file_mask != SnortConfig::get_conf()->file_mask))
umask(sc->file_mask);
{ "--warn-conf", Parameter::PT_IMPLIED, nullptr, nullptr,
"warn about configuration issues" },
+ { "--warn-conf-strict", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "warn about unrecognized elements in configuration files" },
+
{ "--warn-daq", Parameter::PT_IMPLIED, nullptr, nullptr,
"warn about DAQ issues, usually related to mode" },
else if ( v.is("--warn-conf") )
sc->warning_flags |= (1 << WARN_CONF);
+ else if ( v.is("--warn-conf-strict") )
+ sc->warning_flags |= (1 << WARN_CONF_STRICT);
+
else if ( v.is("--warn-daq") )
sc->warning_flags |= (1 << WARN_DAQ);
set (CPP_INCLUDES
${CMAKE_CURRENT_BINARY_DIR}/lua_bootstrap.h
${CMAKE_CURRENT_BINARY_DIR}/lua_coreinit.h
+ ${CMAKE_CURRENT_BINARY_DIR}/lua_finalize.h
)
set( MANAGERS_INCLUDES
OUTPUT lua_bootstrap.h snort_config.lua
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lua_wrap.sh ${CMAKE_CURRENT_SOURCE_DIR} bootstrap > lua_bootstrap.h
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bootstrap.lua ${CMAKE_CURRENT_BINARY_DIR}/snort_config.lua
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bootstrap.lua
+)
+
+add_custom_command (
+ OUTPUT lua_finalize.h
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lua_wrap.sh ${CMAKE_CURRENT_SOURCE_DIR} finalize > lua_finalize.h
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/finalize.lua
)
add_custom_command (
OUTPUT lua_coreinit.h
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lua_wrap.sh ${CMAKE_CURRENT_SOURCE_DIR} coreinit > lua_coreinit.h
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/coreinit.lua
)
include_directories (${CMAKE_CURRENT_BINARY_DIR})
-- with this program; if not, write to the Free Software Foundation, Inc.,
-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
---------------------------------------------------------------------------
--- snort_config.lua author Russ Combs <rucombs@cisco.com>
+-- bootstrap.lua author Russ Combs <rucombs@cisco.com>
---------------------------------------------------------------------------
-- Snort uses this to configure Lua settings into C++
ffi = require("ffi")
ffi.cdef[[
-bool open_table(const char*, int);
-void close_table(const char*, int);
-bool set_bool(const char*, bool);
-bool set_number(const char*, double);
-bool set_string(const char*, const char*);
-bool set_alias(const char*, const char*);
const char* push_include_path(const char*);
void pop_include_path();
+void snort_whitelist_append(const char*);
+void snort_whitelist_add_prefix(const char*);
]]
-function snort_traverse(tab, fqn)
- local key, val
-
- for key,val in pairs(tab) do
- -- skip Lua reserved symbols
- if ( string.sub(key, 1, 1) ~= '_' ) then
- if ( type(val) == 'string' ) then
- snort_set(fqn, key, val)
- end
- end
- end
-
- for key,val in pairs(tab) do
- -- skip Lua reserved symbols
- if ( string.sub(key, 1, 1) ~= '_' ) then
- if ( type(val) ~= 'string' ) then
- snort_set(fqn, key, val)
+function whitelist_append(list, is_prefix)
+ for w in list:gmatch("%S+") do
+ if ( type(w) == 'string' ) then
+ if ( w:match('^%a') ~= nil ) then
+ if ( is_prefix ) then
+ ffi.C.snort_whitelist_add_prefix(w)
+ else
+ ffi.C.snort_whitelist_append(w)
+ end
end
end
end
end
-function snort_set(fqn, key, val)
- local name
- local idx = 0
- local what = type(val)
-
- if ( not fqn ) then
- name = key
-
- elseif ( type(key) == 'number' ) then
- name = fqn
- idx = key
-
- else
- name = fqn .. '.' .. key
- end
-
- if ( what == 'boolean' ) then
- ffi.C.set_bool(name, val)
-
- elseif ( what == 'number' ) then
- ffi.C.set_number(name, val)
-
- elseif ( what == 'string' ) then
- ffi.C.set_string(name, val)
-
- elseif ( what == 'table' ) then
- if ( ffi.C.open_table(name, idx) ) then
- snort_traverse(val, name)
- ffi.C.close_table(name, idx)
- end
- end
+function snort_whitelist_append(list)
+ whitelist_append(list, false)
end
-function load_aliases()
- for i,v in ipairs(binder) do
- if ( v.use and type(v.use) == "table" ) then
- if ( v.use.name and v.use.type ) then
- ffi.C.set_alias(v.use.name, v.use.type)
- tab = _G[v.use.name]
+function snort_whitelist_add_prefix(list)
+ whitelist_append(list, true)
+end
- if ( tab ) then
- snort_set(nil, v.use.name, _G[v.use.name])
- end
+function initialize_whitelist(tab)
+ for key, val in pairs(tab) do
+ -- skip Lua reserved symbols
+ if ( string.sub(key, 1, 1) ~= '_' ) then
+ if ( type(val) == 'table' ) then
+ ffi.C.snort_whitelist_append(key)
end
end
end
end
-function snort_config(tab)
- snort_traverse(tab)
-
- if ( binder and type(binder) == 'table' ) then
- load_aliases()
- end
-end
-
---------------------------------------------------------------------------
-- path magic for includes
---------------------------------------------------------------------------
ffi.C.pop_include_path()
end
+initialize_whitelist(_G)
+initialize_whitelist = nil
Some Lua files are here as they are coupled closely with C++ code in this
directory (module_manager.cc):
-* snort_config.lua provides the ability to parse a Lua configuration. It
+* bootstrap.lua provides FFI for adding table names and prefixes to
+ snort's lua whitelist. This whitelist is used to ignore warnings
+ when snort modules are not found for table names. This file also
+ has the FFI used to resolve file includes in lua file.
+
+* finalize.lua provides the ability to parse a Lua configuration. It
is much easier to traverse the Lua tables via Lua itself. This file
leverages the LuaJIT FFI to open and close tables and set values.
--- /dev/null
+---------------------------------------------------------------------------
+-- Copyright (C) 2014-2020 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.
+---------------------------------------------------------------------------
+-- finalize.lua author Russ Combs <rucombs@cisco.com>
+
+---------------------------------------------------------------------------
+-- Snort uses this to configure Lua settings into C++
+---------------------------------------------------------------------------
+
+ffi = require("ffi")
+
+ffi.cdef[[
+bool open_table(const char*, int);
+void close_table(const char*, int);
+bool set_bool(const char*, bool);
+bool set_number(const char*, double);
+bool set_string(const char*, const char*);
+bool set_alias(const char*, const char*);
+]]
+
+function snort_traverse(tab, fqn)
+ for key,val in pairs(tab) do
+ -- skip Lua reserved symbols
+ if ( string.sub(key, 1, 1) ~= '_' ) then
+ if ( type(val) == 'string' ) then
+ snort_set(fqn, key, val)
+ end
+ end
+ end
+
+ for key,val in pairs(tab) do
+ -- skip Lua reserved symbols
+ if ( string.sub(key, 1, 1) ~= '_' ) then
+ if ( type(val) ~= 'string' ) then
+ snort_set(fqn, key, val)
+ end
+ end
+ end
+end
+
+function snort_set(fqn, key, val)
+ local name
+ local idx = 0
+ local what = type(val)
+
+ if ( not fqn ) then
+ name = key
+
+ elseif ( type(key) == 'number' ) then
+ name = fqn
+ idx = key
+
+ else
+ name = fqn .. '.' .. key
+ end
+
+ if ( what == 'boolean' ) then
+ ffi.C.set_bool(name, val)
+
+ elseif ( what == 'number' ) then
+ ffi.C.set_number(name, val)
+
+ elseif ( what == 'string' ) then
+ ffi.C.set_string(name, val)
+
+ elseif ( what == 'table' ) then
+ if ( ffi.C.open_table(name, idx) ) then
+ snort_traverse(val, name)
+ ffi.C.close_table(name, idx)
+ end
+ end
+end
+
+function load_aliases()
+ for i,v in ipairs(binder) do
+ if ( v.use and type(v.use) == "table" ) then
+ if ( v.use.name and v.use.type ) then
+ ffi.C.set_alias(v.use.name, v.use.type)
+ local tab = _G[v.use.name]
+
+ if ( tab ) then
+ snort_whitelist_append(v.use.name)
+ snort_set(nil, v.use.name, _G[v.use.name])
+ end
+ end
+ end
+ end
+end
+
+function snort_config(tab)
+ if ( binder and type(binder) == 'table' ) then
+ load_aliases()
+ end
+ snort_traverse(tab)
+end
+
+snort_config(_G)
// "Lua" includes
#include "lua_bootstrap.h"
#include "lua_coreinit.h"
+#include "lua_finalize.h"
using namespace snort;
using namespace std;
const char* push_include_path(const char* file);
void pop_include_path();
+ void snort_whitelist_append(const char*);
+ void snort_whitelist_add_prefix(const char*);
}
//-------------------------------------------------------------------------
const char* ModuleManager::get_lua_bootstrap()
{ return lua_bootstrap; }
+const char* ModuleManager::get_lua_finalize()
+{ return lua_finalize; }
+
const char* ModuleManager::get_lua_coreinit()
{ return lua_coreinit; }
return true;
}
+SO_PUBLIC void snort_whitelist_append(const char* s)
+{
+ Shell::whitelist_append(s, false);
+}
+
+SO_PUBLIC void snort_whitelist_add_prefix(const char* s)
+{
+ Shell::whitelist_append(s, true);
+}
+
SO_PUBLIC bool open_table(const char* s, int idx)
{
const char* orig = s;
ModHook* h = get_hook(key.c_str());
if ( !h || (h->api && h->api->type == PT_IPS_OPTION) )
+ {
+ if ( !Shell::is_whitelisted(key) )
+ ParseWarning(WARN_CONF_STRICT, "unknown table %s", key.c_str());
return false;
+ }
// FIXIT-M only basic modules, inspectors and ips actions can be reloaded at present
if ( ( Snort::is_reloading() ) and h->api
SO_PUBLIC static std::list<Module*> get_all_modules();
static const char* get_lua_bootstrap();
+ static const char* get_lua_finalize();
static const char* get_lua_coreinit();
static void list_modules(const char* = nullptr);