Compare commits

...

41 Commits

Author SHA1 Message Date
Federico Di Pierro
bc714d06fc chore(userspace/falco): do not allow multiple Falco instances.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2022-10-10 14:22:16 +02:00
Stanley Chan
79d875c28f cleanup(scripts): cleanup systemd unit in RPM installer
Signed-off-by: Stanley Chan <pocketgamer5000@gmail.com>
2022-10-07 14:47:00 +02:00
Stanley Chan
7610ee53e5 cleanup(scripts): cleanup systemd unit in DEB installer
Signed-off-by: Stanley Chan <pocketgamer5000@gmail.com>
2022-10-07 14:47:00 +02:00
Jason Dellaluce
3c02b40a21 chore(userspace/falco): make log message termination consistent
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 21:27:06 +02:00
Jason Dellaluce
e85a8c914f chore(userspace/falco): move enabled sources list printout when capture is opened
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 21:27:06 +02:00
Jason Dellaluce
21c2b1f472 update(userspace/falco): use unordered_set where possible for faster lookups
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 21:27:06 +02:00
Jason Dellaluce
909f6d0961 chore(userspace/falco): make log messages formatting more consistent
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 21:27:06 +02:00
Jason Dellaluce
83a83a5853 update(userspace): pass string as const refs when possible
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 21:27:06 +02:00
Jason Dellaluce
b4ea2f4da2 fix(userspace/falco): stabilize termination signal handler
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 18:21:05 +02:00
Jason Dellaluce
59ba2f9aab fix(userspace/falco): properly terminate threads
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 18:21:05 +02:00
Jason Dellaluce
32ec3240b4 fix(rules): add falco no-driver images to k8s_containers macro
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-06 15:44:10 +02:00
Andrea Terzolo
fbac2a9570 tests: fix broken tests
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-10-05 19:38:21 +02:00
Andrea Terzolo
805f0cdd78 chore: bump libs to latest release branch commit
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-10-05 19:38:21 +02:00
Federico Di Pierro
e68151eb07 chore(test,userspace/falco): fixed tests after libs bump.
Moreover, try to create grpc socket folder path only if grpc is actually enabled.

Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2022-10-05 19:38:21 +02:00
Andrea Terzolo
ec7ddbbaf8 chore: bump libs/driver to pre-release tag
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-10-05 19:38:21 +02:00
Jason Dellaluce
663c1d073a fix(userspace/falco): check plugin requirements when validating rule files
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-05 13:21:20 +02:00
Jason Dellaluce
bbb821fb8e refactor(userspace/falco): move rules plugin requirements check in an internal funcion
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-05 13:21:20 +02:00
Jason Dellaluce
5781c53ddc fix(userspace): add explicit constructors and initializations
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-10-03 13:04:15 +02:00
Andrea Terzolo
545b58ee14 update(open_inspector): use variable buffer dim in modern bpf
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-09-28 18:55:06 +02:00
Andrea Terzolo
cf83ff5447 chore: bump libs to latest master
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-09-28 18:55:06 +02:00
Andrea Terzolo
8d8e7622e1 update(cmd_line): put modern bpf to false
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-09-28 18:55:06 +02:00
Andrea Terzolo
fd097e94d7 new(cmdline): add support for modern BPF probe
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-09-28 18:55:06 +02:00
Luca Guerra
6634c896b7 fix(falco): print container info and gvisor info in the same way
Signed-off-by: Luca Guerra <luca@guerra.sh>
2022-09-28 12:45:04 +02:00
spyder-kyle
38c823533c Add PIDs to falco_rules.yaml rules
Signed-off-by: Kyle Smith Hanna <kyle.smithhanna@spyderbat.com>
2022-09-27 10:51:00 +02:00
Andrea Terzolo
3aa9267b48 fix(syscall_buffer): set dimension if page size not available
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-09-27 10:47:59 +02:00
Andrea Terzolo
725714726d update(configuration): define m_syscall_buf_size_preset as uint16_t
improve also some logs for `m_syscall_buf_size_preset` configuration errors

Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
Co-authored-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:47:59 +02:00
Andrea Terzolo
c9fa585801 update: address some review comments
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Melissa Kilby <melissa.kilby.oss@gmail.com>
2022-09-27 10:47:59 +02:00
Andrea Terzolo
90e4634a79 update(syscall_buffer_size): don't crash in case of getpagesize error
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
Co-authored-by: Federico Di Pierro <nierro92@gmail.com>
2022-09-27 10:47:59 +02:00
Andrea Terzolo
b0b2f05eb5 new: configure syscall buffer dimension from Falco
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
2022-09-27 10:47:59 +02:00
Jason Dellaluce
8aea0935c9 chore(userspace/engine): remove unused var
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
9c240198a0 refactor(userspace/engine): refactor falco_engine with new loader defs
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
f6f763fe84 refactor(userspace/engine): clean up rule collector
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
9b5f3ee99e refactor(userspace/engine): clean up rule compiler
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
89e8f70de0 refactor(userspace/engine): clean up and rename rule reader
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
b0f0105116 refactor(userspace/engine): clean up rule loader
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
5f2267f716 update(userspace/engine): add new loader files to CMakeLists
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
b65157af5e refactor(userspace/engine): split rule loader git history (5)
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
b2b1feb1f2 refactor(userspace/engine): split rule loader git history (4)
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
b900e46dfe refactor(userspace/engine): split rule loader git history (3)
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
a98c9cdd20 refactor(userspace/engine): split rule loader git history (2)
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
Jason Dellaluce
2a427925a0 refactor(userspace/engine): split rule loader git history (1)
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-09-27 10:42:59 +02:00
57 changed files with 1800 additions and 1031 deletions

View File

@@ -27,6 +27,14 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND CMAKE_SYSTEM_NAME MATCHES "Linux
endif()
endif()
# Modern BPF is not supported on not Linux systems and in MINIMAL_BUILD
if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND NOT MINIMAL_BUILD)
option(BUILD_FALCO_MODERN_BPF "Build modern BPF support for Falco" OFF)
if(BUILD_FALCO_MODERN_BPF)
add_definitions(-DHAS_MODERN_BPF)
endif()
endif()
# We shouldn't need to set this, see https://gitlab.kitware.com/cmake/cmake/-/issues/16419
option(EP_UPDATE_DISCONNECTED "ExternalProject update disconnected" OFF)
if (${EP_UPDATE_DISCONNECTED})

View File

@@ -26,8 +26,8 @@ else()
# In case you want to test against another driver version (or branch, or commit) just pass the variable -
# ie., `cmake -DDRIVER_VERSION=dev ..`
if(NOT DRIVER_VERSION)
set(DRIVER_VERSION "fd46dd139a8e35692a7d40ab2f0ed2016df827cf")
set(DRIVER_CHECKSUM "SHA256=7c14a4b5f282c9e1e4496f5416d73c3132c72954d581e1a737f82ffa0e3a6bdd")
set(DRIVER_VERSION "9ec78ad55ff558f3381941111b6bf313e043b4b0")
set(DRIVER_CHECKSUM "SHA256=333a0aec05653ade6ff0dbdd057a8fe84abe32c07a22626288c2028b1ebc7d2e")
endif()
# cd /path/to/build && cmake /path/to/source

View File

@@ -27,8 +27,8 @@ else()
# In case you want to test against another falcosecurity/libs version (or branch, or commit) just pass the variable -
# ie., `cmake -DFALCOSECURITY_LIBS_VERSION=dev ..`
if(NOT FALCOSECURITY_LIBS_VERSION)
set(FALCOSECURITY_LIBS_VERSION "fd46dd139a8e35692a7d40ab2f0ed2016df827cf")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=7c14a4b5f282c9e1e4496f5416d73c3132c72954d581e1a737f82ffa0e3a6bdd")
set(FALCOSECURITY_LIBS_VERSION "9ec78ad55ff558f3381941111b6bf313e043b4b0")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=333a0aec05653ade6ff0dbdd057a8fe84abe32c07a22626288c2028b1ebc7d2e")
endif()
# cd /path/to/build && cmake /path/to/source
@@ -60,6 +60,9 @@ set(LIBSINSP_DIR "${FALCOSECURITY_LIBS_SOURCE_DIR}")
# configure gVisor support
set(BUILD_LIBSCAP_GVISOR ${BUILD_FALCO_GVISOR} CACHE BOOL "")
# configure modern BPF support
set(BUILD_LIBSCAP_MODERN_BPF ${BUILD_FALCO_MODERN_BPF} CACHE BOOL "")
# explicitly disable the tests/examples of this dependency
set(CREATE_TEST_TARGETS OFF CACHE BOOL "")
set(BUILD_LIBSCAP_EXAMPLES OFF CACHE BOOL "")

View File

@@ -169,6 +169,61 @@ syscall_event_drops:
syscall_event_timeouts:
max_consecutives: 1000
# --- [Description]
#
# This is an index that controls the dimension of the syscall buffers.
# The syscall buffer is the shared space between Falco and its drivers where all the syscall events
# are stored.
# Falco uses a syscall buffer for every online CPU, and all these buffers share the same dimension.
# So this parameter allows you to control the size of all the buffers!
#
# --- [Usage]
#
# You can choose between different indexes: from `1` to `10` (`0` is reserved for future uses).
# Every index corresponds to a dimension in bytes:
#
# [(*), 1 MB, 2 MB, 4 MB, 8 MB, 16 MB, 32 MB, 64 MB, 128 MB, 256 MB, 512 MB]
# ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
# | | | | | | | | | | |
# 0 1 2 3 4 5 6 7 8 9 10
#
# As you can see the `0` index is reserved, while the index `1` corresponds to
# `1 MB` and so on.
#
# These dimensions in bytes derive from the fact that the buffer size must be:
# (1) a power of 2.
# (2) a multiple of your system_page_dimension.
# (3) greater than `2 * (system_page_dimension)`.
#
# According to these constraints is possible that sometimes you cannot use all the indexes, let's consider an
# example to better understand it:
# If you have a `page_size` of 1 MB the first available buffer size is 4 MB because 2 MB is exactly
# `2 * (system_page_size)` -> `2 * 1 MB`, but this is not enough we need more than `2 * (system_page_size)`!
# So from this example is clear that if you have a page size of 1 MB the first index that you can use is `3`.
#
# Please note: this is a very extreme case just to let you understand the mechanism, usually the page size is something
# like 4 KB so you have no problem at all and you can use all the indexes (from `1` to `10`).
#
# To check your system page size use the Falco `--page-size` command line option. The output on a system with a page
# size of 4096 Bytes (4 KB) should be the following:
#
# "Your system page size is: 4096 bytes."
#
# --- [Suggestions]
#
# Before the introduction of this param the buffer size was fixed to 8 MB (so index `4`, as you can see
# in the default value below).
# You can increase the buffer size when you face syscall drops. A size of 16 MB (so index `5`) can reduce
# syscall drops in production-heavy systems without noticeable impact. Very large buffers however could
# slow down the entire machine.
# On the other side you can try to reduce the buffer size to speed up the system, but this could
# increase the number of syscall drops!
# As a final remark consider that the buffer size is mapped twice in the process' virtual memory so a buffer of 8 MB
# will result in a 16 MB area in the process virtual memory.
# Please pay attention when you use this parameter and change it only if the default size doesn't fit your use case.
syscall_buf_size_preset: 4
# Falco continuously monitors outputs performance. When an output channel does not allow
# to deliver an alert within a given deadline, an error is reported indicating
# which output is blocking notifications.

View File

@@ -367,7 +367,7 @@
desc: Detect any new ssh connection to a host other than those in an allowed group of hosts
condition: (inbound_outbound) and ssh_port and not allowed_ssh_hosts
enabled: false
output: Disallowed SSH Connection (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
output: Disallowed SSH Connection (command=%proc.cmdline pid=%proc.pid connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, mitre_remote_service]
@@ -397,7 +397,7 @@
(fd.snet in (allowed_outbound_destination_networks)) or
(fd.sip.name in (allowed_outbound_destination_domains)))
enabled: false
output: Disallowed outbound connection destination (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
output: Disallowed outbound connection destination (command=%proc.cmdline pid=%proc.pid connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network]
@@ -418,7 +418,7 @@
(fd.cnet in (allowed_inbound_source_networks)) or
(fd.cip.name in (allowed_inbound_source_domains)))
enabled: false
output: Disallowed inbound connection source (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
output: Disallowed inbound connection source (command=%proc.cmdline pid=%proc.pid connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network]
@@ -461,7 +461,7 @@
and not exe_running_docker_save
and not user_known_shell_config_modifiers
output: >
a shell configuration file has been modified (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pcmdline=%proc.pcmdline file=%fd.name container_id=%container.id image=%container.image.repository)
a shell configuration file has been modified (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid pcmdline=%proc.pcmdline file=%fd.name container_id=%container.id image=%container.image.repository)
priority:
WARNING
tags: [file, mitre_persistence]
@@ -479,7 +479,7 @@
(not proc.name in (shell_binaries))
enabled: false
output: >
a shell configuration file was read by a non-shell program (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline file=%fd.name container_id=%container.id image=%container.image.repository)
a shell configuration file was read by a non-shell program (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid file=%fd.name container_id=%container.id image=%container.image.repository)
priority:
WARNING
tags: [file, mitre_discovery]
@@ -495,7 +495,7 @@
not user_known_cron_jobs
enabled: false
output: >
Cron jobs were scheduled to run (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline
Cron jobs were scheduled to run (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid
file=%fd.name container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority:
NOTICE
@@ -874,7 +874,7 @@
and not exe_running_docker_save
and not user_known_update_package_registry
output: >
Repository files get updated (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pcmdline=%proc.pcmdline file=%fd.name newpath=%evt.arg.newpath container_id=%container.id image=%container.image.repository)
Repository files get updated (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid pcmdline=%proc.pcmdline file=%fd.name newpath=%evt.arg.newpath container_id=%container.id image=%container.image.repository)
priority:
NOTICE
tags: [filesystem, mitre_persistence]
@@ -896,7 +896,7 @@
and not user_known_write_below_binary_dir_activities
output: >
File below a known binary directory opened for writing (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
command=%proc.cmdline pid=%proc.pid file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -952,7 +952,7 @@
and not user_known_write_monitored_dir_conditions
output: >
File below a monitored directory opened for writing (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
command=%proc.cmdline pid=%proc.pid file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -969,7 +969,7 @@
enabled: true
output: >
Read monitored file via directory traversal (username=%user.name useruid=%user.uid user_loginuid=%user.loginuid program=%proc.name exe=%proc.exepath
command=%proc.cmdline parent=%proc.pname file=%fd.name fileraw=%fd.nameraw parent=%proc.pname
command=%proc.cmdline pid=%proc.pid parent=%proc.pname file=%fd.name fileraw=%fd.nameraw parent=%proc.pname
gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository returncode=%evt.res cwd=%proc.cwd)
priority: WARNING
tags: [filesystem, mitre_discovery, mitre_exfiltration, mitre_credential_access]
@@ -990,7 +990,7 @@
enabled: false
output: >
ssh-related file/directory read by non-ssh program (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline container_id=%container.id image=%container.image.repository)
command=%proc.cmdline pid=%proc.pid file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline container_id=%container.id image=%container.image.repository)
priority: ERROR
tags: [filesystem, mitre_discovery]
@@ -1276,7 +1276,7 @@
- rule: Write below etc
desc: an attempt to write to any file below /etc
condition: write_etc_common
output: "File below /etc opened for writing (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent=%proc.pname pcmdline=%proc.pcmdline file=%fd.name program=%proc.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)"
output: "File below /etc opened for writing (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent=%proc.pname pcmdline=%proc.pcmdline file=%fd.name program=%proc.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)"
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -1373,7 +1373,7 @@
and not known_root_conditions
and not user_known_write_root_conditions
and not user_known_write_below_root_activities
output: "File below / or /root opened for writing (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent=%proc.pname file=%fd.name program=%proc.name container_id=%container.id image=%container.image.repository)"
output: "File below / or /root opened for writing (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent=%proc.pname file=%fd.name program=%proc.name container_id=%container.id image=%container.image.repository)"
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -1391,7 +1391,7 @@
condition: sensitive_files and open_read and server_procs and not proc_is_new and proc.name!="sshd" and not user_known_read_sensitive_files_activities
output: >
Sensitive file opened for reading by trusted program after startup (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline parent=%proc.pname file=%fd.name parent=%proc.pname gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
command=%proc.cmdline pid=%proc.pid parent=%proc.pname file=%fd.name parent=%proc.pname gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
priority: WARNING
tags: [filesystem, mitre_credential_access]
@@ -1461,7 +1461,7 @@
and not user_read_sensitive_file_containers
output: >
Sensitive file opened for reading by non-trusted program (user=%user.name user_loginuid=%user.loginuid program=%proc.name
command=%proc.cmdline 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)
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]
@@ -1485,7 +1485,7 @@
and not exe_running_docker_save
and not amazon_linux_running_python_yum
and not user_known_write_rpm_database_activities
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline container_id=%container.id image=%container.image.repository)"
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline pid=%proc.pid file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline container_id=%container.id image=%container.image.repository)"
priority: ERROR
tags: [filesystem, software_mgmt, mitre_persistence]
@@ -1524,7 +1524,7 @@
and not user_known_db_spawned_processes
output: >
Database-related program spawned process other than itself (user=%user.name user_loginuid=%user.loginuid
program=%proc.cmdline parent=%proc.pname container_id=%container.id image=%container.image.repository)
program=%proc.cmdline pid=%proc.pid parent=%proc.pname container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [process, database, mitre_execution]
@@ -1535,7 +1535,7 @@
desc: an attempt to modify any file below a set of binary directories.
condition: bin_dir_rename and modify and not package_mgmt_procs and not exe_running_docker_save and not user_known_modify_bin_dir_activities
output: >
File below known binary directory renamed/removed (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline
File below known binary directory renamed/removed (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid
pcmdline=%proc.pcmdline operation=%evt.type file=%fd.name %evt.args container_id=%container.id image=%container.image.repository)
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -1553,7 +1553,7 @@
and not exe_running_docker_save
output: >
Directory below known binary directory created (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline directory=%evt.arg.path container_id=%container.id image=%container.image.repository)
command=%proc.cmdline pid=%proc.pid directory=%evt.arg.path container_id=%container.id image=%container.image.repository)
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -1594,7 +1594,7 @@
and not user_known_change_thread_namespace_activities
enabled: false
output: >
Namespace change (setns) by unexpected program (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline
Namespace change (setns) by unexpected program (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid
parent=%proc.pname %container.info container_id=%container.id image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [process, mitre_privilege_escalation, mitre_lateral_movement]
@@ -1741,7 +1741,7 @@
and not user_shell_container_exclusions
output: >
Shell spawned by untrusted binary (user=%user.name user_loginuid=%user.loginuid shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline pcmdline=%proc.pcmdline gparent=%proc.aname[2] ggparent=%proc.aname[3]
cmdline=%proc.cmdline pid=%proc.pid pcmdline=%proc.pcmdline gparent=%proc.aname[2] ggparent=%proc.aname[3]
aname[4]=%proc.aname[4] aname[5]=%proc.aname[5] aname[6]=%proc.aname[6] aname[7]=%proc.aname[7] container_id=%container.id image=%container.image.repository)
priority: DEBUG
tags: [shell, mitre_execution]
@@ -1925,7 +1925,7 @@
and not falco_privileged_containers
and not user_privileged_containers
and not redhat_image
output: Privileged container started (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
output: Privileged container started (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag)
priority: INFO
tags: [container, cis, mitre_privilege_escalation, mitre_lateral_movement]
@@ -1949,7 +1949,7 @@
and excessively_capable_container
and not falco_privileged_containers
and not user_privileged_containers
output: Excessively capable container started (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag cap_permitted=%thread.cap_permitted)
output: Excessively capable container started (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag cap_permitted=%thread.cap_permitted)
priority: INFO
tags: [container, cis, mitre_privilege_escalation, mitre_lateral_movement]
@@ -1995,7 +1995,7 @@
and sensitive_mount
and not falco_sensitive_mount_containers
and not user_sensitive_mount_containers
output: Container with sensitive mount started (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag mounts=%container.mounts)
output: Container with sensitive mount started (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag mounts=%container.mounts)
priority: INFO
tags: [container, cis, mitre_lateral_movement]
@@ -2015,7 +2015,7 @@
desc: >
Detect the initial process started by a container that is not in a list of allowed containers.
condition: container_started and container and not allowed_containers
output: Container started and not in allowed list (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
output: Container started and not in allowed list (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [container, mitre_lateral_movement]
@@ -2030,7 +2030,7 @@
- rule: System user interactive
desc: an attempt to run interactive commands by a system (i.e. non-login) user
condition: spawned_process and system_users and interactive and not user_known_system_user_login
output: "System user ran an interactive command (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline container_id=%container.id image=%container.image.repository)"
output: "System user ran an interactive command (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid container_id=%container.id image=%container.image.repository)"
priority: INFO
tags: [users, mitre_remote_access_tools]
@@ -2048,7 +2048,7 @@
and not user_expected_terminal_shell_in_container_conditions
output: >
A shell was spawned in a container with an attached terminal (user=%user.name user_loginuid=%user.loginuid %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository)
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline pid=%proc.pid terminal=%proc.tty container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [container, shell, mitre_execution]
@@ -2124,7 +2124,7 @@
and not user_expected_system_procs_network_activity_conditions
output: >
Known system binary sent/received network traffic
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline connection=%fd.name container_id=%container.id image=%container.image.repository)
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid connection=%fd.name container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, mitre_exfiltration]
@@ -2163,7 +2163,7 @@
enabled: false
output: >
Program run with disallowed HTTP_PROXY environment variable
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline env=%proc.env parent=%proc.pname container_id=%container.id image=%container.image.repository)
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid env=%proc.env parent=%proc.pname container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [host, users]
@@ -2179,7 +2179,7 @@
enabled: false
output: >
Interpreted program received/listened for network traffic
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline connection=%fd.name container_id=%container.id image=%container.image.repository)
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid connection=%fd.name container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, mitre_exfiltration]
@@ -2190,7 +2190,7 @@
enabled: false
output: >
Interpreted program performed outgoing network connection
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline connection=%fd.name container_id=%container.id image=%container.image.repository)
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid connection=%fd.name container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, mitre_exfiltration]
@@ -2229,7 +2229,7 @@
enabled: false
output: >
Unexpected UDP Traffic Seen
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline connection=%fd.name proto=%fd.l4proto evt=%evt.type %evt.args container_id=%container.id image=%container.image.repository)
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid connection=%fd.name proto=%fd.l4proto evt=%evt.type %evt.args container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, mitre_exfiltration]
@@ -2289,7 +2289,7 @@
and not user_known_non_sudo_setuid_conditions
output: >
Unexpected setuid call by non-sudo, non-root program (user=%user.name user_loginuid=%user.loginuid cur_uid=%user.uid parent=%proc.pname
command=%proc.cmdline uid=%evt.arg.uid container_id=%container.id image=%container.image.repository)
command=%proc.cmdline pid=%proc.pid uid=%evt.arg.uid container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [users, mitre_privilege_escalation]
@@ -2321,7 +2321,7 @@
not user_known_user_management_activities
output: >
User management binary command run outside of container
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4])
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4])
priority: NOTICE
tags: [host, users, mitre_persistence]
@@ -2345,7 +2345,7 @@
and not fd.name in (allowed_dev_files)
and not fd.name startswith /dev/tty
and not user_known_create_files_below_dev_activities
output: "File created below /dev by untrusted program (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline file=%fd.name container_id=%container.id image=%container.image.repository)"
output: "File created below /dev by untrusted program (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid file=%fd.name container_id=%container.id image=%container.image.repository)"
priority: ERROR
tags: [filesystem, mitre_persistence]
@@ -2367,7 +2367,7 @@
- rule: Contact EC2 Instance Metadata Service From Container
desc: Detect attempts to contact the EC2 Instance Metadata Service from a container
condition: outbound and fd.sip="169.254.169.254" and container and not ec2_metadata_containers
output: Outbound connection to EC2 instance metadata service (command=%proc.cmdline connection=%fd.name %container.info image=%container.image.repository:%container.image.tag)
output: Outbound connection to EC2 instance metadata service (command=%proc.cmdline pid=%proc.pid connection=%fd.name %container.info image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, aws, container, mitre_discovery]
@@ -2384,7 +2384,7 @@
desc: Detect attempts to contact the Cloud Instance Metadata Service from a container
condition: outbound and fd.sip="169.254.169.254" and container and not user_known_metadata_access
enabled: false
output: Outbound connection to cloud instance metadata service (command=%proc.cmdline connection=%fd.name %container.info image=%container.image.repository:%container.image.tag)
output: Outbound connection to cloud instance metadata service (command=%proc.cmdline pid=%proc.pid connection=%fd.name %container.info image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, container, mitre_discovery]
@@ -2412,7 +2412,9 @@
quay.io/prometheus-operator/prometheus-operator,
k8s.gcr.io/ingress-nginx/kube-webhook-certgen, quay.io/spotahome/redis-operator,
registry.opensource.zalan.do/acid/postgres-operator, registry.opensource.zalan.do/acid/postgres-operator-ui,
rabbitmqoperator/cluster-operator)
rabbitmqoperator/cluster-operator,
falcosecurity/falco-no-driver, docker.io/falcosecurity/falco-no-driver,
public.ecr.aws/falcosecurity/falco-no-driver)
or (k8s.ns.name = "kube-system"))
- macro: k8s_api_server
@@ -2430,7 +2432,7 @@
not k8s_containers and
k8s_api_server and
not user_known_contact_k8s_api_server_activities
output: Unexpected connection to K8s API Server from container (command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag connection=%fd.name)
output: Unexpected connection to K8s API Server from container (command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag connection=%fd.name)
priority: NOTICE
tags: [network, k8s, container, mitre_discovery]
@@ -2446,7 +2448,7 @@
- rule: Unexpected K8s NodePort Connection
desc: Detect attempts to use K8s NodePorts from a container
condition: (inbound_outbound) and fd.sport >= 30000 and fd.sport <= 32767 and container and not nodeport_containers
output: Unexpected K8s NodePort Connection (command=%proc.cmdline connection=%fd.name container_id=%container.id image=%container.image.repository)
output: Unexpected K8s NodePort Connection (command=%proc.cmdline pid=%proc.pid connection=%fd.name container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, k8s, container, mitre_port_knocking]
@@ -2483,7 +2485,7 @@
and not pkg_mgmt_in_kube_proxy
output: >
Package management process launched in container (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
command=%proc.cmdline pid=%proc.pid container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: ERROR
tags: [process, mitre_persistence]
@@ -2497,7 +2499,7 @@
)
output: >
Netcat runs inside container that allows remote code execution (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
command=%proc.cmdline pid=%proc.pid container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [network, process, mitre_execution]
@@ -2509,7 +2511,7 @@
condition: >
spawned_process and container and network_tool_procs and not user_known_network_tool_activities
output: >
Network tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent_process=%proc.pname
Network tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent_process=%proc.pname
container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, process, mitre_discovery, mitre_exfiltration]
@@ -2529,7 +2531,7 @@
network_tool_procs and
not user_known_network_tool_activities
output: >
Network tool launched on host (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent_process=%proc.pname)
Network tool launched on host (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent_process=%proc.pname)
priority: NOTICE
tags: [network, process, mitre_discovery, mitre_exfiltration]
@@ -2565,7 +2567,7 @@
)
output: >
Grep private keys or passwords activities found
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline container_id=%container.id container_name=%container.name
(user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid container_id=%container.id container_name=%container.name
image=%container.image.repository:%container.image.tag)
priority:
WARNING
@@ -2599,7 +2601,7 @@
not trusted_logging_images and
not allowed_clear_log_files
output: >
Log files were tampered (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline file=%fd.name container_id=%container.id image=%container.image.repository)
Log files were tampered (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid file=%fd.name container_id=%container.id image=%container.image.repository)
priority:
WARNING
tags: [file, mitre_defense_evasion]
@@ -2617,7 +2619,7 @@
desc: Detect process running to clear bulk data from disk
condition: spawned_process and clear_data_procs and not user_known_remove_data_activities
output: >
Bulk data has been removed from disk (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline file=%fd.name container_id=%container.id image=%container.image.repository)
Bulk data has been removed from disk (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid file=%fd.name container_id=%container.id image=%container.image.repository)
priority:
WARNING
tags: [process, mitre_persistence]
@@ -2658,7 +2660,7 @@
not var_lib_docker_filepath and
not proc.name in (docker_binaries)
output: >
Shell history had been deleted or renamed (user=%user.name user_loginuid=%user.loginuid type=%evt.type command=%proc.cmdline fd.name=%fd.name name=%evt.arg.name path=%evt.arg.path oldpath=%evt.arg.oldpath %container.info)
Shell history had been deleted or renamed (user=%user.name user_loginuid=%user.loginuid type=%evt.type command=%proc.cmdline pid=%proc.pid fd.name=%fd.name name=%evt.arg.name path=%evt.arg.path oldpath=%evt.arg.oldpath %container.info)
priority:
WARNING
tags: [process, mitre_defense_evasion]
@@ -2671,7 +2673,7 @@
((spawned_process and proc.name in (shred, rm, mv) and proc.args contains "bash_history") or
(open_write and fd.name contains "bash_history" and evt.arg.flags contains "O_TRUNC"))
output: >
Shell history had been deleted or renamed (user=%user.name user_loginuid=%user.loginuid type=%evt.type command=%proc.cmdline fd.name=%fd.name name=%evt.arg.name path=%evt.arg.path oldpath=%evt.arg.oldpath %container.info)
Shell history had been deleted or renamed (user=%user.name user_loginuid=%user.loginuid type=%evt.type command=%proc.cmdline pid=%proc.pid fd.name=%fd.name name=%evt.arg.name path=%evt.arg.path oldpath=%evt.arg.oldpath %container.info)
priority:
WARNING
tags: [process, mitre_defense_evasion]
@@ -2698,7 +2700,7 @@
enabled: false
output: >
Setuid or setgid bit is set via chmod (fd=%evt.arg.fd filename=%evt.arg.filename mode=%evt.arg.mode user=%user.name user_loginuid=%user.loginuid process=%proc.name
command=%proc.cmdline container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
command=%proc.cmdline pid=%proc.pid container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority:
NOTICE
tags: [process, mitre_persistence]
@@ -2720,7 +2722,7 @@
and not exe_running_docker_save
enabled: false
output: >
Hidden file or directory created (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline
Hidden file or directory created (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid
file=%fd.name newpath=%evt.arg.newpath container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority:
NOTICE
@@ -2745,7 +2747,7 @@
and remote_file_copy_procs
and not user_known_remote_file_copy_activities
output: >
Remote file copy tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent_process=%proc.pname
Remote file copy tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent_process=%proc.pname
container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, process, mitre_lateral_movement, mitre_exfiltration]
@@ -2756,7 +2758,7 @@
create_symlink and
(evt.arg.target in (sensitive_file_names) or evt.arg.target in (sensitive_directory_names))
output: >
Symlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname)
Symlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname)
priority: WARNING
tags: [file, mitre_exfiltration]
@@ -2766,7 +2768,7 @@
create_hardlink and
(evt.arg.oldpath in (sensitive_file_names))
output: >
Hardlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.oldpath linkpath=%evt.arg.newpath parent_process=%proc.pname)
Hardlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid target=%evt.arg.oldpath linkpath=%evt.arg.newpath parent_process=%proc.pname)
priority: WARNING
tags: [file, mitre_exfiltration]
@@ -2871,14 +2873,14 @@
desc: Miners typically connect to miner pools on common ports.
condition: net_miner_pool and not trusted_images_query_miner_domain_dns
enabled: false
output: Outbound connection to IP/Port flagged by https://cryptoioc.ch (command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository)
output: Outbound connection to IP/Port flagged by https://cryptoioc.ch (command=%proc.cmdline pid=%proc.pid port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository)
priority: CRITICAL
tags: [network, mitre_execution]
- rule: Detect crypto miners using the Stratum protocol
desc: Miners typically specify the mining pool to connect to with a URI that begins with 'stratum+tcp'
condition: spawned_process and (proc.cmdline contains "stratum+tcp" or proc.cmdline contains "stratum2+tcp" or proc.cmdline contains "stratum+ssl" or proc.cmdline contains "stratum2+ssl")
output: Possible miner running (command=%proc.cmdline container=%container.info image=%container.image.repository)
output: Possible miner running (command=%proc.cmdline pid=%proc.pid container=%container.info image=%container.image.repository)
priority: CRITICAL
tags: [process, mitre_execution]
@@ -2908,7 +2910,7 @@
- rule: The docker client is executed in a container
desc: Detect a k8s client tool executed inside a container
condition: spawned_process and container and not user_known_k8s_client_container_parens and proc.name in (k8s_client_binaries)
output: "Docker or kubernetes client executed in container (user=%user.name user_loginuid=%user.loginuid %container.info parent=%proc.pname cmdline=%proc.cmdline image=%container.image.repository:%container.image.tag)"
output: "Docker or kubernetes client executed in container (user=%user.name user_loginuid=%user.loginuid %container.info parent=%proc.pname cmdline=%proc.cmdline pid=%proc.pid image=%container.image.repository:%container.image.tag)"
priority: WARNING
tags: [container, mitre_execution]
@@ -2918,7 +2920,7 @@
- rule: Packet socket created in container
desc: Detect new packet socket at the device driver (OSI Layer 2) level in a container. Packet socket could be used for ARP Spoofing and privilege escalation(CVE-2020-14386) by attacker.
condition: evt.type=socket and evt.arg[0]=AF_PACKET and container and not proc.name in (user_known_packet_socket_binaries)
output: Packet socket was created in a container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline socket_info=%evt.args container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
output: Packet socket was created in a container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid socket_info=%evt.args container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, mitre_discovery]
@@ -2949,7 +2951,7 @@
enabled: false
output: >
Network connection outside local subnet
(command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id
(command=%proc.cmdline pid=%proc.pid connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id
image=%container.image.repository namespace=%k8s.ns.name
fd.rip.name=%fd.rip.name fd.lip.name=%fd.lip.name fd.cip.name=%fd.cip.name fd.sip.name=%fd.sip.name)
priority: WARNING
@@ -2983,7 +2985,7 @@
enabled: false
output: >
Network connection outside authorized port and binary
(command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id
(command=%proc.cmdline pid=%proc.pid connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id
image=%container.image.repository)
priority: WARNING
tags: [network]
@@ -2998,7 +3000,7 @@
desc: Detect redirecting stdout/stdin to network connection in container (potential reverse shell).
condition: dup and container and evt.rawres in (0, 1, 2) and fd.type in ("ipv4", "ipv6") and not user_known_stand_streams_redirect_activities
output: >
Redirect stdout/stdin to network connection (user=%user.name user_loginuid=%user.loginuid %container.info process=%proc.name parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip)
Redirect stdout/stdin to network connection (user=%user.name user_loginuid=%user.loginuid %container.info process=%proc.name parent=%proc.pname cmdline=%proc.cmdline pid=%proc.pid terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip)
priority: NOTICE
# The two Container Drift rules below will fire when a new executable is created in a container.
@@ -3027,7 +3029,7 @@
(evt.arg.mode contains "S_IXGRP") or
(evt.arg.mode contains "S_IXOTH"))
enabled: false
output: Drift detected (chmod), new executable created in a container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline filename=%evt.arg.filename name=%evt.arg.name mode=%evt.arg.mode event=%evt.type)
output: Drift detected (chmod), new executable created in a container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid filename=%evt.arg.filename name=%evt.arg.name mode=%evt.arg.mode event=%evt.type)
priority: ERROR
# ****************************************************************************
@@ -3044,7 +3046,7 @@
not user_known_container_drift_activities and
evt.rawres>=0
enabled: false
output: Drift detected (open+create), new executable created in a container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline filename=%evt.arg.filename name=%evt.arg.name mode=%evt.arg.mode event=%evt.type)
output: Drift detected (open+create), new executable created in a container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid filename=%evt.arg.filename name=%evt.arg.name mode=%evt.arg.mode event=%evt.type)
priority: ERROR
- list: c2_server_ip_list
@@ -3053,7 +3055,7 @@
- rule: Outbound Connection to C2 Servers
desc: Detect outbound connection to command & control servers
condition: outbound and fd.sip in (c2_server_ip_list)
output: Outbound connection to C2 server (command=%proc.cmdline connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
output: Outbound connection to C2 server (command=%proc.cmdline pid=%proc.pid connection=%fd.name user=%user.name user_loginuid=%user.loginuid container_id=%container.id image=%container.image.repository)
priority: WARNING
tags: [network]
@@ -3088,7 +3090,7 @@
- rule: Sudo Potential Privilege Escalation
desc: Privilege escalation vulnerability affecting sudo (<= 1.9.5p2). Executing sudo using sudoedit -s or sudoedit -i command with command-line argument that ends with a single backslash character from an unprivileged user it's possible to elevate the user privileges to root.
condition: spawned_process and user.uid != 0 and (proc.name=sudoedit or proc.name = sudo) and (proc.args contains -s or proc.args contains -i or proc.args contains --login) and (proc.args contains "\ " or proc.args endswith \)
output: "Detect Sudo Privilege Escalation Exploit (CVE-2021-3156) (user=%user.name parent=%proc.pname cmdline=%proc.cmdline %container.info)"
output: "Detect Sudo Privilege Escalation Exploit (CVE-2021-3156) (user=%user.name parent=%proc.pname cmdline=%proc.cmdline pid=%proc.pid %container.info)"
priority: CRITICAL
tags: [filesystem, mitre_privilege_escalation]
@@ -3098,7 +3100,7 @@
spawned_process and container
and container.privileged=true
and proc.name=debugfs
output: Debugfs launched started in a privileged container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
output: Debugfs launched started in a privileged container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [container, cis, mitre_lateral_movement]
@@ -3122,7 +3124,7 @@
and not mount_info
and not known_gke_mount_in_privileged_containers
and not user_known_mount_in_privileged_containers
output: Mount was executed inside a privileged container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
output: Mount was executed inside a privileged container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [container, cis, mitre_lateral_movement]
@@ -3136,7 +3138,7 @@
user.uid != 0 and
(evt.rawres >= 0 or evt.res != -1) and
not proc.name in (user_known_userfaultfd_processes)
output: An userfaultfd syscall was successfully executed by an unprivileged user (user=%user.name user_loginuid=%user.loginuid process=%proc.name command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
output: An userfaultfd syscall was successfully executed by an unprivileged user (user=%user.name user_loginuid=%user.loginuid process=%proc.name command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag)
priority: CRITICAL
tags: [syscall, mitre_defense_evasion]
@@ -3166,7 +3168,7 @@
(ingress_remote_file_copy_procs or curl_download) and
not user_known_ingress_remote_file_copy_activities
output: >
Ingress remote file copy tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent_process=%proc.pname
Ingress remote file copy tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent_process=%proc.pname
container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, process, mitre_command_and_control]
@@ -3178,7 +3180,7 @@
condition:
spawned_process and user.uid != 0 and proc.name=pkexec and proc.args = ''
output:
"Detect Polkit pkexec Local Privilege Escalation Exploit (CVE-2021-4034) (user=%user.loginname uid=%user.loginuid command=%proc.cmdline args=%proc.args)"
"Detect Polkit pkexec Local Privilege Escalation Exploit (CVE-2021-4034) (user=%user.loginname uid=%user.loginuid command=%proc.cmdline pid=%proc.pid args=%proc.args)"
priority: CRITICAL
tags: [process, mitre_privilege_escalation]
@@ -3201,7 +3203,7 @@
desc: Detected Java process downloading a class file which could indicate a successful exploit of the log4shell Log4j vulnerability (CVE-2021-44228)
condition: >
java_network_read and evt.buffer bcontains cafebabe
output: Java process class file download (user=%user.name user_loginname=%user.loginname user_loginuid=%user.loginuid event=%evt.type connection=%fd.name server_ip=%fd.sip server_port=%fd.sport proto=%fd.l4proto process=%proc.name command=%proc.cmdline parent=%proc.pname buffer=%evt.buffer container_id=%container.id image=%container.image.repository)
output: Java process class file download (user=%user.name user_loginname=%user.loginname user_loginuid=%user.loginuid event=%evt.type connection=%fd.name server_ip=%fd.sip server_port=%fd.sport proto=%fd.l4proto process=%proc.name command=%proc.cmdline pid=%proc.pid parent=%proc.pname buffer=%evt.buffer container_id=%container.id image=%container.image.repository)
priority: CRITICAL
tags: [mitre_initial_access]
@@ -3217,7 +3219,7 @@
open_write and container and (fd.name=/proc/self/exe or fd.name startswith /proc/self/fd/) and not docker_procs and not proc.cmdline = "runc:[1:CHILD] init"
enabled: false
output: >
Detect Potential Container Breakout Exploit (CVE-2019-5736) (user=%user.name process=%proc.name file=%fd.name cmdline=%proc.cmdline %container.info)
Detect Potential Container Breakout Exploit (CVE-2019-5736) (user=%user.name process=%proc.name file=%fd.name cmdline=%proc.cmdline pid=%proc.pid %container.info)
priority: WARNING
tags: [container, filesystem, mitre_initial_access]
@@ -3235,6 +3237,6 @@
and not proc.name in (known_binaries_to_read_environment_variables_from_proc_files)
output: >
Environment variables were retrieved from /proc files (user=%user.name user_loginuid=%user.loginuid program=%proc.name
command=%proc.cmdline 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)
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]

View File

@@ -41,3 +41,34 @@ case "$1" in
fi
;;
esac
# Based off what debhelper dh_systemd_enable/13.3.4 would have added
# ref: https://www.debian.org/doc/manuals/debmake-doc/ch05.en.html#debhelper
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
# This will only remove masks created by d-s-h on package removal.
deb-systemd-helper unmask 'falco.service' >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet was-enabled 'falco.service'; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper enable 'falco.service' >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper update-state 'falco.service' >/dev/null || true
fi
fi
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -d /run/systemd/system ]; then
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
_dh_action=restart
else
_dh_action=start
fi
deb-systemd-invoke $_dh_action 'falco.service' >/dev/null || true
fi
fi

View File

@@ -15,3 +15,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Based off what debhelper dh_systemd_enable/13.3.4 would have added
# ref: https://www.debian.org/doc/manuals/debmake-doc/ch05.en.html#debhelper
set -e
if [ -d /run/systemd/system ] && [ "$1" = remove ]; then
systemctl --system daemon-reload >/dev/null || true
fi
if [ "$1" = "remove" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper mask 'falco.service' >/dev/null || true
fi
fi
if [ "$1" = "purge" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper purge 'falco.service' >/dev/null || true
deb-systemd-helper unmask 'falco.service' >/dev/null || true
fi
fi

View File

@@ -17,6 +17,14 @@
#
set -e
# Based off what debhelper dh_systemd_enable/13.3.4 would have added
# ref: https://www.debian.org/doc/manuals/debmake-doc/ch05.en.html#debhelper
# Currently running falco service uses the driver, so stop it before driver cleanup
if [ -d /run/systemd/system ] && [ "$1" = remove ]; then
deb-systemd-invoke stop 'falco.service' >/dev/null || true
fi
case "$1" in
remove|upgrade|deconfigure)
/usr/bin/falco-driver-loader --clean

View File

@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -e
mod_version="@DRIVER_VERSION@"
dkms add -m falco -v $mod_version --rpm_safe_upgrade
@@ -29,3 +30,35 @@ else
echo -e "Module build for the currently running kernel was skipped since the"
echo -e "kernel source for this kernel does not seem to be installed."
fi
# validate rpm macros by `rpm -qp --scripts <rpm>`
# RPM scriptlets: https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_systemd
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_syntax
# systemd_post macro expands to
# if postinst:
# `systemd-update-helper install-system-units <service>`
%systemd_post 'falco.service'
# post install mirrored from .deb
if [ $1 -eq 1 ]; then
# This will only remove masks created on package removal.
/usr/bin/systemctl --system unmask 'falco.service' >/dev/null || true
# enable falco on installation
# note: DEB postinstall script checks for changed symlinks
/usr/bin/systemctl --system enable 'falco.service' >/dev/null || true
# start falco on installation
/usr/bin/systemctl --system start 'falco.service' >/dev/null || true
fi
# post upgrade mirrored from .deb
if [ $1 -gt 1 ]; then
if [ -d /run/systemd/system ]; then
/usr/bin/systemctl --system daemon-reload >/dev/null || true
# restart falco on upgrade if service is already running
/usr/bin/systemctl --system condrestart 'falco.service' >/dev/null || true
fi
fi

View File

@@ -14,3 +14,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -e
# post uninstall mirrored from .deb
if [ -d /run/systemd/system ] && [ "$1" = 0 ]; then
/usr/bin/systemctl --system daemon-reload >/dev/null || true
/usr/bin/systemctl --system mask 'falco.service' >/dev/null || true
fi
# validate rpm macros by `rpm -qp --scripts <rpm>`
# RPM scriptlets: https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_systemd
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_syntax
# systemd_postun_with_restart macro expands to
# if package upgrade, not uninstall:
# `systemd-update-helper mark-restart-system-units <service>`
%systemd_postun_with_restart 'falco.service'

View File

@@ -14,5 +14,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -e
# pre uninstall mirrored from .deb
# Currently running falco service uses the driver, so stop it before driver cleanup
if [ -d /run/systemd/system ] && [ $1 -eq 0 ]; then
# stop falco service before uninstall
/usr/bin/systemctl --system stop 'falco.service' >/dev/null || true
fi
/usr/bin/falco-driver-loader --clean
# validate rpm macros by `rpm -qp --scripts <rpm>`
# RPM scriptlets: https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_systemd
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_syntax
# systemd_preun macro expands to
# if preuninstall:
# `systemd-update-helper remove-system-units <service>`
%systemd_preun 'falco.service'

View File

@@ -724,7 +724,7 @@ trace_files: !mux
- "Write below etc": 1
- "System procs network activity": 1
- "Mkdir binary dirs": 1
- "System user interactive": 1
- "System user interactive": 0
- "DB program spawned process": 1
- "Non sudo setuid": 1
- "Create files below dev": 1

View File

@@ -62,7 +62,7 @@ traces: !mux
falco-event-generator:
trace_file: traces-positive/falco-event-generator.scap
detect: True
detect_level: [ERROR, WARNING, INFO, NOTICE, DEBUG]
detect_level: [ERROR, WARNING, NOTICE, DEBUG]
detect_counts:
- "Write below binary dir": 1
- "Read sensitive file untrusted": 3
@@ -71,7 +71,7 @@ traces: !mux
- "Write below etc": 1
- "System procs network activity": 1
- "Mkdir binary dirs": 1
- "System user interactive": 1
- "System user interactive": 0
- "DB program spawned process": 1
- "Non sudo setuid": 1
- "Create files below dev": 1

View File

@@ -21,9 +21,11 @@ set(FALCO_ENGINE_SOURCE_FILES
filter_macro_resolver.cpp
filter_evttype_resolver.cpp
filter_warning_resolver.cpp
stats_manager.cpp
rule_loader.cpp
rule_reader.cpp
stats_manager.cpp)
rule_loader_reader.cpp
rule_loader_collector.cpp
rule_loader_compiler.cpp)
add_library(falco_engine STATIC ${FALCO_ENGINE_SOURCE_FILES})
add_dependencies(falco_engine njson string-view-lite)

View File

@@ -27,7 +27,8 @@ limitations under the License.
#include "falco_engine.h"
#include "falco_utils.h"
#include "falco_engine_version.h"
#include "rule_reader.h"
#include "rule_loader_reader.h"
#include "rule_loader_compiler.h"
#include "formats.h"
@@ -59,7 +60,7 @@ falco_engine::falco_engine(bool seed_rng)
falco_engine::~falco_engine()
{
m_rules.clear();
m_rule_loader.clear();
m_rule_collector.clear();
m_rule_stats_manager.clear();
m_sources.clear();
}
@@ -185,15 +186,17 @@ std::unique_ptr<load_result> falco_engine::load_rules(const std::string &rules_c
cfg.replace_output_container_info = m_replace_container_info;
cfg.default_ruleset_id = m_default_ruleset_id;
rule_reader reader;
if (reader.load(cfg, m_rule_loader))
rule_loader::reader reader;
if (reader.read(cfg, m_rule_collector))
{
for (auto &src : m_sources)
{
src.ruleset = src.ruleset_factory->new_ruleset();
}
rule_loader::compiler compiler;
m_rules.clear();
m_rule_loader.compile(cfg, m_rules);
compiler.compile(cfg, m_rule_collector, m_rules);
}
if (cfg.res->successful())
@@ -445,7 +448,7 @@ void falco_engine::read_file(const std::string& filename, std::string& contents)
is.open(filename);
if (!is.is_open())
{
throw falco_exception("Could not open " + filename + " for reading.");
throw falco_exception("Could not open " + filename + " for reading");
}
contents.assign(istreambuf_iterator<char>(is),
@@ -515,7 +518,7 @@ bool falco_engine::check_plugin_requirements(
std::string& err) const
{
err = "";
for (const auto &alternatives : m_rule_loader.required_plugin_versions())
for (const auto &alternatives : m_rule_collector.required_plugin_versions())
{
if (!check_plugin_requirement_alternatives(plugins, alternatives, err))
{

View File

@@ -32,6 +32,7 @@ limitations under the License.
#include "gen_filter.h"
#include "filter_ruleset.h"
#include "rule_loader.h"
#include "rule_loader_collector.h"
#include "stats_manager.h"
#include "falco_common.h"
#include "falco_source.h"
@@ -278,7 +279,7 @@ private:
//
inline bool should_drop_evt() const;
rule_loader m_rule_loader;
rule_loader::collector m_rule_collector;
indexed_vector<falco_rule> m_rules;
stats_manager m_rule_stats_manager;

View File

@@ -56,6 +56,8 @@ public:
LOAD_UNKNOWN_ITEM
};
virtual ~load_result() = default;
// The warning code as a string
static const std::string& warning_code_str(warning_code ec);

View File

@@ -26,6 +26,12 @@ limitations under the License.
*/
struct falco_rule
{
falco_rule(): id(0), priority(falco_common::PRIORITY_DEBUG) {}
falco_rule(falco_rule&&) = default;
falco_rule& operator = (falco_rule&&) = default;
falco_rule(const falco_rule&) = default;
falco_rule& operator = (const falco_rule&) = default;
std::size_t id;
std::string source;
std::string name;

View File

@@ -26,6 +26,23 @@ limitations under the License.
*/
struct falco_source
{
falco_source() = default;
falco_source(falco_source&&) = default;
falco_source& operator = (falco_source&&) = default;
falco_source(const falco_source& s):
name(s.name),
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_factory = s.ruleset_factory;
filter_factory = s.filter_factory;
formatter_factory = s.formatter_factory;
return *this;
};
std::string name;
std::shared_ptr<filter_ruleset> ruleset;
std::shared_ptr<filter_ruleset_factory> ruleset_factory;
@@ -36,7 +53,7 @@ struct falco_source
// matches an event.
mutable falco_rule m_rule;
inline bool is_field_defined(std::string field) const
inline bool is_field_defined(const std::string& field) const
{
auto *chk = filter_factory->new_filtercheck(field.c_str());
if (chk)

View File

@@ -17,12 +17,11 @@ limitations under the License.
#include "filter_evttype_resolver.h"
#include <sinsp.h>
using namespace std;
using namespace libsinsp::filter;
extern sinsp_evttables g_infotables;
static bool is_evttype_operator(const string& op)
static bool is_evttype_operator(const std::string& op)
{
return op == "==" || op == "=" || op == "!=" || op == "in";
}
@@ -44,7 +43,7 @@ void filter_evttype_resolver::visitor::inversion(falco_event_types& types)
}
}
void filter_evttype_resolver::visitor::evttypes(string evtname, falco_event_types& out)
void filter_evttype_resolver::visitor::evttypes(const std::string& evtname, falco_event_types& out)
{
// Fill in from 2 to PPM_EVENT_MAX-1. 0 and 1 are excluded as
// those are PPM_GENERIC_E/PPME_GENERIC_X
@@ -53,7 +52,7 @@ void filter_evttype_resolver::visitor::evttypes(string evtname, falco_event_type
{
// Skip unused events or events not matching the requested evtname
if(!(etable[i].flags & EF_UNUSED)
&& (evtname.empty() || string(etable[i].name) == evtname))
&& (evtname.empty() || std::string(etable[i].name) == evtname))
{
out.insert(i);
}

View File

@@ -157,7 +157,7 @@ public:
string is passed, all the available evttypes are collected
\param out The set to be filled with the evttypes
*/
inline void evttypes(std::string evtname, falco_event_types& out) const
inline void evttypes(const std::string& evtname, falco_event_types& out) const
{
falco_event_types evt_types;
visitor().evttypes(evtname, evt_types);
@@ -188,6 +188,12 @@ public:
private:
struct visitor : public libsinsp::filter::ast::expr_visitor
{
visitor(): m_expect_value(false) {}
visitor(visitor&&) = default;
visitor& operator = (visitor&&) = default;
visitor(const visitor&) = default;
visitor& operator = (const visitor&) = default;
bool m_expect_value;
falco_event_types m_last_node_evttypes;
@@ -199,6 +205,6 @@ private:
void visit(libsinsp::filter::ast::unary_check_expr* e) override;
void visit(libsinsp::filter::ast::binary_check_expr* e) override;
void inversion(falco_event_types& types);
void evttypes(std::string evtname, falco_event_types& out);
void evttypes(const std::string& evtname, falco_event_types& out);
};
};

View File

@@ -81,6 +81,12 @@ class filter_macro_resolver
struct visitor : public libsinsp::filter::ast::expr_visitor
{
visitor() = default;
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;

View File

@@ -151,5 +151,7 @@ public:
class filter_ruleset_factory
{
public:
virtual ~filter_ruleset_factory() = default;
virtual std::shared_ptr<filter_ruleset> new_ruleset() = 0;
};

View File

@@ -48,6 +48,12 @@ public:
private:
struct visitor : public libsinsp::filter::ast::base_expr_visitor
{
visitor(): m_is_equality_check(false) {}
visitor(visitor&&) = default;
visitor& operator = (visitor&&) = default;
visitor(const visitor&) = delete;
visitor& operator = (const visitor&) = delete;
bool m_is_equality_check;
std::set<falco::load_result::warning_code>* m_warnings;

View File

@@ -28,7 +28,12 @@ template <typename T>
class indexed_vector
{
public:
indexed_vector() = default;
virtual ~indexed_vector() = default;
indexed_vector(indexed_vector&&) = default;
indexed_vector& operator = (indexed_vector&&) = default;
indexed_vector(const indexed_vector&) = default;
indexed_vector& operator = (const indexed_vector&) = default;
/*!
\brief Returns the number of elements
@@ -68,7 +73,7 @@ public:
\param index String index of the element to be added in the vector
\return The numeric index assigned to the element
*/
virtual inline size_t insert(T& entry, const std::string& index)
virtual inline size_t insert(const T& entry, const std::string& index)
{
size_t id;
auto prev = m_index.find(index);
@@ -89,7 +94,7 @@ public:
*/
virtual inline T* at(size_t id) const
{
if (id <= m_entries.size())
if (id < m_entries.size())
{
return (T* const) &m_entries[id];
}

View File

@@ -14,27 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include "falco_engine.h"
#include "falco_utils.h"
#include "rule_loader.h"
#include "filter_macro_resolver.h"
#include "filter_evttype_resolver.h"
#include "filter_warning_resolver.h"
#include <version.h>
#include <string>
#include <sstream>
#define MAX_VISIBILITY ((uint32_t) -1)
#include "rule_loader.h"
#define THROW(cond, err, ctx) { if ((cond)) { throw rule_loader::rule_load_exception(falco::load_result::LOAD_ERR_VALIDATE, (err), (ctx)); } }
using namespace falco;
static string s_container_info_fmt = "%container.info";
static string s_default_extra_fmt = "%container.name (id=%container.id)";
using namespace std;
using namespace libsinsp::filter;
static const std::string item_type_strings[] = {
"value for",
@@ -521,7 +504,7 @@ const nlohmann::json& rule_loader::result::as_json(const rules_contents_t& conte
}
rule_loader::engine_version_info::engine_version_info(context &ctx)
: ctx(ctx)
: ctx(ctx), version(0)
{
}
@@ -536,12 +519,12 @@ rule_loader::plugin_version_info::plugin_version_info(context &ctx)
}
rule_loader::list_info::list_info(context &ctx)
: ctx(ctx)
: ctx(ctx), used(false), index(0), visibility(0)
{
}
rule_loader::macro_info::macro_info(context &ctx)
: ctx(ctx), cond_ctx(ctx)
: ctx(ctx), cond_ctx(ctx), used(false), index(0), visibility(0)
{
}
@@ -551,520 +534,13 @@ rule_loader::rule_exception_info::rule_exception_info(context &ctx)
}
rule_loader::rule_info::rule_info(context &ctx)
: ctx(ctx), cond_ctx(ctx), output_ctx(ctx)
: ctx(ctx), cond_ctx(ctx), output_ctx(ctx), index(0), visibility(0),
priority(falco_common::PRIORITY_DEBUG), enabled(true),
warn_evttypes(true), skip_if_unknown_filter(false)
{
}
// todo(jasondellaluce): this breaks string escaping in lists and exceptions
static void quote_item(string& e)
{
if (e.find(" ") != string::npos && e[0] != '"' && e[0] != '\'')
{
e = '"' + e + '"';
}
}
static void paren_item(string& e)
{
if(e[0] != '(')
{
e = '(' + e + ')';
}
}
static inline bool is_operator_defined(const string& op)
{
auto ops = libsinsp::filter::parser::supported_operators();
return find(ops.begin(), ops.end(), op) != ops.end();
}
static inline bool is_operator_for_list(const string& op)
{
auto ops = libsinsp::filter::parser::supported_operators(true);
return find(ops.begin(), ops.end(), op) != ops.end();
}
static bool is_format_valid(const falco_source& source, string fmt, string& err)
{
try
{
shared_ptr<gen_event_formatter> formatter;
formatter = source.formatter_factory->create_formatter(fmt);
return true;
}
catch(exception &e)
{
err = e.what();
return false;
}
}
template <typename T>
static inline void define_info(indexed_vector<T>& infos, T& info, uint32_t id)
{
auto prev = infos.at(info.name);
if (prev)
{
info.index = prev->index;
info.visibility = id;
*prev = info;
}
else
{
info.index = id;
info.visibility = id;
infos.insert(info, info.name);
}
}
template <typename T>
static inline void append_info(T* prev, T& info, uint32_t id)
{
prev->visibility = id;
}
static void validate_exception_info(
const falco_source& source,
rule_loader::rule_exception_info &ex)
{
if (ex.fields.is_list)
{
if (!ex.comps.is_valid())
{
ex.comps.is_list = true;
for (size_t i = 0; i < ex.fields.items.size(); i++)
{
ex.comps.items.push_back({false, "="});
}
}
THROW(ex.fields.items.size() != ex.comps.items.size(),
"Fields and comps lists must have equal length",
ex.ctx);
for (auto &v : ex.comps.items)
{
THROW(!is_operator_defined(v.item),
std::string("'") + v.item + "' is not a supported comparison operator",
ex.ctx);
}
for (auto &v : ex.fields.items)
{
THROW(!source.is_field_defined(v.item),
std::string("'") + v.item + "' is not a supported filter field",
ex.ctx);
}
}
else
{
if (!ex.comps.is_valid())
{
ex.comps.is_list = false;
ex.comps.item = "in";
}
THROW(ex.comps.is_list,
"Fields and comps must both be strings",
ex.ctx);
THROW((ex.comps.item != "in" && ex.comps.item != "pmatch" && ex.comps.item != "intersects"),
"When fields is a single value, comps must be one of (in, pmatch, intersects)",
ex.ctx);
THROW(!source.is_field_defined(ex.fields.item),
std::string("'") + ex.fields.item + "' is not a supported filter field",
ex.ctx);
}
}
static void build_rule_exception_infos(
const vector<rule_loader::rule_exception_info>& exceptions,
set<string>& exception_fields,
string& condition)
{
string tmp;
for (auto &ex : exceptions)
{
string icond;
if(!ex.fields.is_list)
{
for (auto &val : ex.values)
{
THROW(val.is_list,
"Expected values array to contain a list of strings",
ex.ctx)
icond += icond.empty()
? ("(" + ex.fields.item + " "
+ ex.comps.item + " (")
: ", ";
exception_fields.insert(ex.fields.item);
tmp = val.item;
quote_item(tmp);
icond += tmp;
}
icond += icond.empty() ? "" : "))";
}
else
{
icond = "(";
for (auto &values : ex.values)
{
THROW(ex.fields.items.size() != values.items.size(),
"Fields and values lists must have equal length",
ex.ctx);
icond += icond == "(" ? "" : " or ";
icond += "(";
uint32_t k = 0;
string istr;
for (auto &field : ex.fields.items)
{
icond += k == 0 ? "" : " and ";
if (values.items[k].is_list)
{
istr = "(";
for (auto &v : values.items[k].items)
{
tmp = v.item;
quote_item(tmp);
istr += istr == "(" ? "" : ", ";
istr += tmp;
}
istr += ")";
}
else
{
istr = values.items[k].item;
if(is_operator_for_list(ex.comps.items[k].item))
{
paren_item(istr);
}
else
{
quote_item(istr);
}
}
icond += " " + field.item;
icond += " " + ex.comps.items[k].item + " " + istr;
exception_fields.insert(field.item);
k++;
}
icond += ")";
}
icond += ")";
if (icond == "()")
{
icond = "";
}
}
condition += icond.empty() ? "" : " and not " + icond;
}
}
// todo(jasondellaluce): this breaks string escaping in lists
static bool resolve_list(string& cnd, const rule_loader::list_info& list)
{
static string blanks = " \t\n\r";
static string delims = blanks + "(),=";
string new_cnd;
size_t start, end;
bool used = false;
start = cnd.find(list.name);
while (start != string::npos)
{
// the characters surrounding the name must
// be delims of beginning/end of string
end = start + list.name.length();
if ((start == 0 || delims.find(cnd[start - 1]) != string::npos)
&& (end >= cnd.length() || delims.find(cnd[end]) != string::npos))
{
// shift pointers to consume all whitespaces
while (start > 0
&& blanks.find(cnd[start - 1]) != string::npos)
{
start--;
}
while (end < cnd.length()
&& blanks.find(cnd[end]) != string::npos)
{
end++;
}
// create substitution string by concatenating all values
string sub = "";
for (auto &v : list.items)
{
if (!sub.empty())
{
sub += ", ";
}
sub += v;
}
// if substituted list is empty, we need to
// remove a comma from the left or the right
if (sub.empty())
{
if (start > 0 && cnd[start - 1] == ',')
{
start--;
}
else if (end < cnd.length() && cnd[end] == ',')
{
end++;
}
}
// compose new string with substitution
new_cnd = "";
if (start > 0)
{
new_cnd += cnd.substr(0, start) + " ";
}
new_cnd += sub + " ";
if (end <= cnd.length())
{
new_cnd += cnd.substr(end);
}
cnd = new_cnd;
start += sub.length() + 1;
used = true;
}
start = cnd.find(list.name, start + 1);
}
return used;
}
static void resolve_macros(
indexed_vector<rule_loader::macro_info>& macros,
shared_ptr<ast::expr>& ast,
uint32_t visibility,
const rule_loader::context &ctx)
{
filter_macro_resolver macro_resolver;
for (auto &m : macros)
{
if (m.index < visibility)
{
macro_resolver.set_macro(m.name, m.cond_ast);
}
}
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())
{
macros.at(m)->used = true;
}
}
// note: there is no visibility order between filter conditions and lists
static shared_ptr<ast::expr> parse_condition(
string condition,
indexed_vector<rule_loader::list_info>& lists,
const rule_loader::context &ctx)
{
for (auto &l : lists)
{
if (resolve_list(condition, l))
{
l.used = true;
}
}
libsinsp::filter::parser p(condition);
p.set_max_depth(1000);
try
{
shared_ptr<ast::expr> res_ptr(p.parse());
return res_ptr;
}
catch (const sinsp_exception& e)
{
rule_loader::context parsectx(p.get_pos(), condition, ctx);
throw rule_loader::rule_load_exception(
load_result::LOAD_ERR_COMPILE_CONDITION,
e.what(),
parsectx);
}
}
static void apply_output_substitutions(
rule_loader::configuration& cfg,
string& out)
{
if (out.find(s_container_info_fmt) != string::npos)
{
if (cfg.replace_output_container_info)
{
out = replace(out, s_container_info_fmt, cfg.output_extra);
return;
}
out = replace(out, s_container_info_fmt, s_default_extra_fmt);
}
out += cfg.output_extra.empty() ? "" : " " + cfg.output_extra;
}
void rule_loader::clear()
{
m_cur_index = 0;
m_rule_infos.clear();
m_list_infos.clear();
m_macro_infos.clear();
m_required_plugin_versions.clear();
}
const std::vector<rule_loader::plugin_version_info::requirement_alternatives>& rule_loader::required_plugin_versions() const
{
return m_required_plugin_versions;
}
void rule_loader::define(configuration& cfg, engine_version_info& info)
{
auto v = falco_engine::engine_version();
THROW(v < info.version, "Rules require engine version "
+ to_string(info.version) + ", but engine version is " + to_string(v),
info.ctx);
}
void rule_loader::define(configuration& cfg, plugin_version_info& info)
{
std::unordered_set<std::string> plugin_names;
for (const auto& req : info.alternatives)
{
sinsp_version plugin_version(req.version);
THROW(!plugin_version.m_valid,
"Invalid required version '" + req.version
+ "' for plugin '" + req.name + "'",
info.ctx);
THROW(plugin_names.find(req.name) != plugin_names.end(),
"Defined multiple alternative version requirements for plugin '"
+ req.name + "'",
info.ctx);
plugin_names.insert(req.name);
}
m_required_plugin_versions.push_back(info.alternatives);
}
void rule_loader::define(configuration& cfg, list_info& info)
{
define_info(m_list_infos, info, m_cur_index++);
}
void rule_loader::append(configuration& cfg, list_info& info)
{
auto prev = m_list_infos.at(info.name);
THROW(!prev,
"List has 'append' key but no list by that name already exists",
info.ctx);
prev->items.insert(prev->items.end(), info.items.begin(), info.items.end());
append_info(prev, info, m_cur_index++);
}
void rule_loader::define(configuration& cfg, macro_info& info)
{
define_info(m_macro_infos, info, m_cur_index++);
}
void rule_loader::append(configuration& cfg, macro_info& info)
{
auto prev = m_macro_infos.at(info.name);
THROW(!prev,
"Macro has 'append' key but no macro by that name already exists",
info.ctx);
prev->cond += " ";
prev->cond += info.cond;
append_info(prev, info, m_cur_index++);
}
void rule_loader::define(configuration& cfg, rule_info& info)
{
auto source = cfg.sources.at(info.source);
if (!source)
{
cfg.res->add_warning(load_result::LOAD_UNKNOWN_SOURCE,
"Unknown source " + info.source + ", skipping",
info.ctx);
return;
}
auto prev = m_rule_infos.at(info.name);
THROW(prev && prev->source != info.source,
"Rule has been re-defined with a different source",
info.ctx);
for (auto &ex : info.exceptions)
{
THROW(!ex.fields.is_valid(),
"Rule exception item must have fields property with a list of fields",
ex.ctx);
validate_exception_info(*source, ex);
}
define_info(m_rule_infos, info, m_cur_index++);
}
void rule_loader::append(configuration& cfg, rule_info& info)
{
auto prev = m_rule_infos.at(info.name);
THROW(!prev,
"Rule has 'append' key but no rule by that name already exists",
info.ctx);
THROW(info.cond.empty() && info.exceptions.empty(),
"Appended rule must have exceptions or condition property",
info.ctx);
auto source = cfg.sources.at(prev->source);
// note: this is not supposed to happen
THROW(!source,
std::string("Unknown source ") + prev->source,
info.ctx);
if (!info.cond.empty())
{
prev->cond += " ";
prev->cond += info.cond;
}
for (auto &ex : info.exceptions)
{
auto prev_ex = find_if(prev->exceptions.begin(), prev->exceptions.end(),
[&ex](const rule_loader::rule_exception_info& i)
{ return i.name == ex.name; });
if (prev_ex == prev->exceptions.end())
{
THROW(!ex.fields.is_valid(),
"Rule exception must have fields property with a list of fields",
ex.ctx);
THROW(ex.values.empty(),
"Rule exception must have values property with a list of values",
ex.ctx);
validate_exception_info(*source, ex);
prev->exceptions.push_back(ex);
}
else
{
THROW(ex.fields.is_valid(),
"Can not append exception fields to existing exception, only values",
ex.ctx);
THROW(ex.comps.is_valid(),
"Can not append exception comps to existing exception, only values",
ex.ctx);
prev_ex->values.insert(
prev_ex->values.end(), ex.values.begin(), ex.values.end());
}
}
append_info(prev, info, m_cur_index++);
}
void rule_loader::enable(configuration& cfg, rule_info& info)
{
auto prev = m_rule_infos.at(info.name);
THROW(!prev,
"Rule has 'enabled' key but no rule by that name already exists",
info.ctx);
prev->enabled = info.enabled;
}
rule_loader::rule_load_exception::rule_load_exception(load_result::error_code ec, std::string msg, const context& ctx)
rule_loader::rule_load_exception::rule_load_exception(falco::load_result::error_code ec, std::string msg, const context& ctx)
: ec(ec), msg(msg), ctx(ctx)
{
}
@@ -1075,235 +551,8 @@ rule_loader::rule_load_exception::~rule_load_exception()
const char* rule_loader::rule_load_exception::what()
{
errstr = load_result::error_code_str(ec) + ": "
errstr = falco::load_result::error_code_str(ec) + ": "
+ msg.c_str();
return errstr.c_str();
}
void rule_loader::compile_list_infos(configuration& cfg, indexed_vector<list_info>& out) const
{
string tmp;
vector<string> used;
for (auto &list : m_list_infos)
{
list_info v = list;
v.items.clear();
for (auto &item : list.items)
{
auto ref = m_list_infos.at(item);
if (ref && ref->index < list.visibility)
{
used.push_back(ref->name);
for (auto val : ref->items)
{
quote_item(val);
v.items.push_back(val);
}
}
else
{
tmp = item;
quote_item(tmp);
v.items.push_back(tmp);
}
}
v.used = false;
out.insert(v, v.name);
}
for (auto &v : used)
{
out.at(v)->used = true;
}
}
// note: there is a visibility ordering between macros
void rule_loader::compile_macros_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& out) const
{
set<string> used;
for (auto &m : m_macro_infos)
{
macro_info entry = m;
entry.cond_ast = parse_condition(m.cond, lists, m.cond_ctx);
entry.used = false;
out.insert(entry, m.name);
}
for (auto &m : out)
{
resolve_macros(out, m.cond_ast, m.visibility, m.ctx);
}
}
void rule_loader::compile_rule_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out) const
{
string err, condition;
set<load_result::warning_code> warn_codes;
filter_warning_resolver warn_resolver;
for (auto &r : m_rule_infos)
{
// skip the rule if below the minimum priority
if (r.priority > cfg.min_priority)
{
continue;
}
auto source = cfg.sources.at(r.source);
// note: this is not supposed to happen
THROW(!source,
std::string("Unknown source ") + r.source,
r.ctx);
// build filter AST by parsing the condition, building exceptions,
// and resolving lists and macros
falco_rule rule;
condition = r.cond;
if (!r.exceptions.empty())
{
build_rule_exception_infos(
r.exceptions, rule.exception_fields, condition);
}
auto ast = parse_condition(condition, lists, r.cond_ctx);
resolve_macros(macros, ast, MAX_VISIBILITY, r.ctx);
// check for warnings in the filtering condition
warn_codes.clear();
if (warn_resolver.run(ast.get(), warn_codes))
{
for (auto &w : warn_codes)
{
cfg.res->add_warning(w, "", r.ctx);
}
}
// build rule output message
rule.output = r.output;
if (r.source == falco_common::syscall_source)
{
apply_output_substitutions(cfg, rule.output);
}
if(!is_format_valid(*cfg.sources.at(r.source), rule.output, err))
{
throw rule_load_exception(
load_result::LOAD_ERR_COMPILE_OUTPUT,
err,
r.output_ctx);
}
// construct rule definition and compile it to a filter
rule.name = r.name;
rule.source = r.source;
rule.description = r.desc;
rule.priority = r.priority;
rule.tags = r.tags;
auto rule_id = out.insert(rule, rule.name);
out.at(rule_id)->id = rule_id;
// This also compiles the filter, and might throw a
// falco_exception with details on the compilation
// failure.
try {
source->ruleset->add(*out.at(rule_id), ast);
}
catch (const falco_exception& e)
{
// Allow errors containing "nonexistent field" if
// skip_if_unknown_filter is true
std::string err = e.what();
if (err.find("nonexistent field") != string::npos &&
r.skip_if_unknown_filter)
{
cfg.res->add_warning(
load_result::LOAD_UNKNOWN_FIELD,
e.what(),
r.cond_ctx);
}
else
{
throw rule_loader::rule_load_exception(
load_result::LOAD_ERR_COMPILE_CONDITION,
e.what(),
r.cond_ctx);
}
}
// By default rules are enabled/disabled for the default ruleset
if(r.enabled)
{
source->ruleset->enable(rule.name, true, cfg.default_ruleset_id);
}
else
{
source->ruleset->disable(rule.name, true, cfg.default_ruleset_id);
}
// populate set of event types and emit an special warning
set<uint16_t> evttypes = { ppm_event_type::PPME_PLUGINEVENT_E };
if(rule.source == falco_common::syscall_source)
{
evttypes.clear();
filter_evttype_resolver().evttypes(ast, evttypes);
if ((evttypes.empty() || evttypes.size() > 100)
&& r.warn_evttypes)
{
cfg.res->add_warning(
load_result::LOAD_NO_EVTTYPE,
"Rule matches too many evt.type values. This has a significant performance penalty.",
r.ctx);
}
}
}
}
void rule_loader::compile(configuration& cfg, indexed_vector<falco_rule>& out) const
{
indexed_vector<list_info> lists;
indexed_vector<macro_info> macros;
// expand all lists, macros, and rules
try
{
compile_list_infos(cfg, lists);
compile_macros_infos(cfg, lists, macros);
compile_rule_infos(cfg, lists, macros, out);
}
catch(rule_load_exception &e)
{
cfg.res->add_error(e.ec, e.msg, e.ctx);
}
// print info on any dangling lists or macros that were not used anywhere
for (auto &m : macros)
{
if (!m.used)
{
cfg.res->add_warning(
load_result::LOAD_UNUSED_MACRO,
"Macro not referred to by any other rule/macro",
m.ctx);
}
}
for (auto &l : lists)
{
if (!l.used)
{
cfg.res->add_warning(
load_result::LOAD_UNUSED_LIST,
"List not referred to by any other rule/macro",
l.ctx);
}
}
}

View File

@@ -16,24 +16,16 @@ limitations under the License.
#pragma once
#include <map>
#include <string>
#include <vector>
#include <yaml-cpp/yaml.h>
#include <nlohmann/json.hpp>
#include "falco_rule.h"
#include "falco_source.h"
#include "falco_load_result.h"
#include "indexed_vector.h"
/*!
\brief Ruleset loader of the falco engine
*/
class rule_loader
namespace rule_loader
{
public:
class context
{
public:
@@ -74,6 +66,11 @@ public:
position() : pos(0), line(0), column(0) {};
position(const YAML::Mark& mark) : pos(mark.pos), line(mark.line), column(mark.column) {};
~position() = default;
position(position&&) = default;
position& operator = (position&&) = default;
position(const position&) = default;
position& operator = (const position&) = default;
int pos;
int line;
int column;
@@ -81,6 +78,18 @@ public:
struct location
{
location(): item_type(context::item_type::VALUE_FOR) {}
location(
const std::string n,
const position& p,
context::item_type i,
const std::string in):
name(n), pos(p), item_type(i), item_name(in) {}
location(location&&) = default;
location& operator = (location&&) = default;
location(const location&) = default;
location& operator = (const location&) = default;
// A name for the content this location refers
// to. Will generally be a filename, can also
// refer to a rule/macro condition when the
@@ -118,6 +127,11 @@ public:
virtual ~context() = default;
context(context&&) = default;
context& operator = (context&&) = default;
context(const context&) = default;
context& operator = (const context&) = default;
// Return the content name (generally filename) for
// this context
const std::string& name() const;
@@ -153,6 +167,16 @@ public:
struct warning
{
warning(): ctx("no-filename-given") {}
warning(
falco::load_result::warning_code w,
const std::string& m,
const context& c): wc(w), msg(m), ctx(c) {}
warning(warning&&) = default;
warning& operator = (warning&&) = default;
warning(const warning&) = default;
warning& operator = (const warning&) = default;
falco::load_result::warning_code wc;
std::string msg;
context ctx;
@@ -160,6 +184,16 @@ public:
struct error
{
error(): ctx("no-filename-given") {}
error(
falco::load_result::error_code e,
const std::string& m,
const context& c): ec(e), msg(m), ctx(c) {}
error(error&&) = default;
error& operator = (error&&) = default;
error(const error&) = default;
error& operator = (const error&) = default;
falco::load_result::error_code ec;
std::string msg;
context ctx;
@@ -170,6 +204,11 @@ public:
public:
rule_load_exception(falco::load_result::error_code ec, std::string msg, const context& ctx);
virtual ~rule_load_exception();
rule_load_exception(rule_load_exception&&) = default;
rule_load_exception& operator = (rule_load_exception&&) = default;
rule_load_exception(const rule_load_exception&) = default;
rule_load_exception& operator = (const rule_load_exception&) = default;
const char* what();
falco::load_result::error_code ec;
@@ -187,6 +226,10 @@ public:
public:
result(const std::string &name);
virtual ~result() = default;
result(result&&) = default;
result& operator = (result&&) = default;
result(const result&) = default;
result& operator = (const result&) = default;
virtual bool successful() override;
virtual bool has_warnings() override;
@@ -225,10 +268,16 @@ public:
const std::string& cont,
const indexed_vector<falco_source>& srcs,
std::string name)
: content(cont), sources(srcs), name(name)
: content(cont), sources(srcs), name(name),
default_ruleset_id(0), replace_output_container_info(false),
min_priority(falco_common::PRIORITY_DEBUG)
{
res.reset(new result(name));
}
configuration(configuration&&) = default;
configuration& operator = (configuration&&) = default;
configuration(const configuration&) = delete;
configuration& operator = (const configuration&) = delete;
const std::string& content;
const indexed_vector<falco_source>& sources;
@@ -247,6 +296,10 @@ public:
{
engine_version_info(context &ctx);
~engine_version_info() = default;
engine_version_info(engine_version_info&&) = default;
engine_version_info& operator = (engine_version_info&&) = default;
engine_version_info(const engine_version_info&) = default;
engine_version_info& operator = (const engine_version_info&) = default;
context ctx;
uint32_t version;
@@ -262,6 +315,10 @@ public:
requirement() = default;
requirement(const std::string n, const std::string v):
name(n), version(v) { }
requirement(requirement&&) = default;
requirement& operator = (requirement&&) = default;
requirement(const requirement&) = default;
requirement& operator = (const requirement&) = default;
std::string name;
std::string version;
@@ -275,6 +332,10 @@ public:
plugin_version_info();
plugin_version_info(context &ctx);
~plugin_version_info() = default;
plugin_version_info(plugin_version_info&&) = default;
plugin_version_info& operator = (plugin_version_info&&) = default;
plugin_version_info(const plugin_version_info&) = default;
plugin_version_info& operator = (const plugin_version_info&) = default;
context ctx;
requirement_alternatives alternatives;
@@ -287,6 +348,10 @@ public:
{
list_info(context &ctx);
~list_info() = default;
list_info(list_info&&) = default;
list_info& operator = (list_info&&) = default;
list_info(const list_info&) = default;
list_info& operator = (const list_info&) = default;
context ctx;
bool used;
@@ -303,6 +368,10 @@ public:
{
macro_info(context &ctx);
~macro_info() = default;
macro_info(macro_info&&) = default;
macro_info& operator = (macro_info&&) = default;
macro_info(const macro_info&) = default;
macro_info& operator = (const macro_info&) = default;
context ctx;
context cond_ctx;
@@ -321,6 +390,10 @@ public:
{
rule_exception_info(context &ctx);
~rule_exception_info() = default;
rule_exception_info(rule_exception_info&&) = default;
rule_exception_info& operator = (rule_exception_info&&) = default;
rule_exception_info(const rule_exception_info&) = default;
rule_exception_info& operator = (const rule_exception_info&) = default;
/*!
\brief This is necessary due to the dynamic-typed nature of
@@ -329,6 +402,14 @@ public:
this easier to implement in C++, that is not non-dynamic-typed.
*/
struct entry {
entry(): is_list(false) {}
explicit entry(const std::string& i): is_list(false), item(i) {}
explicit entry(const std::vector<entry>& v): is_list(true), items(v) {}
entry(entry&&) = default;
entry& operator = (entry&&) = default;
entry(const entry&) = default;
entry& operator = (const entry&) = default;
bool is_list;
std::string item;
std::vector<entry> items;
@@ -354,6 +435,10 @@ public:
{
rule_info(context &ctx);
~rule_info() = default;
rule_info(rule_info&&) = default;
rule_info& operator = (rule_info&&) = default;
rule_info(const rule_info&) = default;
rule_info& operator = (const rule_info&) = default;
context ctx;
context cond_ctx;
@@ -372,67 +457,4 @@ public:
bool warn_evttypes;
bool skip_if_unknown_filter;
};
virtual ~rule_loader() = default;
/*!
\brief Erases all the internal state and definitions
*/
virtual void clear();
/*!
\brief Uses the internal state to compile a list of falco_rules
*/
virtual void compile(configuration& cfg, indexed_vector<falco_rule>& out) const;
/*!
\brief Returns the set of all required versions for each plugin according
to the internal definitions.
*/
virtual const std::vector<plugin_version_info::requirement_alternatives>& required_plugin_versions() const;
/*!
\brief Defines an info block. If a similar info block is found
in the internal state (e.g. another rule with same name), then
the previous definition gets overwritten
*/
virtual void define(configuration& cfg, engine_version_info& info);
virtual void define(configuration& cfg, plugin_version_info& info);
virtual void define(configuration& cfg, list_info& info);
virtual void define(configuration& cfg, macro_info& info);
virtual void define(configuration& cfg, rule_info& info);
/*!
\brief Appends an info block to an existing one. An exception
is thrown if no existing definition can be matched with the appended
one
*/
virtual void append(configuration& cfg, list_info& info);
virtual void append(configuration& cfg, macro_info& info);
virtual void append(configuration& cfg, rule_info& info);
/*!
\brief Updates the 'enabled' flag of an existing definition
*/
virtual void enable(configuration& cfg, rule_info& info);
private:
void compile_list_infos(
configuration& cfg,
indexed_vector<list_info>& out) const;
void compile_macros_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& out) const;
void compile_rule_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out) const;
uint32_t m_cur_index;
indexed_vector<rule_info> m_rule_infos;
indexed_vector<macro_info> m_macro_infos;
indexed_vector<list_info> m_list_infos;
std::vector<plugin_version_info::requirement_alternatives> m_required_plugin_versions;
};

View File

@@ -0,0 +1,280 @@
/*
Copyright (C) 2022 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 <string>
#include <version.h>
#include "falco_engine.h"
#include "rule_loader_collector.h"
#define THROW(cond, err, ctx) { if ((cond)) { throw rule_loader::rule_load_exception(falco::load_result::LOAD_ERR_VALIDATE, (err), (ctx)); } }
static inline bool is_operator_defined(const std::string& op)
{
auto ops = libsinsp::filter::parser::supported_operators();
return find(ops.begin(), ops.end(), op) != ops.end();
}
template <typename T>
static inline void define_info(indexed_vector<T>& infos, T& info, uint32_t id)
{
auto prev = infos.at(info.name);
if (prev)
{
info.index = prev->index;
info.visibility = id;
*prev = info;
}
else
{
info.index = id;
info.visibility = id;
infos.insert(info, info.name);
}
}
template <typename T>
static inline void append_info(T* prev, T& info, uint32_t id)
{
prev->visibility = id;
}
static void validate_exception_info(
const falco_source& source,
rule_loader::rule_exception_info &ex)
{
if (ex.fields.is_list)
{
if (!ex.comps.is_valid())
{
ex.comps.is_list = true;
for (size_t i = 0; i < ex.fields.items.size(); i++)
{
ex.comps.items.push_back(rule_loader::rule_exception_info::entry("="));
}
}
THROW(ex.fields.items.size() != ex.comps.items.size(),
"Fields and comps lists must have equal length",
ex.ctx);
for (auto &v : ex.comps.items)
{
THROW(!is_operator_defined(v.item),
std::string("'") + v.item + "' is not a supported comparison operator",
ex.ctx);
}
for (auto &v : ex.fields.items)
{
THROW(!source.is_field_defined(v.item),
std::string("'") + v.item + "' is not a supported filter field",
ex.ctx);
}
}
else
{
if (!ex.comps.is_valid())
{
ex.comps.is_list = false;
ex.comps.item = "in";
}
THROW(ex.comps.is_list,
"Fields and comps must both be strings",
ex.ctx);
THROW((ex.comps.item != "in" && ex.comps.item != "pmatch" && ex.comps.item != "intersects"),
"When fields is a single value, comps must be one of (in, pmatch, intersects)",
ex.ctx);
THROW(!source.is_field_defined(ex.fields.item),
std::string("'") + ex.fields.item + "' is not a supported filter field",
ex.ctx);
}
}
void rule_loader::collector::clear()
{
m_cur_index = 0;
m_rule_infos.clear();
m_list_infos.clear();
m_macro_infos.clear();
m_required_plugin_versions.clear();
}
const std::vector<rule_loader::plugin_version_info::requirement_alternatives>& rule_loader::collector::required_plugin_versions() const
{
return m_required_plugin_versions;
}
const indexed_vector<rule_loader::list_info>& rule_loader::collector::lists() const
{
return m_list_infos;
}
const indexed_vector<rule_loader::macro_info>& rule_loader::collector::macros() const
{
return m_macro_infos;
}
const indexed_vector<rule_loader::rule_info>& rule_loader::collector::rules() const
{
return m_rule_infos;
}
void rule_loader::collector::define(configuration& cfg, engine_version_info& info)
{
auto v = falco_engine::engine_version();
THROW(v < info.version, "Rules require engine version "
+ std::to_string(info.version) + ", but engine version is " + std::to_string(v),
info.ctx);
}
void rule_loader::collector::define(configuration& cfg, plugin_version_info& info)
{
std::unordered_set<std::string> plugin_names;
for (const auto& req : info.alternatives)
{
sinsp_version plugin_version(req.version);
THROW(!plugin_version.m_valid,
"Invalid required version '" + req.version
+ "' for plugin '" + req.name + "'",
info.ctx);
THROW(plugin_names.find(req.name) != plugin_names.end(),
"Defined multiple alternative version requirements for plugin '"
+ req.name + "'",
info.ctx);
plugin_names.insert(req.name);
}
m_required_plugin_versions.push_back(info.alternatives);
}
void rule_loader::collector::define(configuration& cfg, list_info& info)
{
define_info(m_list_infos, info, m_cur_index++);
}
void rule_loader::collector::append(configuration& cfg, list_info& info)
{
auto prev = m_list_infos.at(info.name);
THROW(!prev,
"List has 'append' key but no list by that name already exists",
info.ctx);
prev->items.insert(prev->items.end(), info.items.begin(), info.items.end());
append_info(prev, info, m_cur_index++);
}
void rule_loader::collector::define(configuration& cfg, macro_info& info)
{
define_info(m_macro_infos, info, m_cur_index++);
}
void rule_loader::collector::append(configuration& cfg, macro_info& info)
{
auto prev = m_macro_infos.at(info.name);
THROW(!prev,
"Macro has 'append' key but no macro by that name already exists",
info.ctx);
prev->cond += " ";
prev->cond += info.cond;
append_info(prev, info, m_cur_index++);
}
void rule_loader::collector::define(configuration& cfg, rule_info& info)
{
auto source = cfg.sources.at(info.source);
if (!source)
{
cfg.res->add_warning(falco::load_result::LOAD_UNKNOWN_SOURCE,
"Unknown source " + info.source + ", skipping",
info.ctx);
return;
}
auto prev = m_rule_infos.at(info.name);
THROW(prev && prev->source != info.source,
"Rule has been re-defined with a different source",
info.ctx);
for (auto &ex : info.exceptions)
{
THROW(!ex.fields.is_valid(),
"Rule exception item must have fields property with a list of fields",
ex.ctx);
validate_exception_info(*source, ex);
}
define_info(m_rule_infos, info, m_cur_index++);
}
void rule_loader::collector::append(configuration& cfg, rule_info& info)
{
auto prev = m_rule_infos.at(info.name);
THROW(!prev,
"Rule has 'append' key but no rule by that name already exists",
info.ctx);
THROW(info.cond.empty() && info.exceptions.empty(),
"Appended rule must have exceptions or condition property",
info.ctx);
auto source = cfg.sources.at(prev->source);
// note: this is not supposed to happen
THROW(!source,
std::string("Unknown source ") + prev->source,
info.ctx);
if (!info.cond.empty())
{
prev->cond += " ";
prev->cond += info.cond;
}
for (auto &ex : info.exceptions)
{
auto prev_ex = find_if(prev->exceptions.begin(), prev->exceptions.end(),
[&ex](const rule_loader::rule_exception_info& i)
{ return i.name == ex.name; });
if (prev_ex == prev->exceptions.end())
{
THROW(!ex.fields.is_valid(),
"Rule exception must have fields property with a list of fields",
ex.ctx);
THROW(ex.values.empty(),
"Rule exception must have values property with a list of values",
ex.ctx);
validate_exception_info(*source, ex);
prev->exceptions.push_back(ex);
}
else
{
THROW(ex.fields.is_valid(),
"Can not append exception fields to existing exception, only values",
ex.ctx);
THROW(ex.comps.is_valid(),
"Can not append exception comps to existing exception, only values",
ex.ctx);
prev_ex->values.insert(
prev_ex->values.end(), ex.values.begin(), ex.values.end());
}
}
append_info(prev, info, m_cur_index++);
}
void rule_loader::collector::enable(configuration& cfg, rule_info& info)
{
auto prev = m_rule_infos.at(info.name);
THROW(!prev,
"Rule has 'enabled' key but no rule by that name already exists",
info.ctx);
prev->enabled = info.enabled;
}

View File

@@ -0,0 +1,97 @@
/*
Copyright (C) 2022 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.
*/
#pragma once
#include <vector>
#include "rule_loader.h"
#include "indexed_vector.h"
namespace rule_loader
{
/*!
\brief Collector for the ruleset loader of the falco engine
*/
class collector
{
public:
collector(): m_cur_index(0) { }
virtual ~collector() = default;
collector(collector&&) = default;
collector& operator = (collector&&) = default;
collector(const collector&) = delete;
collector& operator = (const collector&) = delete;
/*!
\brief Erases all the internal state and definitions
*/
virtual void clear();
/*!
\brief Returns the set of all defined required plugin versions
*/
virtual const std::vector<plugin_version_info::requirement_alternatives>& required_plugin_versions() const;
/*!
\brief Returns the list of defined lists
*/
virtual const indexed_vector<list_info>& lists() const;
/*!
\brief Returns the list of defined macros
*/
virtual const indexed_vector<macro_info>& macros() const;
/*!
\brief Returns the list of defined rules
*/
virtual const indexed_vector<rule_info>& rules() const;
/*!
\brief Defines an info block. If a similar info block is found
in the internal state (e.g. another rule with same name), then
the previous definition gets overwritten
*/
virtual void define(configuration& cfg, engine_version_info& info);
virtual void define(configuration& cfg, plugin_version_info& info);
virtual void define(configuration& cfg, list_info& info);
virtual void define(configuration& cfg, macro_info& info);
virtual void define(configuration& cfg, rule_info& info);
/*!
\brief Appends an info block to an existing one. An exception
is thrown if no existing definition can be matched with the appended
one
*/
virtual void append(configuration& cfg, list_info& info);
virtual void append(configuration& cfg, macro_info& info);
virtual void append(configuration& cfg, rule_info& info);
/*!
\brief Updates the 'enabled' flag of an existing definition
*/
virtual void enable(configuration& cfg, rule_info& info);
private:
uint32_t m_cur_index;
indexed_vector<rule_info> m_rule_infos;
indexed_vector<macro_info> m_macro_infos;
indexed_vector<list_info> m_list_infos;
std::vector<plugin_version_info::requirement_alternatives> m_required_plugin_versions;
};
}; // namespace rule_loader

View File

@@ -0,0 +1,542 @@
/*
Copyright (C) 2022 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 <string>
#include <memory>
#include <set>
#include <vector>
#include "rule_loader_compiler.h"
#include "filter_macro_resolver.h"
#include "filter_evttype_resolver.h"
#include "filter_warning_resolver.h"
#define MAX_VISIBILITY ((uint32_t) -1)
#define THROW(cond, err, ctx) { if ((cond)) { throw rule_loader::rule_load_exception(falco::load_result::LOAD_ERR_VALIDATE, (err), (ctx)); } }
static std::string s_container_info_fmt = "%container.info";
static std::string s_default_extra_fmt = "%container.name (id=%container.id)";
using namespace libsinsp::filter;
// todo(jasondellaluce): this breaks string escaping in lists and exceptions
static void quote_item(std::string& e)
{
if (e.find(" ") != std::string::npos && e[0] != '"' && e[0] != '\'')
{
e = '"' + e + '"';
}
}
static void paren_item(std::string& e)
{
if(e[0] != '(')
{
e = '(' + e + ')';
}
}
static inline bool is_operator_defined(const std::string& op)
{
auto ops = libsinsp::filter::parser::supported_operators();
return find(ops.begin(), ops.end(), op) != ops.end();
}
static inline bool is_operator_for_list(const std::string& op)
{
auto ops = libsinsp::filter::parser::supported_operators(true);
return find(ops.begin(), ops.end(), op) != ops.end();
}
static bool is_format_valid(const falco_source& source, std::string fmt, std::string& err)
{
try
{
std::shared_ptr<gen_event_formatter> formatter;
formatter = source.formatter_factory->create_formatter(fmt);
return true;
}
catch(exception &e)
{
err = e.what();
return false;
}
}
static void build_rule_exception_infos(
const std::vector<rule_loader::rule_exception_info>& exceptions,
std::set<std::string>& exception_fields,
std::string& condition)
{
std::string tmp;
for (auto &ex : exceptions)
{
std::string icond;
if(!ex.fields.is_list)
{
for (auto &val : ex.values)
{
THROW(val.is_list,
"Expected values array to contain a list of strings",
ex.ctx)
icond += icond.empty()
? ("(" + ex.fields.item + " "
+ ex.comps.item + " (")
: ", ";
exception_fields.insert(ex.fields.item);
tmp = val.item;
quote_item(tmp);
icond += tmp;
}
icond += icond.empty() ? "" : "))";
}
else
{
icond = "(";
for (auto &values : ex.values)
{
THROW(ex.fields.items.size() != values.items.size(),
"Fields and values lists must have equal length",
ex.ctx);
icond += icond == "(" ? "" : " or ";
icond += "(";
uint32_t k = 0;
std::string istr;
for (auto &field : ex.fields.items)
{
icond += k == 0 ? "" : " and ";
if (values.items[k].is_list)
{
istr = "(";
for (auto &v : values.items[k].items)
{
tmp = v.item;
quote_item(tmp);
istr += istr == "(" ? "" : ", ";
istr += tmp;
}
istr += ")";
}
else
{
istr = values.items[k].item;
if(is_operator_for_list(ex.comps.items[k].item))
{
paren_item(istr);
}
else
{
quote_item(istr);
}
}
icond += " " + field.item;
icond += " " + ex.comps.items[k].item + " " + istr;
exception_fields.insert(field.item);
k++;
}
icond += ")";
}
icond += ")";
if (icond == "()")
{
icond = "";
}
}
condition += icond.empty() ? "" : " and not " + icond;
}
}
// todo(jasondellaluce): this breaks string escaping in lists
static bool resolve_list(std::string& cnd, const rule_loader::list_info& list)
{
static std::string blanks = " \t\n\r";
static std::string delims = blanks + "(),=";
std::string new_cnd;
size_t start, end;
bool used = false;
start = cnd.find(list.name);
while (start != std::string::npos)
{
// the characters surrounding the name must
// be delims of beginning/end of string
end = start + list.name.length();
if ((start == 0 || delims.find(cnd[start - 1]) != std::string::npos)
&& (end >= cnd.length() || delims.find(cnd[end]) != std::string::npos))
{
// shift pointers to consume all whitespaces
while (start > 0
&& blanks.find(cnd[start - 1]) != std::string::npos)
{
start--;
}
while (end < cnd.length()
&& blanks.find(cnd[end]) != std::string::npos)
{
end++;
}
// create substitution string by concatenating all values
std::string sub = "";
for (auto &v : list.items)
{
if (!sub.empty())
{
sub += ", ";
}
sub += v;
}
// if substituted list is empty, we need to
// remove a comma from the left or the right
if (sub.empty())
{
if (start > 0 && cnd[start - 1] == ',')
{
start--;
}
else if (end < cnd.length() && cnd[end] == ',')
{
end++;
}
}
// compose new string with substitution
new_cnd = "";
if (start > 0)
{
new_cnd += cnd.substr(0, start) + " ";
}
new_cnd += sub + " ";
if (end <= cnd.length())
{
new_cnd += cnd.substr(end);
}
cnd = new_cnd;
start += sub.length() + 1;
used = true;
}
start = cnd.find(list.name, start + 1);
}
return used;
}
static void resolve_macros(
indexed_vector<rule_loader::macro_info>& macros,
std::shared_ptr<ast::expr>& ast,
uint32_t visibility,
const rule_loader::context &ctx)
{
filter_macro_resolver macro_resolver;
for (auto &m : macros)
{
if (m.index < visibility)
{
macro_resolver.set_macro(m.name, m.cond_ast);
}
}
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())
{
macros.at(m)->used = true;
}
}
// note: there is no visibility order between filter conditions and lists
static std::shared_ptr<ast::expr> parse_condition(
std::string condition,
indexed_vector<rule_loader::list_info>& lists,
const rule_loader::context &ctx)
{
for (auto &l : lists)
{
if (resolve_list(condition, l))
{
l.used = true;
}
}
libsinsp::filter::parser p(condition);
p.set_max_depth(1000);
try
{
std::shared_ptr<ast::expr> res_ptr(p.parse());
return res_ptr;
}
catch (const sinsp_exception& e)
{
rule_loader::context parsectx(p.get_pos(), condition, ctx);
throw rule_loader::rule_load_exception(
falco::load_result::LOAD_ERR_COMPILE_CONDITION,
e.what(),
parsectx);
}
}
static void apply_output_substitutions(
rule_loader::configuration& cfg,
std::string& out)
{
if (out.find(s_container_info_fmt) != std::string::npos)
{
if (cfg.replace_output_container_info)
{
out = replace(out, s_container_info_fmt, cfg.output_extra);
return;
}
out = replace(out, s_container_info_fmt, s_default_extra_fmt);
}
out += cfg.output_extra.empty() ? "" : " " + cfg.output_extra;
}
void rule_loader::compiler::compile_list_infos(
configuration& cfg,
const collector& col,
indexed_vector<list_info>& out) const
{
std::string tmp;
std::vector<std::string> used;
for (auto &list : col.lists())
{
list_info v = list;
v.items.clear();
for (auto &item : list.items)
{
const auto ref = col.lists().at(item);
if (ref && ref->index < list.visibility)
{
used.push_back(ref->name);
for (auto val : ref->items)
{
quote_item(val);
v.items.push_back(val);
}
}
else
{
tmp = item;
quote_item(tmp);
v.items.push_back(tmp);
}
}
v.used = false;
out.insert(v, v.name);
}
for (auto &v : used)
{
out.at(v)->used = true;
}
}
// note: there is a visibility ordering between macros
void rule_loader::compiler::compile_macros_infos(
configuration& cfg,
const collector& col,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& out) const
{
for (auto &m : col.macros())
{
macro_info entry = m;
entry.cond_ast = parse_condition(m.cond, lists, m.cond_ctx);
entry.used = false;
out.insert(entry, m.name);
}
for (auto &m : out)
{
resolve_macros(out, m.cond_ast, m.visibility, m.ctx);
}
}
void rule_loader::compiler::compile_rule_infos(
configuration& cfg,
const collector& col,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out) const
{
std::string err, condition;
std::set<falco::load_result::load_result::warning_code> warn_codes;
filter_warning_resolver warn_resolver;
for (auto &r : col.rules())
{
// skip the rule if below the minimum priority
if (r.priority > cfg.min_priority)
{
continue;
}
auto source = cfg.sources.at(r.source);
// note: this is not supposed to happen
THROW(!source,
std::string("Unknown source ") + r.source,
r.ctx);
// build filter AST by parsing the condition, building exceptions,
// and resolving lists and macros
falco_rule rule;
condition = r.cond;
if (!r.exceptions.empty())
{
build_rule_exception_infos(
r.exceptions, rule.exception_fields, condition);
}
auto ast = parse_condition(condition, lists, r.cond_ctx);
resolve_macros(macros, ast, MAX_VISIBILITY, r.ctx);
// check for warnings in the filtering condition
warn_codes.clear();
if (warn_resolver.run(ast.get(), warn_codes))
{
for (auto &w : warn_codes)
{
cfg.res->add_warning(w, "", r.ctx);
}
}
// build rule output message
rule.output = r.output;
if (r.source == falco_common::syscall_source)
{
apply_output_substitutions(cfg, rule.output);
}
if(!is_format_valid(*cfg.sources.at(r.source), rule.output, err))
{
throw rule_load_exception(
falco::load_result::load_result::LOAD_ERR_COMPILE_OUTPUT,
err,
r.output_ctx);
}
// construct rule definition and compile it to a filter
rule.name = r.name;
rule.source = r.source;
rule.description = r.desc;
rule.priority = r.priority;
rule.tags = r.tags;
auto rule_id = out.insert(rule, rule.name);
out.at(rule_id)->id = rule_id;
// This also compiles the filter, and might throw a
// falco_exception with details on the compilation
// failure.
try {
source->ruleset->add(*out.at(rule_id), ast);
}
catch (const falco_exception& e)
{
// Allow errors containing "nonexistent field" if
// skip_if_unknown_filter is true
std::string err = e.what();
if (err.find("nonexistent field") != std::string::npos &&
r.skip_if_unknown_filter)
{
cfg.res->add_warning(
falco::load_result::load_result::LOAD_UNKNOWN_FIELD,
e.what(),
r.cond_ctx);
}
else
{
throw rule_loader::rule_load_exception(
falco::load_result::load_result::LOAD_ERR_COMPILE_CONDITION,
e.what(),
r.cond_ctx);
}
}
// By default rules are enabled/disabled for the default ruleset
if(r.enabled)
{
source->ruleset->enable(rule.name, true, cfg.default_ruleset_id);
}
else
{
source->ruleset->disable(rule.name, true, cfg.default_ruleset_id);
}
// populate set of event types and emit an special warning
std::set<uint16_t> evttypes = { ppm_event_type::PPME_PLUGINEVENT_E };
if(rule.source == falco_common::syscall_source)
{
evttypes.clear();
filter_evttype_resolver().evttypes(ast, evttypes);
if ((evttypes.empty() || evttypes.size() > 100)
&& r.warn_evttypes)
{
cfg.res->add_warning(
falco::load_result::load_result::LOAD_NO_EVTTYPE,
"Rule matches too many evt.type values. This has a significant performance penalty.",
r.ctx);
}
}
}
}
void rule_loader::compiler::compile(
configuration& cfg,
const collector& col,
indexed_vector<falco_rule>& out) const
{
indexed_vector<list_info> lists;
indexed_vector<macro_info> macros;
// expand all lists, macros, and rules
try
{
compile_list_infos(cfg, col, lists);
compile_macros_infos(cfg, col, lists, macros);
compile_rule_infos(cfg, col, lists, macros, out);
}
catch(rule_load_exception &e)
{
cfg.res->add_error(e.ec, e.msg, e.ctx);
}
// print info on any dangling lists or macros that were not used anywhere
for (auto &m : macros)
{
if (!m.used)
{
cfg.res->add_warning(
falco::load_result::load_result::LOAD_UNUSED_MACRO,
"Macro not referred to by any other rule/macro",
m.ctx);
}
}
for (auto &l : lists)
{
if (!l.used)
{
cfg.res->add_warning(
falco::load_result::LOAD_UNUSED_LIST,
"List not referred to by any other rule/macro",
l.ctx);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
Copyright (C) 2022 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.
*/
#pragma once
#include "rule_loader.h"
#include "rule_loader_collector.h"
#include "indexed_vector.h"
#include "falco_rule.h"
namespace rule_loader
{
/*!
\brief Compiler for the ruleset loader of the falco engine
*/
class compiler
{
public:
compiler() = default;
virtual ~compiler() = default;
compiler(compiler&&) = default;
compiler& operator = (compiler&&) = default;
compiler(const compiler&) = default;
compiler& operator = (const compiler&) = default;
/*!
\brief Compiles a list of falco rules
*/
virtual void compile(
configuration& cfg,
const collector& col,
indexed_vector<falco_rule>& out) const;
private:
void compile_list_infos(
configuration& cfg,
const collector& col,
indexed_vector<list_info>& out) const;
void compile_macros_infos(
configuration& cfg,
const collector& col,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& out) const;
void compile_rule_infos(
configuration& cfg,
const collector& col,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out) const;
};
}; // namespace rule_loader

View File

@@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include "rule_reader.h"
#include <string>
#include <vector>
#define THROW(cond, err, ctx) { if ((cond)) { throw rule_loader::rule_load_exception(load_result::LOAD_ERR_YAML_VALIDATE, (err), (ctx)); } }
#include "rule_loader_reader.h"
#define THROW(cond, err, ctx) { if ((cond)) { throw rule_loader::rule_load_exception(falco::load_result::LOAD_ERR_YAML_VALIDATE, (err), (ctx)); } }
using namespace falco;
// Don't call this directly, call decode_val/decode_optional_val instead.
template <typename T>
@@ -241,7 +243,7 @@ static void read_rule_exceptions(
static void read_item(
rule_loader::configuration& cfg,
rule_loader& loader,
rule_loader::collector& collector,
const YAML::Node& item,
const rule_loader::context& parent)
{
@@ -255,7 +257,7 @@ static void read_item(
rule_loader::engine_version_info v(ctx);
decode_val(item, "required_engine_version", v.version, ctx);
loader.define(cfg, v);
collector.define(cfg, v);
}
else if(item["required_plugin_versions"].IsDefined())
{
@@ -296,7 +298,7 @@ static void read_item(
}
}
loader.define(cfg, v);
collector.define(cfg, v);
}
}
else if(item["list"].IsDefined())
@@ -317,11 +319,11 @@ static void read_item(
if(append)
{
loader.append(cfg, v);
collector.append(cfg, v);
}
else
{
loader.define(cfg, v);
collector.define(cfg, v);
}
}
else if(item["macro"].IsDefined())
@@ -345,11 +347,11 @@ static void read_item(
if(append)
{
loader.append(cfg, v);
collector.append(cfg, v);
}
else
{
loader.define(cfg, v);
collector.define(cfg, v);
}
}
else if(item["rule"].IsDefined())
@@ -379,7 +381,7 @@ static void read_item(
v.cond_ctx = rule_loader::context(item["condition"], rule_loader::context::RULE_CONDITION, "", ctx);
}
read_rule_exceptions(item, v, ctx, append);
loader.append(cfg, v);
collector.append(cfg, v);
}
else
{
@@ -394,7 +396,7 @@ static void read_item(
!item["priority"].IsDefined())
{
decode_val(item, "enabled", v.enabled, ctx);
loader.enable(cfg, v);
collector.enable(cfg, v);
}
else
{
@@ -421,18 +423,18 @@ static void read_item(
decode_optional_val(item, "skip-if-unknown-filter", v.skip_if_unknown_filter, ctx);
decode_tags(item, v.tags, ctx);
read_rule_exceptions(item, v, ctx, append);
loader.define(cfg, v);
collector.define(cfg, v);
}
}
}
else
{
rule_loader::context ctx(item, rule_loader::context::RULES_CONTENT_ITEM, "", parent);
cfg.res->add_warning(load_result::LOAD_UNKNOWN_ITEM, "Unknown top level item", ctx);
cfg.res->add_warning(falco::load_result::LOAD_UNKNOWN_ITEM, "Unknown top level item", ctx);
}
}
bool rule_reader::load(rule_loader::configuration& cfg, rule_loader& loader)
bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& collector)
{
std::vector<YAML::Node> docs;
try
@@ -442,7 +444,7 @@ bool rule_reader::load(rule_loader::configuration& cfg, rule_loader& loader)
catch(const exception& e)
{
rule_loader::context ctx(cfg.name);
cfg.res->add_error(load_result::LOAD_ERR_YAML_PARSE, e.what(), ctx);
cfg.res->add_error(falco::load_result::LOAD_ERR_YAML_PARSE, e.what(), ctx);
return false;
}
@@ -465,7 +467,7 @@ bool rule_reader::load(rule_loader::configuration& cfg, rule_loader& loader)
{
if (!it->IsNull())
{
read_item(cfg, loader, *it, ctx);
read_item(cfg, collector, *it, ctx);
}
}
}

View File

@@ -16,22 +16,30 @@ limitations under the License.
#pragma once
#include <map>
#include <string>
#include <vector>
#include "rule_loader.h"
#include "rule_loader_collector.h"
namespace rule_loader
{
/*!
\brief Reads the contents of a ruleset
*/
class rule_reader
class reader
{
public:
virtual ~rule_reader() = default;
reader() = default;
virtual ~reader() = default;
reader(reader&&) = default;
reader& operator = (reader&&) = default;
reader(const reader&) = default;
reader& operator = (const reader&) = default;
/*!
\brief Reads the contents of a ruleset and uses a loader to store
\brief Reads the contents of a ruleset and uses a collector to store
thew new definitions
*/
virtual bool load(rule_loader::configuration& cfg, rule_loader& loader);
virtual bool read(configuration& cfg, collector& loader);
};
}; // namespace rule_loader

View File

@@ -34,6 +34,10 @@ class stats_manager
public:
stats_manager();
virtual ~stats_manager();
stats_manager(stats_manager&&) = default;
stats_manager& operator = (stats_manager&&) = default;
stats_manager(const stats_manager&) = default;
stats_manager& operator = (const stats_manager&) = default;
/*!
\brief Erases the internal state and statistics data

View File

@@ -37,12 +37,15 @@ set(
app_actions/print_support.cpp
app_actions/print_syscall_events.cpp
app_actions/print_version.cpp
app_actions/print_page_size.cpp
app_actions/compute_syscall_buffer_size.cpp
app_actions/select_event_sources.cpp
app_actions/start_grpc_server.cpp
app_actions/start_webserver.cpp
app_actions/validate_rules_files.cpp
app_actions/create_requested_paths.cpp
app_actions/configure_interesting_sets.cpp
app_actions/gain_lock.cpp
configuration.cpp
logger.cpp
falco_outputs.cpp

View File

@@ -0,0 +1,71 @@
/*
Copyright (C) 2022 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 "application.h"
using namespace falco::app;
/* These indexes could change over the Falco releases. */
#define MIN_INDEX 1
#define MAX_INDEX 10
#define DEFAULT_BYTE_SIZE 1 << 23
application::run_result application::configure_syscall_buffer_size()
{
/* We don't need to compute the syscall buffer dimension if we are in capture mode or if the
* the syscall source is not enabled.
*/
if(is_capture_mode() || m_state->enabled_sources.find(falco_common::syscall_source) == m_state->enabled_sources.end())
{
return run_result::ok();
}
uint16_t index = m_state->config->m_syscall_buf_size_preset;
if(index < MIN_INDEX || index > MAX_INDEX)
{
return run_result::fatal("The 'syscall_buf_size_preset' value must be between '" + std::to_string(MIN_INDEX) + "' and '" + std::to_string(MAX_INDEX) + "'\n");
}
/* Sizes from `1 MB` to `512 MB`. The index `0` is reserved, users cannot use it! */
std::vector<uint32_t> vect{0, 1 << 20, 1 << 21, 1 << 22, DEFAULT_BYTE_SIZE, 1 << 24, 1 << 25, 1 << 26, 1 << 27, 1 << 28, 1 << 29};
uint64_t chosen_size = vect[index];
/* If the page size is not valid we return here. */
long page_size = getpagesize();
if(page_size <= 0)
{
m_state->syscall_buffer_bytes_size = DEFAULT_BYTE_SIZE;
falco_logger::log(LOG_WARNING, "Unable to get the system page size through 'getpagesize()'. Try to use the default syscall buffer dimension: " + std::to_string(DEFAULT_BYTE_SIZE) + " bytes\n");
return run_result::ok();
}
/* Check if the chosen size is a multiple of the page size. */
if(chosen_size % page_size != 0)
{
return run_result::fatal("The chosen syscall buffer size '" + std::to_string(chosen_size) + "' is not a multiple of your system page size '" + std::to_string(page_size) + "'. Please configure a greater 'syscall_buf_size_preset' value in the Falco configuration file\n");
}
/* Check if the chosen size is greater than `2 * page_size`. */
if((chosen_size / page_size) <= 2)
{
return run_result::fatal("The chosen syscall buffer size '" + std::to_string(chosen_size) + "' is not greater than '2 * " + std::to_string(page_size) + "' where '" + std::to_string(page_size) + "' is your system page size. Please configure a greater 'syscall_buf_size_preset' value in the Falco configuration file\n");
}
m_state->syscall_buffer_bytes_size = chosen_size;
falco_logger::log(LOG_INFO, "The chosen syscall buffer dimension is: " + std::to_string(chosen_size) + " bytes (" + std::to_string(chosen_size / (uint64_t)(1024 * 1024)) + " MBs)\n");
return run_result::ok();
}

View File

@@ -37,7 +37,7 @@ application::run_result application::create_requested_paths()
std::ifstream reader(m_options.gvisor_config);
if (reader.fail())
{
return run_result::fatal(m_options.gvisor_config + ": cannot open file.");
return run_result::fatal(m_options.gvisor_config + ": cannot open file");
}
nlohmann::json parsed_json;
@@ -67,7 +67,7 @@ application::run_result application::create_requested_paths()
}
}
if (!m_state->config->m_grpc_bind_address.empty())
if (m_state->config->m_grpc_enabled && !m_state->config->m_grpc_bind_address.empty())
{
if(falco::utils::network::is_unix_scheme(m_state->config->m_grpc_bind_address))
{
@@ -108,4 +108,4 @@ int application::create_dir(const std::string &path)
}
}
return 0;
}
}

View File

@@ -75,15 +75,14 @@ bool application::create_handler(int sig, void (*func)(int), run_result &ret)
application::run_result application::create_signal_handlers()
{
run_result ret;
s_app = *this;
if(! create_handler(SIGINT, ::signal_callback, ret) ||
! create_handler(SIGTERM, ::signal_callback, ret) ||
! create_handler(SIGUSR1, ::reopen_outputs, ret) ||
! create_handler(SIGHUP, ::restart_falco, ret))
{
return ret;
s_app = dummy;
}
s_app = *this;
return ret;
}
@@ -94,7 +93,7 @@ application::run_result application::attach_inotify_signals()
inot_fd = inotify_init();
if (inot_fd == -1)
{
return run_result::fatal("Could not create inotify handler.");
return run_result::fatal("Could not create inotify handler");
}
struct sigaction sa;
@@ -103,13 +102,13 @@ application::run_result application::attach_inotify_signals()
sa.sa_handler = restart_falco;
if (sigaction(SIGIO, &sa, NULL) == -1)
{
return run_result::fatal("Failed to link SIGIO to inotify handler.");
return run_result::fatal("Failed to link SIGIO to inotify handler");
}
/* Set owner process that is to receive "I/O possible" signal */
if (fcntl(inot_fd, F_SETOWN, getpid()) == -1)
{
return run_result::fatal("Failed to setting owner on inotify handler.");
return run_result::fatal("Failed to setting owner on inotify handler");
}
/*
@@ -119,14 +118,14 @@ application::run_result application::attach_inotify_signals()
int flags = fcntl(inot_fd, F_GETFL);
if (fcntl(inot_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)
{
return run_result::fatal("Failed to setting flags on inotify handler.");
return run_result::fatal("Failed to setting flags on inotify handler");
}
// Watch conf file
int wd = inotify_add_watch(inot_fd, m_options.conf_filename.c_str(), IN_CLOSE_WRITE);
if (wd == -1)
{
return run_result::fatal("Failed to watch conf file.");
return run_result::fatal("Failed to watch conf file");
}
falco_logger::log(LOG_DEBUG, "Watching " + m_options.conf_filename +"\n");
@@ -138,7 +137,7 @@ application::run_result application::attach_inotify_signals()
{
return run_result::fatal("Failed to watch rule file: " + rule);
}
falco_logger::log(LOG_DEBUG, "Watching " + rule +".\n");
falco_logger::log(LOG_DEBUG, "Watching " + rule +"\n");
}
// Watch specified rules folders, if any:
@@ -152,7 +151,7 @@ application::run_result application::attach_inotify_signals()
{
return run_result::fatal("Failed to watch rule folder: " + fld);
}
falco_logger::log(LOG_DEBUG, "Watching " + fld +" folder.\n");
falco_logger::log(LOG_DEBUG, "Watching " + fld +" folder\n");
}
}
return run_result::ok();

View File

@@ -34,7 +34,7 @@ application::run_result application::daemonize()
pid = fork();
if (pid < 0) {
// error
return run_result::fatal("Could not fork.");
return run_result::fatal("Could not fork");
} else if (pid > 0) {
// parent. Write child pid to pidfile and exit
std::ofstream pidfile;
@@ -54,7 +54,7 @@ application::run_result application::daemonize()
// Become own process group.
sid = setsid();
if (sid < 0) {
return run_result::fatal("Could not set session id.");
return run_result::fatal("Could not set session id");
}
// Set umask so no files are world anything or group writable.
@@ -62,7 +62,7 @@ application::run_result application::daemonize()
// Change working directory to '/'
if ((chdir("/")) < 0) {
return run_result::fatal("Could not change working directory to '/'.");
return run_result::fatal("Could not change working directory to '/'");
}
// Close stdin, stdout, stderr and reopen to /dev/null

View File

@@ -0,0 +1,45 @@
/*
Copyright (C) 2022 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 "application.h"
#include <sys/file.h>
using namespace falco::app;
#define FALCO_LOCK_FILE "falco.lock"
application::run_result application::gain_lock()
{
m_lock_fd = open(FALCO_LOCK_FILE, O_CREAT | O_WRONLY, 0644);
if (m_lock_fd < 0)
{
return run_result::fatal(std::string("Failed to open " FALCO_LOCK_FILE " lock file: ") + strerror(errno));
}
if (flock(m_lock_fd, LOCK_EX | LOCK_NB) == -1)
{
return run_result::fatal("A lock is present on " FALCO_LOCK_FILE " Another Falco instance running?");
}
return run_result::ok();
}
bool application::release_lock(std::string &errstr)
{
if (m_lock_fd > 0)
{
close(m_lock_fd);
}
return true;
}

View File

@@ -46,7 +46,7 @@ void application::configure_output_format()
}
else if(m_options.gvisor_config != "")
{
output_format = "container=%container.id pid=%proc.vpid tid=%thread.vtid";
output_format = "container=%container.name (id=%container.id) vpid=%proc.vpid vtid=%thread.vtid";
replace_container_info = true;
}

View File

@@ -26,9 +26,8 @@ application::run_result application::load_config()
falco_logger::set_time_format_iso_8601(m_state->config->m_time_format_iso_8601);
// log after config init because config determines where logs go
falco_logger::log(LOG_INFO, "Falco version: " + std::string(FALCO_VERSION) + "\n");
falco_logger::log(LOG_INFO, "Falco version: " + std::string(FALCO_VERSION) + " (" + std::string(FALCO_TARGET_ARCH) + ")\n");
falco_logger::log(LOG_INFO, "Falco initialized with configuration file: " + m_options.conf_filename + "\n");
falco_logger::log(LOG_INFO, "Falco compiled for: " + std::string(FALCO_TARGET_ARCH) + "\n");
}
else
{

View File

@@ -19,6 +19,21 @@ limitations under the License.
using namespace falco::app;
bool application::check_rules_plugin_requirements(std::string& err)
{
// Ensure that all plugins are compatible with the loaded set of rules
// note: offline inspector contains all the loaded plugins
std::vector<falco_engine::plugin_version_requirement> plugin_reqs;
for (const auto &plugin : m_state->offline_inspector->get_plugin_manager()->plugins())
{
falco_engine::plugin_version_requirement req;
req.name = plugin->name();
req.version = plugin->plugin_version().as_string();
plugin_reqs.push_back(req);
}
return m_state->engine->check_plugin_requirements(plugin_reqs, err);
}
void application::check_for_ignored_events()
{
/* Get the events from the rules. */
@@ -52,6 +67,14 @@ void application::check_for_ignored_events()
continue;
}
/* If the event is not generated by the running system we don't print
* any warning right now.
*/
if(!sinsp::is_generable_event(it))
{
continue;
}
/* If the event is not in this set it is not considered by Falco. */
if(interesting_events.find(it) == interesting_events.end())
{
@@ -134,20 +157,10 @@ application::run_result application::load_rules_files()
}
}
// Ensure that all plugins are compatible with the loaded set of rules
// note: offline inspector contains all the loaded plugins
std::string plugin_vers_err = "";
std::vector<falco_engine::plugin_version_requirement> plugin_reqs;
for (const auto &plugin : m_state->offline_inspector->get_plugin_manager()->plugins())
{
falco_engine::plugin_version_requirement req;
req.name = plugin->name();
req.version = plugin->plugin_version().as_string();
plugin_reqs.push_back(req);
}
if (!m_state->engine->check_plugin_requirements(plugin_reqs, plugin_vers_err))
std::string err = "";
if (!check_rules_plugin_requirements(err))
{
return run_result::fatal(plugin_vers_err);
return run_result::fatal(err);
}
for (const auto& substring : m_options.disabled_rule_substrings)

View File

@@ -54,7 +54,7 @@ application::run_result application::open_live_inspector(
if (p->caps() & CAP_SOURCING && p->event_source() == source)
{
auto cfg = m_state->plugin_configs.at(p->name());
falco_logger::log(LOG_INFO, "Falco uses the '" + cfg->m_name + "' plugin\n");
falco_logger::log(LOG_INFO, "Opening capture with plugin '" + cfg->m_name + "'\n");
inspector->open_plugin(cfg->m_name, cfg->m_open_params);
return run_result::ok();
}
@@ -67,14 +67,19 @@ application::run_result application::open_live_inspector(
//
// Falco uses a ptrace(2) based userspace implementation.
// Regardless of the implementation, the underlying method remains the same.
falco_logger::log(LOG_INFO, "Starting capture with udig\n");
falco_logger::log(LOG_INFO, "Opening capture with udig\n");
inspector->open_udig();
}
else if(!m_options.gvisor_config.empty()) /* gvisor engine. */
{
falco_logger::log(LOG_INFO, "Enabled event collection from gVisor. Configuration path: " + m_options.gvisor_config);
falco_logger::log(LOG_INFO, "Opening capture with gVisor. Configuration path: " + m_options.gvisor_config);
inspector->open_gvisor(m_options.gvisor_config, m_options.gvisor_root);
}
else if(m_options.modern_bpf) /* modern BPF engine. */
{
falco_logger::log(LOG_INFO, "Opening capture with modern BPF probe");
inspector->open_modern_bpf(m_state->syscall_buffer_bytes_size, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
}
else if(getenv(FALCO_BPF_ENV_VARIABLE) != NULL) /* BPF engine. */
{
const char *bpf_probe_path = std::getenv(FALCO_BPF_ENV_VARIABLE);
@@ -90,25 +95,25 @@ application::run_result application::open_live_inspector(
snprintf(full_path, PATH_MAX, "%s/%s", home, FALCO_PROBE_BPF_FILEPATH);
bpf_probe_path = full_path;
}
falco_logger::log(LOG_INFO, "Starting capture with BPF probe. BPF probe path: " + std::string(bpf_probe_path));
inspector->open_bpf(bpf_probe_path, DEFAULT_DRIVER_BUFFER_BYTES_DIM, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
falco_logger::log(LOG_INFO, "Opening capture with BPF probe. BPF probe path: " + std::string(bpf_probe_path));
inspector->open_bpf(bpf_probe_path, m_state->syscall_buffer_bytes_size, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
}
else /* Kernel module (default). */
{
try
{
falco_logger::log(LOG_INFO, "Starting capture with Kernel module.");
inspector->open_kmod(DEFAULT_DRIVER_BUFFER_BYTES_DIM, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
falco_logger::log(LOG_INFO, "Opening capture with Kernel module");
inspector->open_kmod(m_state->syscall_buffer_bytes_size, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
}
catch(sinsp_exception &e)
{
// Try to insert the Falco kernel module
falco_logger::log(LOG_INFO, "Trying to inject the Kernel module and starting the capture again...");
falco_logger::log(LOG_INFO, "Trying to inject the Kernel module and opening the capture again...");
if(system("modprobe " DRIVER_NAME " > /dev/null 2> /dev/null"))
{
falco_logger::log(LOG_ERR, "Unable to load the driver.\n");
falco_logger::log(LOG_ERR, "Unable to load the driver\n");
}
inspector->open_kmod(DEFAULT_DRIVER_BUFFER_BYTES_DIM, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
inspector->open_kmod(m_state->syscall_buffer_bytes_size, m_state->ppm_sc_of_interest, m_state->tp_of_interest);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
Copyright (C) 2022 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 "application.h"
using namespace falco::app;
application::run_result application::print_page_size()
{
if(m_options.print_page_size)
{
long page_size = getpagesize();
if(page_size <= 0)
{
return run_result::fatal("\nUnable to get the system page size through 'getpagesize()'\n");
}
else
{
falco_logger::log(LOG_INFO, "Your system page size is: " + std::to_string(page_size) + " bytes\n");
}
return run_result::exit();
}
return run_result::ok();
}

View File

@@ -21,7 +21,7 @@ limitations under the License.
using namespace falco::app;
static std::string read_file(std::string &filename)
static std::string read_file(const std::string &filename)
{
std::ifstream t(filename);
std::string str((std::istreambuf_iterator<char>(t)),

View File

@@ -284,6 +284,7 @@ static std::shared_ptr<stats_writer> init_stats_writer(const cmdline_options& op
application::run_result application::process_events()
{
application::run_result res = run_result::ok();
bool termination_forced = false;
// Notify engine that we finished loading and enabling all rules
m_state->engine->complete_rule_loading();
@@ -313,49 +314,64 @@ application::run_result application::process_events()
}
else
{
typedef struct
struct live_context
{
live_context() = default;
live_context(live_context&&) = default;
live_context& operator = (live_context&&) = default;
live_context(const live_context&) = default;
live_context& operator = (const live_context&) = default;
// the name of the source of which events are processed
std::string source;
// the result of the event processing loop
application::run_result res;
// if non-null, the thread on which events are processed
std::unique_ptr<std::thread> thread;
} live_context;
};
print_enabled_event_sources();
// start event processing for all enabled sources
std::vector<live_context> ctxs;
ctxs.reserve(m_state->enabled_sources.size());
for (auto source : m_state->enabled_sources)
for (const auto& source : m_state->enabled_sources)
{
auto src_info = m_state->source_infos.at(source);
auto ctx_idx = ctxs.size();
ctxs.emplace_back();
ctxs[ctx_idx].source = source;
auto& ctx = ctxs[ctxs.size() - 1];
ctx.source = source;
auto src_info = m_state->source_infos.at(source);
try
{
falco_logger::log(LOG_DEBUG, "Opening event source '" + source + "'\n");
res = open_live_inspector(src_info->inspector, source);
if (!res.success)
{
return res;
// note: we don't return here because we need to reach
// the thread termination loop below to make sure all
// already-spawned threads get terminated gracefully
break;
}
if (m_state->enabled_sources.size() == 1)
{
// optimization: with only one source we don't spawn additional threads
process_inspector_events(src_info->inspector, statsw, source, &ctxs[ctx_idx].res);
process_inspector_events(src_info->inspector, statsw, source, &ctx.res);
}
else
{
ctxs[ctx_idx].thread.reset(new std::thread(
ctx.thread.reset(new std::thread(
&application::process_inspector_events,
this, src_info->inspector, statsw, source, &ctxs[ctx_idx].res));
this, src_info->inspector, statsw, source, &ctx.res));
}
}
catch (std::exception &e)
{
ctxs[ctx_idx].res = run_result::fatal(e.what());
// note: we don't return here because we need to reach
// the thread termination loop below to make sure all
// already-spawned threads get terminated gracefully
ctx.res = run_result::fatal(e.what());
break;
}
}
@@ -365,13 +381,12 @@ application::run_result application::process_events()
// to force all other event streams to termiante too.
// We accomulate the errors in a single run_result.
size_t closed_count = 0;
bool forced_termination = false;
while (closed_count < ctxs.size())
{
if (!res.success && !forced_termination)
if (!res.success && !termination_forced)
{
terminate();
forced_termination = true;
termination_forced = true;
}
for (auto &ctx : ctxs)
{

View File

@@ -15,6 +15,18 @@ limitations under the License.
using namespace falco::app;
void application::print_enabled_event_sources()
{
/* Print all enabled sources. */
std::string str;
for (const auto &s : m_state->enabled_sources)
{
str += str.empty() ? "" : ", ";
str += s;
}
falco_logger::log(LOG_INFO, "Enabled event sources: " + str + "\n");
}
application::run_result application::select_event_sources()
{
m_state->enabled_sources = m_state->loaded_sources;
@@ -59,12 +71,5 @@ application::run_result application::select_event_sources()
return run_result::fatal("Must enable at least one event source");
}
/* Print all enabled sources. */
std::ostringstream os;
std::copy(m_state->enabled_sources.begin(), m_state->enabled_sources.end(), std::ostream_iterator<std::string>(os, ", "));
std::string result = os.str();
result.pop_back();
falco_logger::log(LOG_INFO, "Enabled event sources: " + result + "\n");
return run_result::ok();
}

View File

@@ -70,6 +70,7 @@ application::run_result application::validate_rules_files()
// The json output encompasses all files so the
// validation result is a single json object.
std::string err = "";
nlohmann::json results = nlohmann::json::array();
for(auto &filename : m_options.validate_rules_filenames)
@@ -77,6 +78,10 @@ application::run_result application::validate_rules_files()
std::unique_ptr<falco::load_result> res;
res = m_state->engine->load_rules(rc.at(filename), filename);
if (!check_rules_plugin_requirements(err))
{
return run_result::fatal(err);
}
successful &= res->successful();

View File

@@ -31,6 +31,7 @@ cmdline_options::cmdline_options()
: event_buffer_format(sinsp_evt::PF_NORMAL),
gvisor_config(""),
list_plugins(false),
modern_bpf(false),
m_cmdline_opts("falco", "Falco - Cloud Native Runtime Security")
{
define();
@@ -168,6 +169,9 @@ void cmdline_options::define()
("g,gvisor-config", "Parse events from gVisor using the specified configuration file. A falco-compatible configuration file can be generated with --gvisor-generate-config and can be used for both runsc and Falco.", cxxopts::value(gvisor_config), "<gvisor_config>")
("gvisor-generate-config", "Generate a configuration file that can be used for gVisor.", cxxopts::value<std::string>(gvisor_generate_config_with_socket)->implicit_value("/run/falco/gvisor.sock"), "<socket_path>")
("gvisor-root", "gVisor root directory for storage of container state. Equivalent to runsc --root flag.", cxxopts::value(gvisor_root), "<gvisor_root>")
#endif
#ifdef HAS_MODERN_BPF
("modern-bpf", "[EXPERIMENTAL] Use BPF modern probe to capture system events.", cxxopts::value(modern_bpf)->default_value("false"))
#endif
("i", "Print all events that are ignored by default (i.e. without the -A flag) and exit.", cxxopts::value(print_ignored_events)->default_value("false"))
#ifndef MINIMAL_BUILD
@@ -203,7 +207,9 @@ void cmdline_options::define()
("u,userspace", "Parse events from userspace. To be used in conjunction with the ptrace(2) based driver (pdig)", cxxopts::value(userspace)->default_value("false"))
("V,validate", "Read the contents of the specified rules(s) file and exit. Can be specified multiple times to validate multiple files.", cxxopts::value(validate_rules_filenames), "<rules_file>")
("v", "Verbose output.", cxxopts::value(verbose)->default_value("false"))
("version", "Print version number.", cxxopts::value(print_version_info)->default_value("false"));
("version", "Print version number.", cxxopts::value(print_version_info)->default_value("false"))
("page-size", "Print the system page size (may help you to choose the right syscall buffer size).", cxxopts::value(print_page_size)->default_value("false"));
m_cmdline_opts.set_width(140);
}

View File

@@ -78,6 +78,8 @@ public:
std::vector<std::string> validate_rules_filenames;
bool verbose;
bool print_version_info;
bool print_page_size;
bool modern_bpf;
bool parse(int argc, char **argv, std::string &errstr);

View File

@@ -46,7 +46,8 @@ application::state::state()
source_infos(),
plugin_configs(),
ppm_sc_of_interest(),
tp_of_interest()
tp_of_interest(),
syscall_buffer_bytes_size(DEFAULT_DRIVER_BUFFER_BYTES_DIM)
{
config = std::make_shared<falco_configuration>();
engine = std::make_shared<falco_engine>();
@@ -129,13 +130,14 @@ bool application::run(std::string &errstr, bool &restart)
// dependencies are honored (e.g. don't process events before
// loading plugins, opening inspector, etc.).
std::list<std::function<run_result()>> run_steps = {
std::bind(&application::gain_lock, this),
std::bind(&application::print_help, this),
std::bind(&application::print_version, this),
std::bind(&application::print_page_size, this),
std::bind(&application::print_generated_gvisor_config, this),
std::bind(&application::print_ignored_events, this),
std::bind(&application::print_syscall_events, this),
std::bind(&application::load_config, this),
std::bind(&application::create_signal_handlers, this),
std::bind(&application::print_plugin_info, this),
std::bind(&application::list_plugins, this),
std::bind(&application::load_plugins, this),
@@ -146,11 +148,13 @@ bool application::run(std::string &errstr, bool &restart)
std::bind(&application::validate_rules_files, this),
std::bind(&application::load_rules_files, this),
std::bind(&application::print_support, this),
std::bind(&application::create_signal_handlers, this),
std::bind(&application::attach_inotify_signals, this),
std::bind(&application::create_requested_paths, this),
std::bind(&application::daemonize, this),
std::bind(&application::init_outputs, this),
std::bind(&application::init_clients, this),
std::bind(&application::configure_syscall_buffer_size, this),
#ifndef MINIMAL_BUILD
std::bind(&application::start_grpc_server, this),
std::bind(&application::start_webserver, this),
@@ -162,8 +166,9 @@ bool application::run(std::string &errstr, bool &restart)
std::bind(&application::unregister_signal_handlers, this, _1),
#ifndef MINIMAL_BUILD
std::bind(&application::stop_grpc_server, this, _1),
std::bind(&application::stop_webserver, this, _1)
std::bind(&application::stop_webserver, this, _1),
#endif
std::bind(&application::release_lock, this, _1)
};
for (auto &func : run_steps)

View File

@@ -28,6 +28,7 @@ limitations under the License.
#include <string>
#include <atomic>
#include <unordered_set>
namespace falco {
namespace app {
@@ -36,6 +37,10 @@ class application {
public:
application();
virtual ~application();
application(application&&) = default;
application& operator = (application&&) = default;
application(const application&) = delete;
application& operator = (const application&) = delete;
// These are only used in signal handlers. Other than there,
// the control flow of the application should not be changed
@@ -90,11 +95,11 @@ private:
// The set of loaded event sources (by default, the syscall event
// source plus all event sources coming from the loaded plugins)
std::set<std::string> loaded_sources;
std::unordered_set<std::string> loaded_sources;
// The set of enabled event sources (can be altered by using
// the --enable-source and --disable-source options)
std::set<std::string> enabled_sources;
std::unordered_set<std::string> enabled_sources;
// Used to load all plugins to get their info. In capture mode,
// this is also used to open the capture file and read its events
@@ -116,6 +121,9 @@ private:
// Set of tracepoints we want the driver to capture
std::unordered_set<uint32_t> tp_of_interest;
// Dimension of the syscall buffer in bytes.
uint64_t syscall_buffer_bytes_size;
#ifndef MINIMAL_BUILD
falco::grpc::server grpc_server;
std::thread grpc_server_thread;
@@ -145,7 +153,7 @@ private:
}
// Failure result that causes the program to stop with an error
inline static run_result fatal(std::string err)
inline static run_result fatal(const std::string& err)
{
run_result r;
r.success = false;
@@ -171,6 +179,11 @@ private:
run_result();
virtual ~run_result();
run_result(run_result&&) = default;
run_result& operator = (run_result&&) = default;
run_result(const run_result&) = default;
run_result& operator = (const run_result&) = default;
// If true, the method completed successfully.
bool success;
@@ -227,6 +240,7 @@ private:
// These methods comprise the code the application "runs". The
// order in which the methods run is in application.cpp.
run_result gain_lock();
run_result create_signal_handlers();
run_result attach_inotify_signals();
run_result daemonize();
@@ -247,9 +261,11 @@ private:
run_result print_support();
run_result print_syscall_events();
run_result print_version();
run_result print_page_size();
run_result process_events();
run_result select_event_sources();
void configure_interesting_sets();
application::run_result configure_syscall_buffer_size();
#ifndef MINIMAL_BUILD
run_result start_grpc_server();
run_result start_webserver();
@@ -264,16 +280,19 @@ private:
bool stop_grpc_server(std::string &errstr);
bool stop_webserver(std::string &errstr);
#endif
bool release_lock(std::string &errstr);
// Methods called by the above methods
int create_dir(const std::string &path);
bool create_handler(int sig, void (*func)(int), run_result &ret);
void configure_output_format();
void check_for_ignored_events();
bool check_rules_plugin_requirements(std::string& err);
void format_plugin_info(std::shared_ptr<sinsp_plugin> p, std::ostream& os) const;
run_result open_offline_inspector();
run_result open_live_inspector(std::shared_ptr<sinsp> inspector, const std::string& source);
void add_source_to_engine(const std::string& src);
void print_enabled_event_sources();
void init_syscall_inspector(std::shared_ptr<sinsp> inspector, const falco::app::cmdline_options& opts);
run_result do_inspect(
std::shared_ptr<sinsp> inspector,
@@ -289,6 +308,7 @@ private:
std::string source, // an empty source represents capture mode
run_result* res) noexcept;
/* Returns true if we are in capture mode. */
inline bool is_capture_mode() const
{
return !m_options.trace_filename.empty();
@@ -302,6 +322,7 @@ private:
std::unique_ptr<state> m_state;
cmdline_options m_options;
bool m_initialized;
int m_lock_fd;
};
}; // namespace app

View File

@@ -285,6 +285,11 @@ void falco_configuration::init(string conf_filename, const vector<string> &cmdli
throw logic_error("Error reading config file(" + m_config_file + "): metadata download watch frequency seconds must be an unsigned integer > 0");
}
/* We put this value in the configuration file because in this way we can change the dimension at every reload.
* The default value is `4` -> 8 MB.
*/
m_syscall_buf_size_preset = m_config->get_scalar<uint16_t>("syscall_buf_size_preset", 4);
std::set<std::string> load_plugins;
bool load_plugins_node_defined = m_config->is_defined("load_plugins");

View File

@@ -269,6 +269,9 @@ public:
uint32_t m_metadata_download_chunk_wait_us;
uint32_t m_metadata_download_watch_freq_sec;
// Index corresponding to the syscall buffer dimension.
uint16_t m_syscall_buf_size_preset;
std::vector<plugin_config> m_plugins;
private:

View File

@@ -16,7 +16,7 @@ limitations under the License.
#pragma once
#include <memory>
#include <set>
#include <unordered_set>
#include <sinsp.h>
#include <token_bucket.h>
@@ -34,7 +34,7 @@ enum class syscall_evt_drop_action : uint8_t
EXIT
};
using syscall_evt_drop_actions = std::set<syscall_evt_drop_action>;
using syscall_evt_drop_actions = std::unordered_set<syscall_evt_drop_action>;
class syscall_evt_drop_mgr
{

View File

@@ -67,11 +67,11 @@ public:
stats_writer(const stats_writer&) = delete;
stats_writer(stats_writer&&) = delete;
stats_writer(stats_writer&&) = default;
stats_writer& operator=(const stats_writer&) = delete;
stats_writer& operator=(stats_writer&&) = delete;
stats_writer& operator=(stats_writer&&) = default;
~stats_writer();
@@ -109,6 +109,12 @@ public:
private:
struct msg
{
msg(): stop(false) {}
msg(msg&&) = default;
msg& operator = (msg&&) = default;
msg(const msg&) = default;
msg& operator = (const msg&) = default;
bool stop;
scap_stats delta;
scap_stats stats;