]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #1767 in SNORT/snort3 from ~MASHASAN/snort3:zone_fitering to master
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Fri, 4 Oct 2019 18:01:56 +0000 (14:01 -0400)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Fri, 4 Oct 2019 18:01:56 +0000 (14:01 -0400)
Squashed commit of the following:

commit c4da727760f9b485dd4cc83f936ed70efeeb2225
Author: Masud Hasan <mashasan@cisco.com>
Date:   Wed Sep 25 07:43:54 2019 -0400

    discovery_filter: Supporting zone matching

src/helpers/discovery_filter.cc
src/helpers/discovery_filter.h
src/main/modules.cc
src/sfip/sf_ipvar.cc
src/sfip/sf_ipvar.h

index 8e5f70ae2c9da6c40c3d41595585d8138729589c..552387b3755d3942fe94fa9eb3b3283dc20ff38d 100644 (file)
 using namespace snort;
 using namespace std;
 
-#define DF_APP        "app"
-#define DF_HOST       "host"
-#define DF_USER       "user"
-
 #define DF_APP_CHECKED    0x01
 #define DF_APP_MONITORED  0x02
 #define DF_HOST_CHECKED   0x04
@@ -57,23 +53,26 @@ DiscoveryFilter::DiscoveryFilter(const string& conf_path)
     if ( !in_stream )
         return;
 
+    vartable = sfvt_alloc_table(); // create empty table when configuration is given
     uint32_t line_num = 0;
-    string line, config_type, config_key, config_value;
 
     while ( in_stream )
     {
-        line = "";
+        string line("");
         getline(in_stream, line);
         ++line_num;
         if ( line.empty() or line.front() == '#' )
             continue;
 
         istringstream line_stream(line);
-        config_type = config_key = config_value = "";
-        line_stream >> config_type >> config_key >> config_value;
+        string config_type("");
+        line_stream >> config_type;
 
         if ( config_type == "config" )
         {
+            string config_key(""), config_value("");
+            line_stream >> config_key >> config_value;
+
             if ( config_key.empty() or config_value.empty() )
             {
                 WarningMessage("Discovery Filter: Empty configuration items at line %u from %s\n",
@@ -81,30 +80,68 @@ DiscoveryFilter::DiscoveryFilter(const string& conf_path)
                 continue;
             }
 
+            if ( config_key.find("Analyze", 0, sizeof("Analyze")-1) != 0 )
+                continue;
+
+            int64_t zone = DF_ANY_ZONE;
+            string config_zone("");
+            line_stream >> config_zone;
+            if ( !config_zone.empty() and config_zone != "-1" )
+            {
+                if (config_zone == "0")
+                    zone = 0;
+                else
+                {
+                    zone = strtol(config_zone.c_str(), nullptr, 0);
+                    if ( zone < 1 or zone >= DF_ANY_ZONE )
+                    {
+                        WarningMessage("Discovery Filter: Invalid zone at line %u from %s;"
+                            " supported range is -1 (any) to %d\n", line_num,
+                            conf_path.c_str(), DF_ANY_ZONE-1);
+                        continue;
+                    }
+                }
+            }
+
             // host or user discovery will also enable application discovery
             if ( config_key == "AnalyzeApplication" )
             {
-                add_ip(DF_APP, config_value);
+                add_ip(DF_APP, zone, config_value);
             }
             else if ( config_key == "AnalyzeHost" )
             {
-                add_ip(DF_APP, config_value);
-                add_ip(DF_HOST, config_value);
+                add_ip(DF_APP, zone, config_value);
+                add_ip(DF_HOST, zone, config_value);
             }
             else if ( config_key == "AnalyzeUser" )
             {
-                add_ip(DF_APP, config_value);
-                add_ip(DF_USER, config_value);
+                add_ip(DF_APP, zone, config_value);
+                add_ip(DF_USER, zone, config_value);
             }
             else if ( config_key == "AnalyzeHostUser" or config_key == "Analyze" )
             {
-                add_ip(DF_APP, config_value);
-                add_ip(DF_HOST, config_value);
-                add_ip(DF_USER, config_value);
+                add_ip(DF_APP, zone, config_value);
+                add_ip(DF_HOST, zone, config_value);
+                add_ip(DF_USER, zone, config_value);
             }
         }
     }
 
+    // Merge any-zone rules to zone-based rules
+    for (int type = DF_APP; type < DF_MAX; ++type)
+    {
+        auto any_list = get_list((FilterType)type, DF_ANY_ZONE);
+        if (!any_list)
+            continue;
+        for (auto& zone_entry : zone_list[type])
+        {
+            if (zone_entry.second != any_list and
+                sfvar_add(zone_entry.second, any_list) != SFIP_SUCCESS)
+                WarningMessage("Discovery Filter: Failed to add any network list "
+                    "to zone network list for type %d", type);
+        }
+    }
+
     in_stream.close();
 }
 
@@ -134,7 +171,7 @@ bool DiscoveryFilter::is_user_monitored(const Packet* p, uint8_t* flag)
     return is_monitored(p, DF_USER, *flag, DF_USER_CHECKED, DF_USER_MONITORED);
 }
 
-bool DiscoveryFilter::is_monitored(const Packet* p, const char* type, uint8_t& flag,
+bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type, uint8_t& flag,
     uint8_t checked, uint8_t monitored)
 {
     if ( flag & checked )
@@ -152,7 +189,7 @@ bool DiscoveryFilter::is_monitored(const Packet* p, const char* type, uint8_t& f
     return false;
 }
 
-bool DiscoveryFilter::is_monitored(const Packet* p, const char* type)
+bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type)
 {
     if ( !vartable )
         return true; // when not configured, 'any' ip/port/zone are monitored by default
@@ -161,58 +198,69 @@ bool DiscoveryFilter::is_monitored(const Packet* p, const char* type)
     // Keep an unordered map of <port, exclusion> where exclusion object holds pointers
     // to ip-list from vartable for each direction (src/dst) and protocol (tcp/udp).
 
-    // To do zone-based filtering, keep an unordered map of <zone, ip-list pointer> where
-    // the pointer refers to a list from vartable. The list itself should be created
-    // during parsing by appending zone id to the name of ip-list. Absence of the list
-    // for a particular zone means lookup the 'any' (-1) zone list.
+    if (zone_list[type].empty())
+        return false; // the configuration did not have this type of rule
 
-    if ( !varip or strcmp(varip->name, type) )
-    {
-        varip = sfvt_lookup_var(vartable, type);
-        if ( !varip )
-            return false;
-    }
+    auto zone = p->pkth->ingress_group;
+    if ( zone == DAQ_PKTHDR_UNKNOWN or zone < 0 )
+        zone = DF_ANY_ZONE;
+    auto varip = get_list(type, zone, true);
+    if (!varip and zone != DF_ANY_ZONE)
+        varip = get_list(type, DF_ANY_ZONE, true);
 
-    return sfvar_ip_in(varip, p->ptrs.ip_api.get_src()); // check source ip only
+    return sfvar_ip_in(varip, p->ptrs.ip_api.get_src()); // source ip only
 }
 
-void DiscoveryFilter::add_ip(const char* name, string ip)
+void DiscoveryFilter::add_ip(FilterType type, ZoneType zone, string& ip)
 {
-    if ( !vartable )
-        vartable = sfvt_alloc_table();
-    else if ( !varip or strcmp(varip->name, name) )
-        varip = sfvt_lookup_var(vartable, name);
-
+    auto varip = get_list(type, zone);
     if ( varip )
         sfvt_add_to_var(vartable, varip, ip.c_str());
     else
     {
-        ip = " " + ip;
-        ip = name + ip;
-        sfvt_add_str(vartable, ip.c_str(), &varip);
+        string named_ip = to_string((int)type);
+        named_ip += "_";
+        named_ip += to_string(zone);
+        named_ip += " ";
+        named_ip += ip;
+
+        if ( sfvt_add_str(vartable, named_ip.c_str(), &varip) == SFIP_SUCCESS )
+            zone_list[type].emplace(zone, varip);
     }
 }
 
+sfip_var_t* DiscoveryFilter::get_list(FilterType type, ZoneType zone, bool exclude_empty)
+{
+    auto& list = zone_list[type];
+    auto entry = list.find(zone);
+
+    // If head is empty and the boolean flag is true, treat every IP as excluded. The flag
+    // is not used during parsing when we are still building, it is used during searching.
+    if ( entry == list.end() or (exclude_empty and entry->second->head == nullptr) )
+        return nullptr;
+    return entry->second;
+}
+
 #ifdef UNIT_TEST
 TEST_CASE("Discovery Filter", "[is_monitored]")
 {
     string conf("test.txt");
     ofstream out_stream(conf.c_str());
     out_stream << "config Error\n"; // invalid
-    out_stream << "config AnalyzeUser ::/0 3\n"; // any ipv6, zone 3
-    out_stream << "config AnalyzeApplication 0.0.0.0/0 -1\n"; // any ipv4, any zone
+    out_stream << "config AnalyzeUser ::/0 0\n"; // any ipv6, zone 0
+    out_stream << "config AnalyzeApplication 1.1.1.0/24 -1\n"; // targeted ipv4, any zone
     out_stream.close();
 
     Packet p;
     SfIp ip;
-    ip.set("1.1.1.1");
+    ip.set("1.1.1.1"); // zone 0 by default
     p.ptrs.ip_api.set(ip, ip);
     DiscoveryFilter df(conf);
 
     // Without flag
-    CHECK(df.is_app_monitored(&p, nullptr) == true);
-    CHECK(df.is_host_monitored(&p, nullptr) == false);
-    CHECK(df.is_user_monitored(&p, nullptr) == false);
+    CHECK(df.is_app_monitored(&p, nullptr) == true);   // any zone rule for app is added to zone 0
+    CHECK(df.is_host_monitored(&p, nullptr) == false); // no rule for host
+    CHECK(df.is_user_monitored(&p, nullptr) == false); // no any zone rule for user
 
     // With flag
     uint8_t flag = 0;
@@ -236,4 +284,86 @@ TEST_CASE("Discovery Filter", "[is_monitored]")
 
     remove("test.txt");
 }
+
+TEST_CASE("Discovery Filter Empty Configuration", "[is_monitored_config]")
+{
+    string conf("test_empty_analyze.txt");
+    ofstream out_stream(conf.c_str());
+    out_stream << "Error\n"; // invalid
+    out_stream << "config AnalyzeNothing ::/0 3\n"; // invalid
+    out_stream.close();
+
+    Packet p;
+    SfIp ip;
+    ip.set("1.1.1.1");
+    p.ptrs.ip_api.set(ip, ip);
+    DiscoveryFilter df(conf);
+
+    CHECK(df.is_app_monitored(&p, nullptr) == false);
+    CHECK(df.is_host_monitored(&p, nullptr) == false);
+    CHECK(df.is_user_monitored(&p, nullptr) == false);
+
+    remove("test_empty_analyze.txt");
+}
+
+TEST_CASE("Discovery Filter Zone", "[is_monitored_zone_vs_ip]")
+{
+    string conf("test_zone_ip.txt");
+    ofstream out_stream(conf.c_str());
+    out_stream << "config AnalyzeHost 1.1.1.1 -1\n";         // zone any
+    out_stream << "config AnalyzeHost 1.1.1.2 0\n";          // zone 0
+    out_stream << "config AnalyzeHost 1.1.1.3 2\n";          // zone 2
+    out_stream << "config AnalyzeHost 1.1.1.4 -3\n";         // zone out of range
+    out_stream << "config AnalyzeHost 1.1.1.5 2147483648\n"; // zone out of range
+    out_stream << "config AnalyzeHost 1.1.1.6 kidding\n";    // zone invalid
+    out_stream.close();
+
+    Packet p;
+    SfIp ip1, ip2, ip3, ip4, ip5, ip6, ip7;
+    ip1.set("1.1.1.1");
+    ip2.set("1.1.1.2");
+    ip3.set("1.1.1.3");
+    ip4.set("1.1.1.4");
+    ip5.set("1.1.1.5");
+    ip6.set("1.1.1.6");
+    ip7.set("1.1.1.7");
+    const DAQ_PktHdr_t* saved_hdr = p.pkth;
+    DAQ_PktHdr_t z_undefined, z1, z2;
+    z_undefined.ingress_group = DAQ_PKTHDR_UNKNOWN;
+    z1.ingress_group = 1;
+    z2.ingress_group = 2;
+    DiscoveryFilter df(conf);
+
+    p.ptrs.ip_api.set(ip1, ip7);  // ip from undefined zone matches zone any list
+    p.pkth = &z_undefined;
+    CHECK(df.is_app_monitored(&p, nullptr) == true); // analyze host enables application discovery
+    CHECK(df.is_host_monitored(&p, nullptr) == true);
+    CHECK(df.is_user_monitored(&p, nullptr) == false);
+
+    p.pkth = &z2; // the ip is not in zone 2 list, but it is in zone any list
+    CHECK(df.is_host_monitored(&p, nullptr) == true);
+
+    p.ptrs.ip_api.set(ip3, ip7); // the ip matches zone 2 list
+    CHECK(df.is_host_monitored(&p, nullptr) == true);
+
+    p.pkth = &z1; // no zone 1 list and the ip is not in zone any list
+    CHECK(df.is_host_monitored(&p, nullptr) == false);
+
+    p.ptrs.ip_api.set(ip1, ip7); // no zone 1 list, but the ip is in zone any list
+    CHECK(df.is_host_monitored(&p, nullptr) == true);
+
+    p.pkth = saved_hdr;
+    p.ptrs.ip_api.set(ip2, ip7);  // the ip matches zone 0 list
+    CHECK(df.is_host_monitored(&p, nullptr) == true);
+
+    // no match since the configuration for these ip addresses were invalid
+    p.ptrs.ip_api.set(ip4, ip7);
+    CHECK(df.is_host_monitored(&p, nullptr) == false);
+    p.ptrs.ip_api.set(ip5, ip7);
+    CHECK(df.is_host_monitored(&p, nullptr) == false);
+    p.ptrs.ip_api.set(ip6, ip7);
+    CHECK(df.is_host_monitored(&p, nullptr) == false);
+
+    remove("test_zone_ip.txt");
+}
 #endif
index ec32c2d7f2a03d42379ef92aaf9ec6c375857743..36127750f8d6ca0b4b14e06f637e9b4878df8ddc 100644 (file)
 #ifndef DISCOVERY_FILTER_H
 #define DISCOVERY_FILTER_H
 
+#include <unordered_map>
+
 #include "protocols/packet.h"
 #include "sfip/sf_ipvar.h"
 #include "sfip/sf_vartable.h"
 
+enum FilterType { DF_APP, DF_HOST, DF_USER, DF_MAX };
+
+typedef int32_t ZoneType; // matching daq header
+#define DF_ANY_ZONE INT32_MAX
+
 // Holds configurations to filter traffic discovery based network address, port, and zone
 class DiscoveryFilter
 {
@@ -38,13 +45,14 @@ public:
     bool is_user_monitored(const snort::Packet* p, uint8_t* flag = nullptr);
 
 private:
-    bool is_monitored(const snort::Packet* p, const char* type, uint8_t& flag,
+    bool is_monitored(const snort::Packet* p, FilterType type, uint8_t& flag,
         uint8_t checked, uint8_t monitored);
-    bool is_monitored(const snort::Packet* p, const char* type);
-    void add_ip(const char* name, std::string ip);
+    bool is_monitored(const snort::Packet* p, FilterType type);
+    void add_ip(FilterType type, ZoneType zone, std::string& ip);
+    sfip_var_t* get_list(FilterType type, ZoneType zone, bool exclude_empty = false);
 
+    std::unordered_map<ZoneType, sfip_var_t*> zone_list[DF_MAX];
     vartable_t* vartable = nullptr;
-    sfip_var_t* varip = nullptr;
 };
 
 #endif
index d449ed77c3e8d3b1067ba3435e6caf150416beff..15db0e0436fbca4b937a933f7a6775f47bc2dd07 100644 (file)
@@ -1831,7 +1831,7 @@ bool RateFilterModule::end(const char*, int idx, SnortConfig* sc)
 static const Parameter single_rule_state_params[] =
 {
     { "action", Parameter::PT_ENUM,
-      "log | pass | alert | drop | block | reset | inherit", "inherit",
+      "log | pass | alert | drop | block | reset | react | reject | rewrite | inherit", "inherit",
       "apply action if rule matches or inherit from rule definition" },
 
     { "enable", Parameter::PT_ENUM, "no | yes | inherit", "inherit",
index eabc982cfe2e81bc5f1636d7e6988853975da0e9..9ba09b483ed19fea60601814ff2af3ac6763bbb5 100644 (file)
@@ -418,8 +418,7 @@ static sfip_node_t* merge_lists(sfip_node_t* list1, sfip_node_t* list2, uint16_t
     return listHead;
 }
 
-/* Deep copy of src added to dst */
-static SfIpRet sfvar_add(sfip_var_t* dst, sfip_var_t* src)
+SfIpRet sfvar_add(sfip_var_t* dst, sfip_var_t* src)
 {
     sfip_var_t* copiedvar;
 
@@ -722,6 +721,9 @@ SfIpRet sfvar_parse_iplist(vartable_t* table, sfip_var_t* var,
                 neg_ip = !neg_ip;
         }
 
+        if (!*str)
+            return SFIP_ARG_ERR;
+
         /* Find end of this token */
         for (end = str+1;
             *end && !isspace((int)*end) && *end != LIST_CLOSE && *end != ',';
index d45bb18db904662c389e0ce3268a97e778bf37e5..357a06361dd1cad34af1201fbf3107f4ac306e08 100644 (file)
@@ -97,6 +97,9 @@ struct vartable_t
     uint32_t id;
 };
 
+/* Deep copy of src added to dst */
+SfIpRet sfvar_add(sfip_var_t* dst, sfip_var_t* src);
+
 /* Creates a new variable that is an alias of another variable
  * Does a "deep" copy so it owns it's own pointers */
 sfip_var_t* sfvar_deep_copy(const sfip_var_t*);