Compare commits

...

13 Commits

Author SHA1 Message Date
Federico Di Pierro
43e4d25848 chore(ci): properly checkout pull request HEAD instead of merge commit in gh actions.
See https://github.com/actions/checkout#checkout-pull-request-head-commit-instead-of-merge-commit.

Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2022-12-05 15:59:30 +01:00
Federico Di Pierro
67d592e83a fix(cmake): properly fetch dev version by appending latest Falco tag, delta between master and tag, and hash.
`describe` can no more be used as tags are now made on release branches.

Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2022-12-05 15:55:17 +01:00
Mark Stemm
356a4a0749 Also copy ruleset when copying falco source
In the copy constructor and assignment operator for falco_source, also
copy the ruleset along with factories/name.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-12-01 17:07:52 +01:00
Mark Stemm
910b8ff858 Fix(engine) Save parse positions when finding unresolved macros
Now that ASTs contain parse positions, use them when reporting errors
about unknown macros.

When doing the first pass to find all macro references, save macros as
a map<macro name,parse position> instead of a set<macro name>. While
making that change, change the visitor struct to use references
instead of pointers.

In the second pass, when reporting any unresolved macro references,
also report the parse position.

The unit tests also check that the positions of macros are properly
returned in the resolved/unresolved maps.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-12-01 17:03:52 +01:00
Mark Stemm
83b12bab1d Fix(engine): include parse positions in compile errors
Now that ASTs have parse positions and the compiler will return the
position of the last error, use that in falco rules to return errors
within condition strings instead of reporting the position as the
beginning of the condition.

This led to a change in the filter_ruleset interface--now, an ast is
compiled to a filter before being passed to the filter_ruleset
object. That avoids polluting the interface with a lot of details
about rule_loader contexts, errors, etc. The ast is still provided in
case the filter_ruleset wants to do indexing/analysis of the filter.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-12-01 17:03:52 +01:00
Lorenzo Susini
ecc1853d60 update(rule): improve insmod detection within container using CAP_SYS_MODULE
Signed-off-by: Lorenzo Susini <susinilorenzo1@gmail.com>
2022-12-01 11:17:50 +01:00
Andrea Terzolo
fbd6628693 new(config): add the simulate_drops config explicitly
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-11-30 19:26:47 +01:00
Jason Dellaluce
ba61706557 update(userspace/falco): enable using zlib with webserver
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-11-30 19:24:47 +01:00
vin01
234026e14b rule(macro rpm_procs): let salt-call write to rpm database
Signed-off-by: vin01 <vinc.i@protonmail.ch>
2022-11-30 19:20:47 +01:00
vin01
d03826379b rule(Read sensitive file untrusted): let salt-call read sensitive files
Signed-off-by: vin01 <vinc.i@protonmail.ch>
2022-11-30 19:20:47 +01:00
Alessandro Brucato
3697d1fae2 Fixed typo
Signed-off-by: Alessandro Brucato <alessandro.brucato@sysdig.com>
2022-11-30 19:12:47 +01:00
Alessandro Brucato
e76c31b493 Added PTRACE_SEIZE, PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_SETREGS and whitelist macro
Signed-off-by: Alessandro Brucato <alessandro.brucato@sysdig.com>
2022-11-30 19:12:47 +01:00
Alessandro Brucato
d95e36b526 Rule: PTRACE attached to process
Signed-off-by: Alessandro Brucato <alessandro.brucato@sysdig.com>
2022-11-30 19:12:47 +01:00
16 changed files with 351 additions and 116 deletions

View File

@@ -14,6 +14,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Update base image
run: sudo apt update -y
@@ -47,6 +48,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Update base image
run: sudo apt update -y
@@ -80,6 +82,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Update base image
run: sudo apt update -y
@@ -113,6 +116,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Update base image
run: sudo apt update -y
@@ -151,6 +155,7 @@ jobs:
with:
fetch-depth: 0
path: falco
ref: ${{ github.event.pull_request.head.sha }}
- name: Link falco repo to /source/falco
run: |

View File

@@ -16,18 +16,32 @@ include(GetGitRevisionDescription)
# Create the falco version variable according to git index
if(NOT FALCO_VERSION)
string(STRIP "${FALCO_HASH}" FALCO_HASH)
# Try to obtain the exact git tag
git_get_exact_tag(FALCO_TAG)
if(NOT FALCO_TAG)
# Obtain the closest tag
git_describe(FALCO_VERSION "--always" "--tags" "--abbrev=7")
# Fallback version
if(FALCO_VERSION MATCHES "NOTFOUND$")
set(FALCO_VERSION "0.0.0")
endif()
# Format FALCO_VERSION to be semver with prerelease and build part
string(REPLACE "-g" "+" FALCO_VERSION "${FALCO_VERSION}")
# Fetch current hash
get_git_head_revision(refspec FALCO_HASH)
if(NOT FALCO_HASH OR FALCO_HASH MATCHES "NOTFOUND$")
set(FALCO_VERSION "0.0.0")
else()
# Obtain the closest tag
git_get_latest_tag(FALCO_LATEST_TAG)
if(NOT FALCO_LATEST_TAG OR FALCO_LATEST_TAG MATCHES "NOTFOUND$")
set(FALCO_VERSION "0.0.0")
else()
# Compute commit delta since tag
git_get_delta_from_tag(FALCO_DELTA ${FALCO_LATEST_TAG} ${FALCO_HASH})
if(NOT FALCO_DELTA OR FALCO_DELTA MATCHES "NOTFOUND$")
set(FALCO_VERSION "0.0.0")
else()
# Cut hash to 7 bytes
string(SUBSTRING ${FALCO_HASH} 0 7 FALCO_HASH)
# Format FALCO_VERSION to be semver with prerelease and build part
set(FALCO_VERSION
"${FALCO_LATEST_TAG}-${FALCO_DELTA}+${FALCO_HASH}")
endif()
endif()
endif()
else()
# A tag has been found: use it as the Falco version
set(FALCO_VERSION "${FALCO_TAG}")

View File

@@ -86,29 +86,36 @@ function(get_git_head_revision _refspecvar _hashvar)
PARENT_SCOPE)
endfunction()
function(git_describe _var)
function(git_get_latest_tag _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE)
# We use git describe --tags `git rev-list --tags --max-count=1`
execute_process(COMMAND
"${GIT_EXECUTABLE}"
rev-list
--tags
--max-count=1
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
COMMAND tail -n1
RESULT_VARIABLE
res
OUTPUT_VARIABLE
tag_hash
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${tag_hash}-${res}-NOTFOUND" PARENT_SCOPE)
return()
endif()
execute_process(COMMAND
"${GIT_EXECUTABLE}"
describe
${hash}
${ARGN}
--tags
${tag_hash}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
@@ -120,10 +127,108 @@ function(git_describe _var)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var} "${out}" PARENT_SCOPE)
endfunction()
function(git_get_delta_from_tag _var tag hash)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
# Count commits in HEAD
execute_process(COMMAND
"${GIT_EXECUTABLE}"
rev-list
--count
${hash}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out_counter_head
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(${_var} "HEADCOUNT-NOTFOUND" PARENT_SCOPE)
return()
endif()
# Count commits in latest tag
execute_process(COMMAND
"${GIT_EXECUTABLE}"
rev-list
--count
${tag}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out_counter_tag
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(${_var} "TAGCOUNT-NOTFOUND" PARENT_SCOPE)
return()
endif()
execute_process(COMMAND
expr
${out_counter_head} - ${out_counter_tag}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out_delta
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(${_var} "DELTA-NOTFOUND" PARENT_SCOPE)
return()
endif()
set(${_var} "${out_delta}" PARENT_SCOPE)
endfunction()
function(git_describe _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE)
return()
endif()
execute_process(COMMAND
"${GIT_EXECUTABLE}"
describe
${hash}
${ARGN}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var}
"${out}"
PARENT_SCOPE)
"${out}"
PARENT_SCOPE)
endfunction()
function(git_get_exact_tag _var)

View File

@@ -150,6 +150,7 @@ syscall_event_drops:
- alert
rate: .03333
max_burst: 1
simulate_drops: false
# Falco uses a shared buffer between the kernel and userspace to receive
# the events (eg., system call information) in userspace.

View File

@@ -220,7 +220,7 @@
items: [probe_rpminfo, probe_rpmverify, probe_rpmverifyfile, probe_rpmverifypackage]
- macro: rpm_procs
condition: (proc.name in (rpm_binaries, openscap_rpm_binaries) or proc.name in (salt-minion))
condition: (proc.name in (rpm_binaries, openscap_rpm_binaries) or proc.name in (salt-call, salt-minion))
- list: deb_binaries
items: [dpkg, dpkg-preconfigu, dpkg-reconfigur, dpkg-divert, apt, apt-get, aptitude,
@@ -1441,7 +1441,7 @@
and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries,
cron_binaries, read_sensitive_file_binaries, shell_binaries, hids_binaries,
vpn_binaries, mail_config_binaries, nomachine_binaries, sshkit_script_binaries,
in.proftpd, mandb, salt-minion, postgres_mgmt_binaries,
in.proftpd, mandb, salt-call, salt-minion, postgres_mgmt_binaries,
google_oslogin_
)
and not cmp_cp_by_passwd
@@ -3064,7 +3064,7 @@
- rule: Linux Kernel Module Injection Detected
desc: Detect kernel module was injected (from container).
condition: spawned_process and container and proc.name=insmod and not proc.args in (white_listed_modules)
condition: spawned_process and container and proc.name=insmod and not proc.args in (white_listed_modules) and thread.cap_effective icontains sys_module
output: Linux Kernel Module injection using insmod detected (user=%user.name user_loginuid=%user.loginuid parent_process=%proc.pname module=%proc.args %container.info image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [process]
@@ -3240,3 +3240,16 @@
command=%proc.cmdline pid=%proc.pid file=%fd.name parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)
priority: WARNING
tags: [filesystem, mitre_credential_access, mitre_discovery]
- list: known_ptrace_binaries
items: []
- macro: known_ptrace_procs
condition: (proc.name in (known_ptrace_binaries))
- rule: PTRACE attached to process
desc: "This rule detects an attempt to inject code into a process using PTRACE."
condition: evt.type=ptrace and evt.dir=> and evt.arg.request in (5, 6, 11, 20, 27) and proc_name_exists and not known_ptrace_procs
output: Detected ptrace PTRACE_ATTACH attempt (proc.cmdline=%proc.cmdline container=%container.info evt.type=%evt.type evt.arg.request=%evt.arg.request proc.pid=%proc.pid proc.cwd=%proc.cwd proc.ppid=%proc.ppid proc.pcmdline=%proc.pcmdline proc.sid=%proc.sid proc.exepath=%proc.exepath user.uid=%user.uid user.loginuid=%user.loginuid user.loginname=%user.loginname user.name=%user.name group.gid=%group.gid group.name=%group.name container.id=%container.id container.name=%container.name image=%container.image.repository)
priority: WARNING
tags: [process]

View File

@@ -20,9 +20,27 @@ limitations under the License.
using namespace std;
using namespace libsinsp::filter::ast;
static pos_info create_pos(uint32_t idx, uint32_t line, uint32_t col)
{
pos_info ret;
ret.idx = idx;
ret.line = line;
ret.col = col;
return ret;
}
static bool operator==(const pos_info& p1, const pos_info& p2)
{
return (p1.idx == p2.idx) &&
(p1.line == p2.line) &&
(p1.col == p2.col);
}
TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
{
string macro_name = "test_macro";
pos_info macro_pos = create_pos(12, 85, 27);
SECTION("in the general case")
{
@@ -31,7 +49,7 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
std::vector<std::unique_ptr<expr>> filter_and;
filter_and.push_back(unary_check_expr::create("evt.name", "", "exists"));
filter_and.push_back(not_expr::create(value_expr::create(macro_name)));
filter_and.push_back(not_expr::create(value_expr::create(macro_name, macro_pos)));
std::shared_ptr<expr> filter = std::move(and_expr::create(filter_and));
std::vector<std::unique_ptr<expr>> expected_and;
@@ -45,7 +63,8 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->first == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->second == macro_pos);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected.get()));
@@ -61,7 +80,7 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
std::shared_ptr<expr> macro = std::move(
unary_check_expr::create("test.field", "", "exists"));
std::shared_ptr<expr> filter = std::move(value_expr::create(macro_name));
std::shared_ptr<expr> filter = std::move(value_expr::create(macro_name, macro_pos));
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
@@ -71,7 +90,8 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
REQUIRE(resolver.run(filter) == true);
REQUIRE(filter.get() != old_filter_ptr);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->first == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->second == macro_pos);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(macro.get()));
@@ -89,14 +109,17 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
string a_macro_name = macro_name + "_1";
string b_macro_name = macro_name + "_2";
pos_info a_macro_pos = create_pos(11, 75, 43);
pos_info b_macro_pos = create_pos(91, 21, 9);
std::shared_ptr<expr> a_macro = std::move(
unary_check_expr::create("one.field", "", "exists"));
std::shared_ptr<expr> b_macro = std::move(
unary_check_expr::create("another.field", "", "exists"));
std::vector<std::unique_ptr<expr>> filter_or;
filter_or.push_back(value_expr::create(a_macro_name));
filter_or.push_back(value_expr::create(b_macro_name));
filter_or.push_back(value_expr::create(a_macro_name, a_macro_pos));
filter_or.push_back(value_expr::create(b_macro_name, b_macro_pos));
std::shared_ptr<expr> filter = std::move(or_expr::create(filter_or));
std::vector<std::unique_ptr<expr>> expected_or;
@@ -111,11 +134,16 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 2);
REQUIRE(resolver.get_resolved_macros().find(a_macro_name)
!= resolver.get_resolved_macros().end());
REQUIRE(resolver.get_resolved_macros().find(b_macro_name)
!= resolver.get_resolved_macros().end());
auto a_resolved_itr = resolver.get_resolved_macros().find(a_macro_name);
REQUIRE(a_resolved_itr != resolver.get_resolved_macros().end());
REQUIRE(a_resolved_itr->first == a_macro_name);
REQUIRE(a_resolved_itr->second == a_macro_pos);
auto b_resolved_itr = resolver.get_resolved_macros().find(b_macro_name);
REQUIRE(b_resolved_itr != resolver.get_resolved_macros().end());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(b_resolved_itr->first == b_macro_name);
REQUIRE(b_resolved_itr->second == b_macro_pos);
REQUIRE(filter->is_equal(expected_filter.get()));
// second run
@@ -130,15 +158,18 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
string a_macro_name = macro_name + "_1";
string b_macro_name = macro_name + "_2";
pos_info a_macro_pos = create_pos(47, 1, 76);
pos_info b_macro_pos = create_pos(111, 65, 2);
std::vector<std::unique_ptr<expr>> a_macro_and;
a_macro_and.push_back(unary_check_expr::create("one.field", "", "exists"));
a_macro_and.push_back(value_expr::create(b_macro_name));
a_macro_and.push_back(value_expr::create(b_macro_name, b_macro_pos));
std::shared_ptr<expr> a_macro = std::move(and_expr::create(a_macro_and));
std::shared_ptr<expr> b_macro = std::move(
unary_check_expr::create("another.field", "", "exists"));
std::shared_ptr<expr> filter = std::move(value_expr::create(a_macro_name));
std::shared_ptr<expr> filter = std::move(value_expr::create(a_macro_name, a_macro_pos));
std::vector<std::unique_ptr<expr>> expected_and;
expected_and.push_back(unary_check_expr::create("one.field", "", "exists"));
@@ -152,10 +183,17 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 2);
REQUIRE(resolver.get_resolved_macros().find(a_macro_name)
!= resolver.get_resolved_macros().end());
REQUIRE(resolver.get_resolved_macros().find(b_macro_name)
!= resolver.get_resolved_macros().end());
auto a_resolved_itr = resolver.get_resolved_macros().find(a_macro_name);
REQUIRE(a_resolved_itr != resolver.get_resolved_macros().end());
REQUIRE(a_resolved_itr->first == a_macro_name);
REQUIRE(a_resolved_itr->second == a_macro_pos);
auto b_resolved_itr = resolver.get_resolved_macros().find(b_macro_name);
REQUIRE(b_resolved_itr != resolver.get_resolved_macros().end());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(b_resolved_itr->first == b_macro_name);
REQUIRE(b_resolved_itr->second == b_macro_pos);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter.get()));
@@ -170,18 +208,20 @@ TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
TEST_CASE("Should find unknown macros", "[rule_loader]")
{
string macro_name = "test_macro";
pos_info macro_pos = create_pos(9, 4, 2);
SECTION("in the general case")
{
std::vector<std::unique_ptr<expr>> filter_and;
filter_and.push_back(unary_check_expr::create("evt.name", "", "exists"));
filter_and.push_back(not_expr::create(value_expr::create(macro_name)));
filter_and.push_back(not_expr::create(value_expr::create(macro_name, macro_pos)));
std::shared_ptr<expr> filter = std::move(and_expr::create(filter_and));
filter_macro_resolver resolver;
REQUIRE(resolver.run(filter) == false);
REQUIRE(resolver.get_unknown_macros().size() == 1);
REQUIRE(*resolver.get_unknown_macros().begin() == macro_name);
REQUIRE(resolver.get_unknown_macros().begin()->first == macro_name);
REQUIRE(resolver.get_unknown_macros().begin()->second == macro_pos);
REQUIRE(resolver.get_resolved_macros().empty());
}
@@ -190,12 +230,15 @@ TEST_CASE("Should find unknown macros", "[rule_loader]")
string a_macro_name = macro_name + "_1";
string b_macro_name = macro_name + "_2";
pos_info a_macro_pos = create_pos(32, 84, 9);
pos_info b_macro_pos = create_pos(1, 0, 5);
std::vector<std::unique_ptr<expr>> a_macro_and;
a_macro_and.push_back(unary_check_expr::create("one.field", "", "exists"));
a_macro_and.push_back(value_expr::create(b_macro_name));
a_macro_and.push_back(value_expr::create(b_macro_name, b_macro_pos));
std::shared_ptr<expr> a_macro = std::move(and_expr::create(a_macro_and));
std::shared_ptr<expr> filter = std::move(value_expr::create(a_macro_name));
std::shared_ptr<expr> filter = std::move(value_expr::create(a_macro_name, a_macro_pos));
auto expected_filter = clone(a_macro.get());
filter_macro_resolver resolver;
@@ -204,9 +247,11 @@ TEST_CASE("Should find unknown macros", "[rule_loader]")
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == a_macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->first == a_macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->second == a_macro_pos);
REQUIRE(resolver.get_unknown_macros().size() == 1);
REQUIRE(*resolver.get_unknown_macros().begin() == b_macro_name);
REQUIRE(resolver.get_unknown_macros().begin()->first == b_macro_name);
REQUIRE(resolver.get_unknown_macros().begin()->second == b_macro_pos);
REQUIRE(filter->is_equal(expected_filter.get()));
}
}
@@ -214,15 +259,19 @@ TEST_CASE("Should find unknown macros", "[rule_loader]")
TEST_CASE("Should undefine macro", "[rule_loader]")
{
string macro_name = "test_macro";
pos_info macro_pos_1 = create_pos(12, 9, 3);
pos_info macro_pos_2 = create_pos(9, 6, 3);
std::shared_ptr<expr> macro = std::move(unary_check_expr::create("test.field", "", "exists"));
std::shared_ptr<expr> a_filter = std::move(value_expr::create(macro_name));
std::shared_ptr<expr> b_filter = std::move(value_expr::create(macro_name));
std::shared_ptr<expr> a_filter = std::move(value_expr::create(macro_name, macro_pos_1));
std::shared_ptr<expr> b_filter = std::move(value_expr::create(macro_name, macro_pos_2));
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
REQUIRE(resolver.run(a_filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->first == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->second == macro_pos_1);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(a_filter->is_equal(macro.get()));
@@ -230,21 +279,24 @@ TEST_CASE("Should undefine macro", "[rule_loader]")
REQUIRE(resolver.run(b_filter) == false);
REQUIRE(resolver.get_resolved_macros().empty());
REQUIRE(resolver.get_unknown_macros().size() == 1);
REQUIRE(*resolver.get_unknown_macros().begin() == macro_name);
REQUIRE(resolver.get_unknown_macros().begin()->first == macro_name);
REQUIRE(resolver.get_unknown_macros().begin()->second == macro_pos_2);
}
// checks that the macro AST is cloned and not shared across resolved filters
TEST_CASE("Should clone macro AST", "[rule_loader]")
{
string macro_name = "test_macro";
pos_info macro_pos = create_pos(5, 2, 8888);
std::shared_ptr<unary_check_expr> macro = std::move(unary_check_expr::create("test.field", "", "exists"));
std::shared_ptr<expr> filter = std::move(value_expr::create(macro_name));
std::shared_ptr<expr> filter = std::move(value_expr::create(macro_name, macro_pos));
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->first == macro_name);
REQUIRE(resolver.get_resolved_macros().begin()->second == macro_pos);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(macro.get()));

View File

@@ -27,30 +27,51 @@ static uint16_t other_non_default_ruleset = 2;
static std::set<std::string> tags = {"some_tag", "some_other_tag"};
static std::set<uint16_t> evttypes = { ppm_event_type::PPME_GENERIC_E };
static std::shared_ptr<libsinsp::filter::ast::expr> create_filter()
static std::shared_ptr<gen_event_filter_factory> create_factory()
{
libsinsp::filter::parser parser("evt.type=open");
std::shared_ptr<libsinsp::filter::ast::expr> ret(parser.parse());
std::shared_ptr<gen_event_filter_factory> ret(new sinsp_filter_factory(NULL));
return ret;
}
static std::shared_ptr<filter_ruleset> create_ruleset()
static std::shared_ptr<libsinsp::filter::ast::expr> create_ast(
std::shared_ptr<gen_event_filter_factory> f)
{
libsinsp::filter::parser parser("evt.type=open");
std::shared_ptr<libsinsp::filter::ast::expr> ret(parser.parse());
return ret;
}
static std::shared_ptr<gen_event_filter> create_filter(
std::shared_ptr<gen_event_filter_factory> f,
std::shared_ptr<libsinsp::filter::ast::expr> ast)
{
sinsp_filter_compiler compiler(f, ast.get());
std::shared_ptr<gen_event_filter> filter(compiler.compile());
return filter;
}
static std::shared_ptr<filter_ruleset> create_ruleset(
std::shared_ptr<gen_event_filter_factory> f)
{
std::shared_ptr<gen_event_filter_factory> f(new sinsp_filter_factory(NULL));
std::shared_ptr<filter_ruleset> ret(new evttype_index_ruleset(f));
return ret;
}
TEST_CASE("Should enable/disable on ruleset", "[rulesets]")
{
auto r = create_ruleset();
auto filter = create_filter();
auto f = create_factory();
auto r = create_ruleset(f);
auto ast = create_ast(f);
auto filter = create_filter(f, ast);
falco_rule rule;
rule.name = "one_rule";
rule.source = falco_common::syscall_source;
rule.tags = tags;
r->add(rule, filter);
r->add(rule, filter, ast);
SECTION("Should enable/disable for exact match w/ default ruleset")
{
@@ -184,21 +205,23 @@ TEST_CASE("Should enable/disable on ruleset", "[rulesets]")
TEST_CASE("Should enable/disable on ruleset for incremental adding tags", "[rulesets]")
{
auto r = create_ruleset();
auto f = create_factory();
auto r = create_ruleset(f);
auto ast = create_ast(f);
auto rule1_filter = create_filter();
auto rule1_filter = create_filter(f, ast);
falco_rule rule1;
rule1.name = "one_rule";
rule1.source = falco_common::syscall_source;
rule1.tags = {"rule1_tag"};
r->add(rule1, rule1_filter);
r->add(rule1, rule1_filter, ast);
auto rule2_filter = create_filter();
auto rule2_filter = create_filter(f, ast);
falco_rule rule2;
rule2.name = "two_rule";
rule2.source = falco_common::syscall_source;
rule2.tags = {"rule2_tag"};
r->add(rule2, rule2_filter);
r->add(rule2, rule2_filter, ast);
std::set<std::string> want_tags;

View File

@@ -153,12 +153,11 @@ void evttype_index_ruleset::ruleset_filters::evttypes_for_ruleset(std::set<uint1
void evttype_index_ruleset::add(
const falco_rule& rule,
std::shared_ptr<gen_event_filter> filter,
std::shared_ptr<libsinsp::filter::ast::expr> condition)
{
try
{
sinsp_filter_compiler compiler(m_filter_factory, condition.get());
shared_ptr<gen_event_filter> filter(compiler.compile());
std::shared_ptr<filter_wrapper> wrap(new filter_wrapper());
wrap->rule = rule;
wrap->filter = filter;

View File

@@ -41,6 +41,7 @@ public:
void add(
const falco_rule& rule,
std::shared_ptr<gen_event_filter> filter,
std::shared_ptr<libsinsp::filter::ast::expr> condition) override;
void clear() override;

View File

@@ -31,12 +31,14 @@ struct falco_source
falco_source& operator = (falco_source&&) = default;
falco_source(const falco_source& s):
name(s.name),
ruleset(s.ruleset),
ruleset_factory(s.ruleset_factory),
filter_factory(s.filter_factory),
formatter_factory(s.formatter_factory) { };
falco_source& operator = (const falco_source& s)
{
name = s.name;
ruleset = s.ruleset;
ruleset_factory = s.ruleset_factory;
filter_factory = s.filter_factory;
formatter_factory = s.formatter_factory;

View File

@@ -21,12 +21,10 @@ using namespace libsinsp::filter;
bool filter_macro_resolver::run(libsinsp::filter::ast::expr*& filter)
{
visitor v;
m_unknown_macros.clear();
m_resolved_macros.clear();
v.m_unknown_macros = &m_unknown_macros;
v.m_resolved_macros = &m_resolved_macros;
v.m_macros = &m_macros;
visitor v(m_unknown_macros, m_resolved_macros, m_macros);
v.m_node_substitute = nullptr;
filter->accept(&v);
if (v.m_node_substitute)
@@ -39,12 +37,10 @@ bool filter_macro_resolver::run(libsinsp::filter::ast::expr*& filter)
bool filter_macro_resolver::run(std::shared_ptr<libsinsp::filter::ast::expr>& filter)
{
visitor v;
m_unknown_macros.clear();
m_resolved_macros.clear();
v.m_unknown_macros = &m_unknown_macros;
v.m_resolved_macros = &m_resolved_macros;
v.m_macros = &m_macros;
visitor v(m_unknown_macros, m_resolved_macros, m_macros);
v.m_node_substitute = nullptr;
filter->accept(&v);
if (v.m_node_substitute)
@@ -61,12 +57,12 @@ void filter_macro_resolver::set_macro(
m_macros[name] = macro;
}
const unordered_set<string>& filter_macro_resolver::get_unknown_macros() const
const filter_macro_resolver::macro_info_map& filter_macro_resolver::get_unknown_macros() const
{
return m_unknown_macros;
}
const unordered_set<string>& filter_macro_resolver::get_resolved_macros() const
const filter_macro_resolver::macro_info_map& filter_macro_resolver::get_resolved_macros() const
{
return m_resolved_macros;
}
@@ -129,8 +125,8 @@ void filter_macro_resolver::visitor::visit(ast::value_expr* e)
// we are supposed to get here only in case
// of identier-only children from either a 'not',
// an 'and' or an 'or'.
auto macro = m_macros->find(e->value);
if (macro != m_macros->end() && macro->second) // skip null-ptr macros
auto macro = m_macros.find(e->value);
if (macro != m_macros.end() && macro->second) // skip null-ptr macros
{
m_node_substitute = nullptr;
auto new_node = ast::clone(macro->second.get());
@@ -141,11 +137,11 @@ void filter_macro_resolver::visitor::visit(ast::value_expr* e)
{
m_node_substitute = std::move(new_node);
}
m_resolved_macros->insert(e->value);
m_resolved_macros[e->value] = e->get_pos();
}
else
{
m_node_substitute = nullptr;
m_unknown_macros->insert(e->value);
m_unknown_macros[e->value] = e->get_pos();
}
}

View File

@@ -40,7 +40,7 @@ class filter_macro_resolver
\return true if at least one of the defined macros is resolved
*/
bool run(libsinsp::filter::ast::expr*& filter);
/*!
\brief Version of run() that works with shared pointers
*/
@@ -58,12 +58,17 @@ class filter_macro_resolver
std::string name,
std::shared_ptr<libsinsp::filter::ast::expr> macro);
/*!
\brief used in get_{resolved,unknown}_macros
*/
typedef std::unordered_map<std::string,libsinsp::filter::ast::pos_info> macro_info_map;
/*!
\brief Returns a set containing the names of all the macros
substituted during the last invocation of run(). Should be
non-empty if the last invocation of run() returned true.
*/
const std::unordered_set<std::string>& get_resolved_macros() const;
const macro_info_map& get_resolved_macros() const;
/*!
\brief Returns a set containing the names of all the macros
@@ -71,8 +76,8 @@ class filter_macro_resolver
A macro remains unresolved if it is found inside the processed
filter but it was not defined with set_macro();
*/
const std::unordered_set<std::string>& get_unknown_macros() const;
const macro_info_map& get_unknown_macros() const;
private:
typedef std::unordered_map<
std::string,
@@ -81,16 +86,18 @@ class filter_macro_resolver
struct visitor : public libsinsp::filter::ast::expr_visitor
{
visitor() = default;
visitor(macro_info_map& unknown_macros, macro_info_map& resolved_macros, macro_defs& macros)
: m_unknown_macros(unknown_macros), m_resolved_macros(resolved_macros), m_macros(macros) {}
visitor(visitor&&) = default;
visitor& operator = (visitor&&) = default;
visitor(const visitor&) = delete;
visitor& operator = (const visitor&) = delete;
std::unique_ptr<libsinsp::filter::ast::expr> m_node_substitute;
std::unordered_set<std::string>* m_unknown_macros;
std::unordered_set<std::string>* m_resolved_macros;
macro_defs* m_macros;
macro_info_map& m_unknown_macros;
macro_info_map& m_resolved_macros;
macro_defs& m_macros;
void visit(libsinsp::filter::ast::and_expr* e) override;
void visit(libsinsp::filter::ast::or_expr* e) override;
@@ -101,7 +108,7 @@ class filter_macro_resolver
void visit(libsinsp::filter::ast::binary_check_expr* e) override;
};
std::unordered_set<std::string> m_unknown_macros;
std::unordered_set<std::string> m_resolved_macros;
macro_info_map m_unknown_macros;
macro_info_map m_resolved_macros;
macro_defs m_macros;
};

View File

@@ -32,16 +32,20 @@ public:
virtual ~filter_ruleset() = default;
/*!
\brief Adds a rule and its filtering condition inside the manager.
An exception is thrown is case of error. This method only adds the rule
inside the internal collection, but does not enable it for any ruleset.
The rule must be enabled for one or more rulesets with the enable() or
enable_tags() methods.
\brief Adds a rule and its filtering filter + condition inside the manager.
This method only adds the rule inside the internal collection,
but does not enable it for any ruleset. The rule must be enabled
for one or more rulesets with the enable() or enable_tags() methods.
The ast representation of the rule's condition is provided to allow
the filter_ruleset object to parse the ast to obtain event types
or do other analysis/indexing of the condition.
\param rule The rule to be added
\param the filter representing the rule's filtering condition.
\param condition The AST representing the rule's filtering condition
*/
virtual void add(
const falco_rule& rule,
std::shared_ptr<gen_event_filter> filter,
std::shared_ptr<libsinsp::filter::ast::expr> condition) = 0;
/*!

View File

@@ -234,6 +234,7 @@ static bool resolve_list(std::string& cnd, const rule_loader::list_info& list)
static void resolve_macros(
indexed_vector<rule_loader::macro_info>& macros,
std::shared_ptr<ast::expr>& ast,
const std::string& condition,
uint32_t visibility,
const rule_loader::context &ctx)
{
@@ -248,15 +249,22 @@ static void resolve_macros(
macro_resolver.run(ast);
// Note: only complaining about the first unknown macro
THROW(!macro_resolver.get_unknown_macros().empty(),
std::string("Undefined macro '")
+ *macro_resolver.get_unknown_macros().begin()
+ "' used in filter.",
ctx);
for (auto &m : macro_resolver.get_resolved_macros())
const filter_macro_resolver::macro_info_map& unresolved_macros = macro_resolver.get_unknown_macros();
if(!unresolved_macros.empty())
{
macros.at(m)->used = true;
auto it = unresolved_macros.begin();
const rule_loader::context cond_ctx(it->second, condition, ctx);
THROW(true,
std::string("Undefined macro '")
+ it->first
+ "' used in filter.",
cond_ctx);
}
for (auto &it : macro_resolver.get_resolved_macros())
{
macros.at(it.first)->used = true;
}
}
@@ -363,7 +371,7 @@ void rule_loader::compiler::compile_macros_infos(
for (auto &m : out)
{
resolve_macros(out, m.cond_ast, m.visibility, m.ctx);
resolve_macros(out, m.cond_ast, m.cond, m.visibility, m.ctx);
}
}
@@ -404,7 +412,7 @@ void rule_loader::compiler::compile_rule_infos(
r.exceptions, rule.exception_fields, condition);
}
auto ast = parse_condition(condition, lists, r.cond_ctx);
resolve_macros(macros, ast, MAX_VISIBILITY, r.ctx);
resolve_macros(macros, ast, condition, MAX_VISIBILITY, r.ctx);
// check for warnings in the filtering condition
warn_codes.clear();
@@ -444,10 +452,12 @@ void rule_loader::compiler::compile_rule_infos(
// This also compiles the filter, and might throw a
// falco_exception with details on the compilation
// failure.
sinsp_filter_compiler compiler(cfg.sources.at(r.source)->filter_factory, ast.get());
try {
source->ruleset->add(*out.at(rule_id), ast);
shared_ptr<gen_event_filter> filter(compiler.compile());
source->ruleset->add(*out.at(rule_id), filter, ast);
}
catch (const falco_exception& e)
catch (const sinsp_exception& e)
{
// Allow errors containing "nonexistent field" if
// skip_if_unknown_filter is true
@@ -463,10 +473,14 @@ void rule_loader::compiler::compile_rule_infos(
}
else
{
rule_loader::context ctx(compiler.get_pos(),
condition,
r.cond_ctx);
throw rule_loader::rule_load_exception(
falco::load_result::load_result::LOAD_ERR_COMPILE_CONDITION,
e.what(),
r.cond_ctx);
ctx);
}
}

View File

@@ -16,8 +16,6 @@ limitations under the License.
#pragma once
#define FALCO_BRANCH "@FALCO_REF@"
#define FALCO_HASH "@FALCO_HASH@"
#define FALCO_VERSION "@FALCO_VERSION@"
#define FALCO_VERSION_MAJOR @FALCO_VERSION_MAJOR@
#define FALCO_VERSION_MINOR @FALCO_VERSION_MINOR@

View File

@@ -16,6 +16,7 @@ limitations under the License.
#pragma once
#define CPPHTTPLIB_OPENSSL_SUPPORT
#define CPPHTTPLIB_ZLIB_SUPPORT
#include <httplib.h>
#include <thread>
#include "configuration.h"