Skip to content

Commit

Permalink
Add ability to return event types used by a filter
Browse files Browse the repository at this point in the history
Add the ability to return a hint on the event types used by a
filter. For example, if a filter was "evt.type=open and
fd.name=/tmp/foo", the event types would be PPME_SYSCALL_OPEN*_{E,X}
as well as PPME_GENERIC_{E,X}, to handle someone calling the syscall
directly.

By default, an empty set is returned, meaning no specific events are
used.

This is used in programs like falco to provide a quick external test
against an event to see if it makes sense to evaluate the filter at
all. This can speed up event processing when falco has a large number
of loaded rules. Prior to this change, this was handled solely in
falco's lua code for loading rules. Moving responsibility to the
filter significantly simplifies the falco side of rule loading.

The only actual implementation of this is in sinsp_filter_check_event
for the field "evt.type". The method handles =, in, and != as
comparison operators with an optional not.

Also add a unit test that compiles various filters and double-checks
the resulting set of event types.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
  • Loading branch information
mstemm committed Aug 25, 2021
1 parent 214113d commit 3031bea
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 1 deletion.
62 changes: 62 additions & 0 deletions userspace/libsinsp/filterchecks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4522,6 +4522,68 @@ uint8_t* sinsp_filter_check_event::extract(sinsp_evt *evt, OUT uint32_t* len, bo
return NULL;
}

std::set<uint16_t> sinsp_filter_check_event::evttypes()
{
std::set<uint16_t> ret;

bool should_match = true;

// This only reports a specific set of event types for a
// limited set of comparison operators. Otherwise, return the
// empty set e.g. all event types.
//
// The goal is to handle the commonly used combinations of
// logical operators, comparison operators, and values, not
// every possible combination.
if(m_field_id == TYPE_TYPE)
{
if(!(m_cmpop == CO_EQ || m_cmpop == CO_NE || m_cmpop == CO_IN))
{
return ret;
}

// If the comparison operator is a not equals operator
// (e.g. !=, "not in", etc), we need to invert the set.
if((m_boolop != BO_NOT && m_cmpop == CO_NE) ||
(m_boolop == BO_NOT && (m_cmpop == CO_EQ || m_cmpop == CO_IN)))
{
should_match = false;
}

}
else
{
// Should run for all event types
return ret;
}

sinsp_evttables* einfo = m_inspector->get_event_info_tables();
const struct ppm_event_info* etable = einfo->m_event_info;

for(uint32_t i = 0; i < PPM_EVENT_MAX; i++)
{
// The values are held as strings, so we need to
// convert them back to numbers.
for (uint16_t j=0; j < m_val_storages.size(); j++)
{
std::string evttype_str((char *) filter_value_p(j));

if((should_match ? (etable[i].name == evttype_str) : (etable[i].name != evttype_str)))
{
ret.insert(i);
}
}
}

// Also, always add PPME_GENERIC_{E,X} to account for
// the case where it's a syscall and the event name
// should also match/not match the syscall of the same name.
ret.insert(PPME_GENERIC_E);
ret.insert(PPME_GENERIC_X);

return ret;
}

bool sinsp_filter_check_event::compare(sinsp_evt *evt)
{
bool res;
Expand Down
2 changes: 2 additions & 0 deletions userspace/libsinsp/filterchecks.h
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ class sinsp_filter_check_event : public sinsp_filter_check
Json::Value extract_as_js(sinsp_evt *evt, OUT uint32_t* len);
bool compare(sinsp_evt *evt);

std::set<uint16_t> evttypes() override;

uint64_t m_u64val;
uint64_t m_tsdelta;
uint32_t m_u32val;
Expand Down
28 changes: 28 additions & 0 deletions userspace/libsinsp/gen_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ gen_event_filter_check::~gen_event_filter_check()
{
}

std::set<uint16_t> gen_event_filter_check::evttypes()
{
return std::set<uint16_t>();
}

void gen_event_filter_check::set_check_id(int32_t id)
{
m_check_id = id;
Expand Down Expand Up @@ -166,6 +171,24 @@ bool gen_event_filter_expression::compare(gen_event *evt)
return res;
}

// This returns the union of all event types for all
// filterchecks. Remember that the default is the empty set/no
// specific event types, so that becomes restricted only when a
// filtercheck returns a non-empty set.
std::set<uint16_t> gen_event_filter_expression::evttypes()
{
std::set<uint16_t> ret;

for(uint32_t i = 0; i < m_checks.size(); i++)
{
std::set<uint16_t> cevttypes = m_checks[i]->evttypes();

ret.insert(cevttypes.begin(), cevttypes.end());
}

return ret;
}

uint8_t *gen_event_filter_expression::extract(gen_event *evt, uint32_t *len, bool sanitize_strings)
{
return NULL;
Expand Down Expand Up @@ -248,3 +271,8 @@ void gen_event_filter::add_check(gen_event_filter_check* chk)
{
m_curexpr->add_check((gen_event_filter_check *) chk);
}

std::set<uint16_t> gen_event_filter::evttypes()
{
return m_filter->evttypes();
}
22 changes: 21 additions & 1 deletion userspace/libsinsp/gen_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ along with Falco. If not, see <http://www.gnu.org/licenses/>.

#pragma once

#include <set>
#include <vector>

/*
Expand Down Expand Up @@ -109,6 +110,19 @@ class gen_event_filter_check
virtual bool compare(gen_event *evt) = 0;
virtual uint8_t* extract(gen_event *evt, uint32_t* len, bool sanitize_strings = true) = 0;

// Return all event types used by this filtercheck. This method is
// optional--by default it returns an empty set, meaning that
// no specific event types are used.
//
// The set of event types can can be a superset of the actual
// event types used. For example, there's no need to deeply
// parse logical operators to find an exact set of event
// types. This method is used to provide an initial simple
// test against an event type to determine if the filter needs
// to be evaluated. So additional event types are okay,
// missing event types could result in problems.
virtual std::set<uint16_t> evttypes();

//
// Configure numeric id to be set on events that match this filter
//
Expand Down Expand Up @@ -150,12 +164,15 @@ class gen_event_filter_expression : public gen_event_filter_check

bool compare(gen_event *evt);

// Return all event types used by this filter expression.
std::set<uint16_t> evttypes();

uint8_t* extract(gen_event *evt, uint32_t* len, bool sanitize_strings = true);

//
// An expression is consistent if all its checks are of the same type (or/and).
//
// This method returns the expression operator (BO_AND/BO_OR/BO_NONE) if the
// This method returns the expression operator (BO_AND/BO_OR/BO_NONE) if the
// expression is consistent. It returns -1 if the expression is not consistent.
//
int32_t get_expr_boolop();
Expand Down Expand Up @@ -184,6 +201,9 @@ class gen_event_filter
void pop_expression();
void add_check(gen_event_filter_check* chk);

// Return all event types used by this filter.
std::set<uint16_t> evttypes();

gen_event_filter_expression* m_filter;

protected:
Expand Down
1 change: 1 addition & 0 deletions userspace/libsinsp/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ include_directories(${LIBSCAP_INCLUDE_DIR})
add_executable(unit-test-libsinsp
cgroup_list_counter.ut.cpp
sinsp.ut.cpp
evttype_filter.ut.cpp
)

target_link_libraries(unit-test-libsinsp
Expand Down
211 changes: 211 additions & 0 deletions userspace/libsinsp/test/evttype_filter.ut.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
Copyright (C) 2021 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
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <gtest.h>
#include <sinsp.h>
#include <filter.h>

std::stringstream & operator<<(std::stringstream &out, set<uint16_t> s)
{
out << "[ ";
for(auto &val : s)
{
out << val;
out << " ";
}
out << "]";

return out;
}

class evttype_filter_test : public testing::Test
{

protected:

void SetUp()
{
for(uint32_t i = 0; i < PPM_EVENT_MAX; i++)
{
if(openat_only.find(i) == openat_only.end())
{
not_openat.insert(i);
}

if(openat_only.find(i) == openat_only.end() ||
close_only.find(i) == close_only.end())

{
not_openat_close.insert(i);
}
}

not_openat.insert(PPME_GENERIC_E);
not_openat.insert(PPME_GENERIC_X);

not_openat_close.insert(PPME_GENERIC_E);
not_openat_close.insert(PPME_GENERIC_X);
}

void TearDown()
{
}

sinsp_filter *compile(const string &fltstr)
{
sinsp_filter_compiler compiler(NULL, fltstr);

return compiler.compile();
}

void compare_evttypes(sinsp_filter *f, std::set<uint16_t> expected)
{
std::set<uint16_t> actual = f->evttypes();

for(auto &etype : expected)
{
if(actual.find(etype) == actual.end())
{
FAIL() << "Expected event type "
<< etype
<< " not found in actual set. "
<< "Expected: " << expected
<< " Actual: " << actual;

}
}

for(auto &etype : actual)
{
if(expected.find(etype) == expected.end())
{
FAIL() << "Actual evttypes had additional event type "
<< etype
<< " not found in expected set. "
<< "Expected: " << expected
<< " Actual: " << actual;
}
}
}

std::set<uint16_t> openat_only{
PPME_SYSCALL_OPENAT_E, PPME_SYSCALL_OPENAT_X,
PPME_SYSCALL_OPENAT_2_E, PPME_SYSCALL_OPENAT_2_X,
PPME_GENERIC_E, PPME_GENERIC_X};

std::set<uint16_t> close_only{
PPME_SYSCALL_CLOSE_E, PPME_SYSCALL_CLOSE_X,
PPME_GENERIC_E, PPME_GENERIC_X};

std::set<uint16_t> openat_close{
PPME_SYSCALL_OPENAT_E, PPME_SYSCALL_OPENAT_X,
PPME_SYSCALL_OPENAT_2_E, PPME_SYSCALL_OPENAT_2_X,
PPME_SYSCALL_CLOSE_E, PPME_SYSCALL_CLOSE_X,
PPME_GENERIC_E, PPME_GENERIC_X};

std::set<uint16_t> not_openat;

std::set<uint16_t> not_openat_close;

std::set<uint16_t> empty;

};

TEST_F(evttype_filter_test, evt_type_eq)
{
sinsp_filter *f = compile("evt.type=openat");

compare_evttypes(f, openat_only);
}

TEST_F(evttype_filter_test, evt_type_in)
{
sinsp_filter *f = compile("evt.type in (openat, close)");

compare_evttypes(f, openat_close);
}

TEST_F(evttype_filter_test, evt_type_ne)
{
sinsp_filter *f = compile("evt.type!=openat");

compare_evttypes(f, not_openat);
}

TEST_F(evttype_filter_test, not_evt_type_eq)
{
sinsp_filter *f = compile("not evt.type=openat");

compare_evttypes(f, not_openat);
}

TEST_F(evttype_filter_test, not_evt_type_in)
{
sinsp_filter *f = compile("not evt.type in (openat, close)");

compare_evttypes(f, not_openat_close);
}

TEST_F(evttype_filter_test, not_evt_type_ne)
{
sinsp_filter *f = compile("not evt.type != openat");

compare_evttypes(f, openat_only);
}

TEST_F(evttype_filter_test, evt_type_or)
{
sinsp_filter *f = compile("evt.type=openat or evt.type=close");

compare_evttypes(f, openat_close);
}

TEST_F(evttype_filter_test, not_evt_type_or)
{
sinsp_filter *f = compile("evt.type!=openat or evt.type!=close");

compare_evttypes(f, not_openat_close);
}

TEST_F(evttype_filter_test, evt_type_or_ne)
{
sinsp_filter *f = compile("evt.type=close or evt.type!=openat");

compare_evttypes(f, not_openat);
}

TEST_F(evttype_filter_test, evt_type_and)
{
sinsp_filter *f = compile("evt.type=close and evt.type=openat");

compare_evttypes(f, openat_close);
}

TEST_F(evttype_filter_test, evt_type_and_non_evt_type)
{
sinsp_filter *f = compile("evt.type=openat and proc.name=nginx");

compare_evttypes(f, openat_only);
}


TEST_F(evttype_filter_test, non_evt_type)
{
sinsp_filter *f = compile("proc.name=nginx");

compare_evttypes(f, empty);
}

0 comments on commit 3031bea

Please sign in to comment.