mirror of
https://github.com/falcosecurity/falco.git
synced 2025-09-29 14:18:01 +00:00
236 lines
6.4 KiB
Lua
236 lines
6.4 KiB
Lua
-- Copyright (C) 2019 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.
|
|
|
|
local parser = require("parser")
|
|
local compiler = {}
|
|
|
|
compiler.trim = parser.trim
|
|
|
|
function map(f, arr)
|
|
local res = {}
|
|
for i,v in ipairs(arr) do
|
|
res[i] = f(v)
|
|
end
|
|
return res
|
|
end
|
|
|
|
function foldr(f, acc, arr)
|
|
for i,v in pairs(arr) do
|
|
acc = f(acc, v)
|
|
end
|
|
return acc
|
|
end
|
|
|
|
--[[
|
|
|
|
Given a map of macro definitions, traverse AST and replace macro references
|
|
with their definitions.
|
|
|
|
The AST is changed in-place.
|
|
|
|
The return value is a boolean which is true if any macro was
|
|
substitued. This allows a caller to re-traverse until no more macros are
|
|
found, a simple strategy for recursive resoltuions (e.g. when a macro
|
|
definition uses another macro).
|
|
|
|
--]]
|
|
|
|
function copy_ast_obj(obj)
|
|
if type(obj) ~= 'table' then return obj end
|
|
local res = {}
|
|
for k, v in pairs(obj) do res[copy_ast_obj(k)] = copy_ast_obj(v) end
|
|
return res
|
|
end
|
|
|
|
function expand_macros(ast, defs, changed)
|
|
|
|
if (ast.type == "Rule") then
|
|
return expand_macros(ast.filter, defs, changed)
|
|
elseif ast.type == "Filter" then
|
|
if (ast.value.type == "Macro") then
|
|
if (defs[ast.value.value] == nil) then
|
|
return false, "Undefined macro '".. ast.value.value .. "' used in filter."
|
|
end
|
|
defs[ast.value.value].used = true
|
|
ast.value = copy_ast_obj(defs[ast.value.value].ast)
|
|
changed = true
|
|
return true, changed
|
|
end
|
|
return expand_macros(ast.value, defs, changed)
|
|
|
|
elseif ast.type == "BinaryBoolOp" then
|
|
|
|
if (ast.left.type == "Macro") then
|
|
if (defs[ast.left.value] == nil) then
|
|
return false, "Undefined macro '".. ast.left.value .. "' used in filter."
|
|
end
|
|
defs[ast.left.value].used = true
|
|
ast.left = copy_ast_obj(defs[ast.left.value].ast)
|
|
changed = true
|
|
end
|
|
|
|
if (ast.right.type == "Macro") then
|
|
if (defs[ast.right.value] == nil) then
|
|
return false, "Undefined macro ".. ast.right.value .. " used in filter."
|
|
end
|
|
defs[ast.right.value].used = true
|
|
ast.right = copy_ast_obj(defs[ast.right.value].ast)
|
|
changed = true
|
|
end
|
|
|
|
local status, changed_left = expand_macros(ast.left, defs, false)
|
|
if status == false then
|
|
return false, changed_left
|
|
end
|
|
local status, changed_right = expand_macros(ast.right, defs, false)
|
|
if status == false then
|
|
return false, changed_right
|
|
end
|
|
return true, changed or changed_left or changed_right
|
|
|
|
elseif ast.type == "UnaryBoolOp" then
|
|
if (ast.argument.type == "Macro") then
|
|
if (defs[ast.argument.value] == nil) then
|
|
return false, "Undefined macro ".. ast.argument.value .. " used in filter."
|
|
end
|
|
defs[ast.argument.value].used = true
|
|
ast.argument = copy_ast_obj(defs[ast.argument.value].ast)
|
|
changed = true
|
|
end
|
|
return expand_macros(ast.argument, defs, changed)
|
|
end
|
|
return true, changed
|
|
end
|
|
|
|
function get_filters(ast)
|
|
|
|
local filters = {}
|
|
|
|
function cb(node)
|
|
if node.type == "FieldName" then
|
|
filters[node.value] = 1
|
|
end
|
|
end
|
|
|
|
parser.traverse_ast(ast.filter.value, {FieldName=1} , cb)
|
|
|
|
return filters
|
|
end
|
|
|
|
function compiler.expand_lists_in(source, list_defs)
|
|
|
|
for name, def in pairs(list_defs) do
|
|
|
|
local bpos = string.find(source, name, 1, true)
|
|
|
|
while bpos ~= nil do
|
|
def.used = true
|
|
|
|
local epos = bpos + string.len(name)
|
|
|
|
-- The characters surrounding the name must be delimiters of beginning/end of string
|
|
if (bpos == 1 or string.match(string.sub(source, bpos-1, bpos-1), "[%s(),=]")) and (epos > string.len(source) or string.match(string.sub(source, epos, epos), "[%s(),=]")) then
|
|
new_source = ""
|
|
|
|
if bpos > 1 then
|
|
new_source = new_source..string.sub(source, 1, bpos-1)
|
|
end
|
|
|
|
sub = table.concat(def.items, ", ")
|
|
|
|
new_source = new_source..sub
|
|
|
|
if epos <= string.len(source) then
|
|
new_source = new_source..string.sub(source, epos, string.len(source))
|
|
end
|
|
|
|
source = new_source
|
|
bpos = bpos + (string.len(sub)-string.len(name))
|
|
end
|
|
|
|
bpos = string.find(source, name, bpos+1, true)
|
|
end
|
|
end
|
|
|
|
return source
|
|
end
|
|
|
|
function compiler.compile_macro(line, macro_defs, list_defs)
|
|
|
|
line = compiler.expand_lists_in(line, list_defs)
|
|
|
|
local ast, error_msg = parser.parse_filter(line)
|
|
|
|
if (error_msg) then
|
|
msg = "Compilation error when compiling \""..line.."\": ".. error_msg
|
|
return false, msg
|
|
end
|
|
|
|
-- Simply as a validation step, try to expand all macros in this
|
|
-- macro's condition. This changes the ast, so we make a copy
|
|
-- first.
|
|
local ast_copy = copy_ast_obj(ast)
|
|
|
|
if (ast.type == "Rule") then
|
|
-- Line is a filter, so expand macro references
|
|
repeat
|
|
status, expanded = expand_macros(ast_copy, macro_defs, false)
|
|
if status == false then
|
|
msg = "Compilation error when compiling \""..line.."\": ".. expanded
|
|
return false, msg
|
|
end
|
|
until expanded == false
|
|
|
|
else
|
|
return false, "Unexpected top-level AST type: "..ast.type
|
|
end
|
|
|
|
return true, ast
|
|
end
|
|
|
|
--[[
|
|
Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST.
|
|
--]]
|
|
function compiler.compile_filter(name, source, macro_defs, list_defs)
|
|
|
|
source = compiler.expand_lists_in(source, list_defs)
|
|
|
|
local ast, error_msg = parser.parse_filter(source)
|
|
|
|
if (error_msg) then
|
|
msg = "Compilation error when compiling \""..source.."\": "..error_msg
|
|
return false, msg
|
|
end
|
|
|
|
if (ast.type == "Rule") then
|
|
-- Line is a filter, so expand macro references
|
|
repeat
|
|
status, expanded = expand_macros(ast, macro_defs, false)
|
|
if status == false then
|
|
return false, expanded
|
|
end
|
|
until expanded == false
|
|
|
|
else
|
|
return false, "Unexpected top-level AST type: "..ast.type
|
|
end
|
|
|
|
filters = get_filters(ast)
|
|
|
|
return true, ast, filters
|
|
end
|
|
|
|
|
|
return compiler
|