From 4f63461b5912749aaaf3769442b09aa655c81d86 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 4 May 2016 10:23:12 -0700 Subject: [PATCH 1/3] Return errors for ignored syscalls. Create a table containing the filtered syscalls and set it as the lua global m_lua_ignored_syscalls == ignored_syscalls. In the parser, add a general purpose ast traversal function traverse_ast that calls a callback for all nodes of a specific type. In the compiler, add a new function check_for_ignored_syscalls that uses the traversal function to be called back for all "BinaryRelOp" nodes (i.e. X = Y, X in [a, b, c], etc). For those nodes, if the lhs is a field 'evt.type' or 'syscall.type' and the rhs contains one of the ignored syscalls, throw an error. Call check_for_ignored_syscalls after parsing any macro or rule filter. The thrown error will contain the macro or rule that had the ignored syscall. In the next commit I'll change the rules to skip the ignored syscalls. --- userspace/falco/lua/compiler.lua | 38 +++++++++++++++++++++++++++++ userspace/falco/lua/parser.lua | 42 ++++++++++++++++++++++++++++++++ userspace/falco/rules.cpp | 21 ++++++++++++++++ userspace/falco/rules.h | 2 ++ 4 files changed, 103 insertions(+) diff --git a/userspace/falco/lua/compiler.lua b/userspace/falco/lua/compiler.lua index ac7793a5..7afa0b46 100644 --- a/userspace/falco/lua/compiler.lua +++ b/userspace/falco/lua/compiler.lua @@ -111,6 +111,36 @@ function get_macros(ast, set) return set end +function check_for_ignored_syscalls(ast, filter_type, source) + + function check_value(val) + if ignored_syscalls[val] then + error("Ignored syscall \""..val.."\" in "..filter_type..": "..source) + end + end + + function cb(node) + if node.left.type == "FieldName" and + (node.left.value == "evt.type" or + node.left.value == "syscall.type") then + + if node.operator == "in" then + for i, v in ipairs(node.right.elements) do + if v.type == "BareString" then + check_value(v.value) + end + end + else + if node.right.type == "BareString" then + check_value(node.right.value) + end + end + end + end + + parser.traverse_ast(ast, "BinaryRelOp", cb) +end + function compiler.compile_macro(line) local ast, error_msg = parser.parse_filter(line) @@ -119,6 +149,10 @@ function compiler.compile_macro(line) error(error_msg) end + -- Traverse the ast looking for events/syscalls in the ignored + -- syscalls table. If any are found, return an error. + check_for_ignored_syscalls(ast, 'macro', line) + return ast end @@ -133,6 +167,10 @@ function compiler.compile_filter(source, macro_defs) error(error_msg) end + -- Traverse the ast looking for events/syscalls in the ignored + -- syscalls table. If any are found, return an error. + check_for_ignored_syscalls(ast, 'rule', source) + if (ast.type == "Rule") then -- Line is a filter, so expand macro references repeat diff --git a/userspace/falco/lua/parser.lua b/userspace/falco/lua/parser.lua index a54f3210..b43a9cfe 100644 --- a/userspace/falco/lua/parser.lua +++ b/userspace/falco/lua/parser.lua @@ -291,4 +291,46 @@ function print_ast(ast, level) end parser.print_ast = print_ast +-- Traverse the provided ast and call the provided callback function +-- for any nodes of the specified type. The callback function should +-- have the signature: +-- cb(ast_node, ctx) +-- ctx is optional. +function traverse_ast(ast, node_type, cb, ctx) + local t = ast.type + + if t == node_type then + cb(ast, ctx) + end + + if t == "Rule" then + traverse_ast(ast.filter, node_type, cb, ctx) + + elseif t == "Filter" then + traverse_ast(ast.value, node_type, cb, ctx) + + elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then + traverse_ast(ast.left, node_type, cb, ctx) + traverse_ast(ast.right, node_type, cb, ctx) + + elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then + traverse_ast(ast.argument, node_type, cb, ctx) + + elseif t == "List" then + for i, v in ipairs(ast.elements) do + traverse_ast(v, node_type, cb, ctx) + end + + elseif t == "MacroDef" then + traverse_ast(ast.value, node_type, cb, ctx) + + elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then + -- do nothing, no traversal needed + + else + error ("Unexpected type in traverse_ast: "..t) + end +end +parser.traverse_ast = traverse_ast + return parser diff --git a/userspace/falco/rules.cpp b/userspace/falco/rules.cpp index 07ef01a2..2ba892b7 100644 --- a/userspace/falco/rules.cpp +++ b/userspace/falco/rules.cpp @@ -9,6 +9,7 @@ extern "C" { falco_rules::falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename) { + m_inspector = inspector; m_ls = ls; m_lua_parser = new lua_parser(inspector, m_ls); @@ -44,6 +45,26 @@ void falco_rules::load_rules(string rules_filename) lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) { + // Create a table containing the syscalls that are ignored by + // the kernel module. Return an error if any rule references + // one of these syscalls. + sinsp_evttables* einfo = m_inspector->get_event_info_tables(); + const struct ppm_event_info* etable = einfo->m_event_info; + const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table; + lua_newtable(m_ls); + + for(uint32_t j = 0; j < PPM_SC_MAX; j++) + { + if(stable[j].flags & EF_DROP_FALCO) + { + lua_pushstring(m_ls, stable[j].name); + lua_pushnumber(m_ls, 1); + lua_settable(m_ls, -3); + } + } + + lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str()); + lua_pushstring(m_ls, rules_filename.c_str()); if(lua_pcall(m_ls, 1, 0, 0) != 0) { diff --git a/userspace/falco/rules.h b/userspace/falco/rules.h index 17d1b18b..e51ec526 100644 --- a/userspace/falco/rules.h +++ b/userspace/falco/rules.h @@ -15,8 +15,10 @@ class falco_rules void load_compiler(string lua_main_filename); lua_parser* m_lua_parser; + sinsp* m_inspector; lua_State* m_ls; string m_lua_load_rules = "load_rules"; + string m_lua_ignored_syscalls = "ignored_syscalls"; string m_lua_on_event = "on_event"; }; From b8cdb8e46c177eb61c55aff3310a6be39f219cf7 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 5 May 2016 23:20:46 -0700 Subject: [PATCH 2/3] Modify existing rules to not use ignored syscalls. The ignored syscalls in macros were: - write: renamed to open_write to make its weaker resolution more apparent. Checks for open with any flag that could change a file. - read: renamed to open_read. Checks for open with any read flag. - sendto: I couldn't think of any way to replace this, so I simply removed it with a comment. I kept the original read/write macros commented out with a note that they use ignored syscalls. I have not tested these changes yet other than verifying that falco starts properly. --- rules/falco_rules.yaml | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 557f11dd..b619f171 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -3,10 +3,31 @@ ############# # File actions -- macro: write - condition: (syscall.type=write and fd.type in (file, directory)) -- macro: read - condition: (syscall.type=read and evt.dir=> and fd.type in (file, directory)) + + +# Currently disabled as read/write are ignored syscalls. The nearly +# similar open_write/open_read check for files being opened for +# reading/writing. +# - macro: write +# condition: (syscall.type=write and fd.type in (file, directory)) +# - macro: read +# condition: (syscall.type=read and evt.dir=> and fd.type in (file, directory)) + +- macro: open_write + condition: > + (evt.type=open or evt.type=openat) and + fd.typechar='f' and + (evt.arg.flags contains O_WRONLY or + evt.arg.flags contains O_RDWR or + evt.arg.flags contains O_CREAT or + evt.arg.flags contains O_TRUNC) +- macro: open_read + condition: > + (evt.type=open or evt.type=openat) and + fd.typechar='f' and + (evt.arg.flags contains O_RDONLY or + evt.arg.flags contains O_RDWR) + - macro: rename condition: syscall.type = rename - macro: mkdir @@ -79,8 +100,10 @@ # Network - macro: inbound condition: (syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<) + +# Currently sendto is an ignored syscall, otherwise this could also check for (syscall.type=sendto and evt.dir=>) - macro: outbound - condition: ((syscall.type=connect and evt.dir=<) or (syscall.type=sendto and evt.dir=>)) and (fd.typechar=4 or fd.typechar=6) + condition: syscall.type=connect and evt.dir=< and (fd.typechar=4 or fd.typechar=6) - macro: ssh_port condition: fd.lport=22 @@ -112,17 +135,17 @@ ####### # Don't write to binary dirs -- condition: evt.dir = > and write and bin_dir +- condition: evt.dir = > and open_write and bin_dir output: "Write to bin dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)" priority: WARNING # Don't write to /etc -- condition: evt.dir = > and write and etc_dir +- condition: evt.dir = > and open_write and etc_dir output: "Write to etc dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)" priority: WARNING # Don't read 'sensitive' files -- condition: read and not proc.name in (sshd, sudo, su) and not_cron and sensitive_files +- condition: open_read and not proc.name in (sshd, sudo, su) and not_cron and sensitive_files output: "Read sensitive file (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)" priority: WARNING @@ -132,7 +155,7 @@ priority: WARNING # Don't load shared objects coming from unexpected places -- condition: read and fd.name contains .so and not (ubuntu_so_dirs or centos_so_dirs) +- condition: open_read and fd.name contains .so and not (ubuntu_so_dirs or centos_so_dirs) output: "Loaded .so from unexpected dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)" priority: WARNING From 7389e05852e94d97ec5b49aba29713e873d5cac3 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Fri, 6 May 2016 18:12:46 -0700 Subject: [PATCH 3/3] Handle both ignored events and syscalls. Henri pointed out that events may also be flagged as ignored. So populate a second table with the set of ignored events, rename check_for_ignored_syscalls to check_for_ignored_syscalls_events, and separately check each table based on whether the LHS of the expression is evt.type or syscall.type. --- userspace/falco/lua/compiler.lua | 27 +++++++++++++++++++++------ userspace/falco/rules.cpp | 22 +++++++++++++++++++--- userspace/falco/rules.h | 1 + 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/userspace/falco/lua/compiler.lua b/userspace/falco/lua/compiler.lua index 7afa0b46..0e809dd6 100644 --- a/userspace/falco/lua/compiler.lua +++ b/userspace/falco/lua/compiler.lua @@ -111,12 +111,19 @@ function get_macros(ast, set) return set end -function check_for_ignored_syscalls(ast, filter_type, source) +function check_for_ignored_syscalls_events(ast, filter_type, source) - function check_value(val) + function check_syscall(val) if ignored_syscalls[val] then error("Ignored syscall \""..val.."\" in "..filter_type..": "..source) end + + end + + function check_event(val) + if ignored_events[val] then + error("Ignored event \""..val.."\" in "..filter_type..": "..source) + end end function cb(node) @@ -127,12 +134,20 @@ function check_for_ignored_syscalls(ast, filter_type, source) if node.operator == "in" then for i, v in ipairs(node.right.elements) do if v.type == "BareString" then - check_value(v.value) + if node.left.value == "evt.type" then + check_event(v.value) + else + check_syscall(v.value) + end end end else if node.right.type == "BareString" then - check_value(node.right.value) + if node.left.value == "evt.type" then + check_event(node.right.value) + else + check_syscall(node.right.value) + end end end end @@ -151,7 +166,7 @@ function compiler.compile_macro(line) -- Traverse the ast looking for events/syscalls in the ignored -- syscalls table. If any are found, return an error. - check_for_ignored_syscalls(ast, 'macro', line) + check_for_ignored_syscalls_events(ast, 'macro', line) return ast end @@ -169,7 +184,7 @@ function compiler.compile_filter(source, macro_defs) -- Traverse the ast looking for events/syscalls in the ignored -- syscalls table. If any are found, return an error. - check_for_ignored_syscalls(ast, 'rule', source) + check_for_ignored_syscalls_events(ast, 'rule', source) if (ast.type == "Rule") then -- Line is a filter, so expand macro references diff --git a/userspace/falco/rules.cpp b/userspace/falco/rules.cpp index 2ba892b7..5e3fa45a 100644 --- a/userspace/falco/rules.cpp +++ b/userspace/falco/rules.cpp @@ -45,12 +45,28 @@ void falco_rules::load_rules(string rules_filename) lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) { - // Create a table containing the syscalls that are ignored by - // the kernel module. Return an error if any rule references - // one of these syscalls. + // Create a table containing the syscalls/events that + // are ignored by the kernel module. load_rules will + // return an error if any rule references one of these + // syscalls/events. sinsp_evttables* einfo = m_inspector->get_event_info_tables(); const struct ppm_event_info* etable = einfo->m_event_info; const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table; + + lua_newtable(m_ls); + + for(uint32_t j = 0; j < PPM_EVENT_MAX; j++) + { + if(etable[j].flags & EF_DROP_FALCO) + { + lua_pushstring(m_ls, etable[j].name); + lua_pushnumber(m_ls, 1); + lua_settable(m_ls, -3); + } + } + + lua_setglobal(m_ls, m_lua_ignored_events.c_str()); + lua_newtable(m_ls); for(uint32_t j = 0; j < PPM_SC_MAX; j++) diff --git a/userspace/falco/rules.h b/userspace/falco/rules.h index e51ec526..0d5655e8 100644 --- a/userspace/falco/rules.h +++ b/userspace/falco/rules.h @@ -20,5 +20,6 @@ class falco_rules string m_lua_load_rules = "load_rules"; string m_lua_ignored_syscalls = "ignored_syscalls"; + string m_lua_ignored_events = "ignored_events"; string m_lua_on_event = "on_event"; };