Featured Image for Falco Rules Now Support Exceptions
Mark Stemm

Falco Rules Now Support Exceptions

One of the upcoming features in Falco 0.28.0 is support for exceptions in rules. Exceptions are a concise way to represent conditions under which a rule should not generate an alert. Here's a quick example:

- rule: Write below binary dir
  ...
  exceptions:
    - name: known_bin_writers
      fields: [proc.name, fd.name]
      comps: [=, contains]
	  values:
	   - [nginx, /usr/bin/nginx]
	   - [apache, /bin/apache]
  ...

This rule defines an exception known_bin_writers, using fields proc.name and fd.name, and lists combinations of processes and file paths that are allowed to write to binary directories.

Why Are Exceptions Useful?

Currently, most rules have additional condition snippets of the form and not ... that define exceptions e.g. conditions user which the rule should not generate an alert.

The rule with the most exceptions is Write below etc via the macro write_etc_common. Prior to these changes, it had ~90 exceptions to track various programs and paths below /etc! Each exception was expressed as its own top-level macro:

- macro: write_etc_common
  condition: >
        etc_dir and evt.dir = < and open_write
	...
    and not sed_temporary_file
    and not exe_running_docker_save
    and not ansible_running_python
    and not python_running_denyhosts
    and not fluentd_writing_conf_files
    and not user_known_write_etc_conditions
    and not run_by_centrify
    and not run_by_adclient
    and not qualys_writing_conf_files
    and not git_writing_nssdb
    and not plesk_writing_keys
    and not plesk_install_writing_apache_conf
    and not plesk_running_mktemp
    and not networkmanager_writing_resolv_conf
    and not run_by_chef
    and not add_shell_writing_shells_tmp
    and not duply_writing_exclude_files
    and not xmlcatalog_writing_files
    and not parent_supervise_running_multilog
    and not supervise_writing_status
    and not pki_realm_writing_realms
    and not htpasswd_writing_passwd
    and not lvprogs_writing_conf
    and not ovsdb_writing_openvswitch
    and not datadog_writing_conf
    and not curl_writing_pki_db	...

This polluted the top-level set of objects with a bunch of one-off macros only used by a single rule.

Problems with Appends/Overrides to Define Exceptions

Although the concepts of macros and lists in condition fields, combined with appending to lists/conditions in macros/rules, is very general purpose, it can be unwieldy:

  • Appending to conditions can result in incorrect behavior, unless the original condition has its logical operators set up properly with parentheses. For example:
rule: my_rule
condition: (evt.type=open and (fd.name=/tmp/foo or fd.name=/tmp/bar))

rule: my_rule
condition: or fd.name=/tmp/baz
append: true

Results in unintended behavior. It will match any fd related event where the name is /tmp/baz, when the intent was probably to add /tmp/baz as an additional opened file.

  • A good convention many rules use is to have a clause "and not user_known_xxxx" built into the condition field. However, it's not in all rules and its use is a bit haphazard.

  • Appends and overrides can get confusing if you try to apply them multiple times. For example:

macro: allowed_files
condition: fd.name=/tmp/foo

...

macro: allowed_files
condition: and fd.name=/tmp/bar
append: true

If someone wanted to override the original behavior of allowed_files, they would have to use append: false in a third definition of allowed_files, but this would result in losing the append: true override.

Exceptions Syntax

Starting in 0.28.0, falco supports an optional exceptions property to rules. The exceptions property value is a list of identifier, list of tuples of filtercheck fields, and optional comparison operators and field values. Here's an example:

- rule: Write below binary dir
  desc: an attempt to write to any file below a set of binary directories
  condition: >
    bin_dir and evt.dir = < and open_write
    and not package_mgmt_procs
    and not exe_running_docker_save
    and not python_running_get_pip
    and not python_running_ms_oms
    and not user_known_write_below_binary_dir_activities    
  exceptions:
   - name: proc_writer
     fields: [proc.name, fd.directory]
   - name: container_writer
     fields: [container.image.repository, fd.directory]
     comps: [=, startswith]
   - name: proc_filenames
     fields: [proc.name, fd.name]
     comps: [=, in]
   - name: filenames
     fields: fd.filename
     comps: in

This rule defines four kinds of exceptions:

  • proc_writer: uses a combination of proc.name and fd.directory
  • container_writer: uses a combination of container.image.repository and fd.directory
  • proc_filenames: uses a combination of process and list of filenames.
  • filenames: uses a list of filenames

The specific strings "proc_writer"/"container_writer"/"proc_filenames"/"filenames" are arbitrary strings and don't have a special meaning to the rules file parser. They're only used to link together the list of field names with the list of field values that exist in the exception object.

proc_writer does not have any comps property, so the fields are directly compared to values using the = operator. container_writer does have a comps property, so each field will be compared to the corresponding exception items using the corresponding comparison operator.

proc_filenames uses the in comparison operator, so the corresponding values entry should be a list of filenames.

filenames differs from the others in that it names a single field and single comp operator. In this case, all values are combined into a single list.

Notice that exception fields and comparison operators are defined as a part of the rule. This is important because the author of the rule defines what construes a valid exception to the rule. In this case, an exception can consist of a process and file directory (actor and target), but not a process name only (too broad).

Exception values may also be defined as a part of a rule, but in many cases values will be defined in rules with append: true. Here's an example:

- list: apt_files
  items: [/bin/ls, /bin/rm]

- rule: Write below binary dir
  exceptions:
  - name: proc_writer
    values:
    - [apk, /usr/lib/alpine]
    - [npm, /usr/node/bin]
  - name: container_writer
    values:
    - [docker.io/alpine, /usr/libexec/alpine]
  - name: proc_filenames
    values:
    - [apt, apt_files]
    - [rpm, [/bin/cp, /bin/pwd]]
  - name: filenames
    values: [python, go]
  append: true

This appended version of Write below binary dir defines tuples of field values, with the exception name used to link the filtercheck fields and values. A rule exception applies if for a given event, the fields in a rule.exception match all of the values in some exception.item. For example, if a program apk writes to a file below /usr/lib/alpine, the rule will not trigger, even if the condition is met.

Notice that an item in a values list can be a list. This allows building exceptions with operators like "in", "pmatch", etc. that work on a list of items. The item can also be a name of an existing list. If not present surrounding parantheses will be added.

Finally, note that the structure of the values property differs between the items where fields is a list of fields (proc_writer/container_writer/proc_filenames) and when it is a single field (procs_only). This changes how the exceptions are folded into the rule's condition.

Rules Updated To Use Exceptions

Along with this code change, we've also revamped the rules to migrate as many rules as possible to use exceptions instead of one-off macros. We've kept the original ad-hoc "customization' macros e.g. user_known_update_package_registry, user_known_write_below_binary_dir_activities, etc. so if you had already added exceptions in the form of appends/overrides of these macros, those exceptions will continue to work. Please consider using exceptions whenever possible to customize the behavior of existing rules, and please define exceptions fields when creating your own rules.

Learn More

We have a more complete description of how exceptions work in the documentation along with best practices for adding exceptions to rules. You can also read the original proposal describes the benefits of exceptions in more detail.

In case you prefer to just try them out please notice you need Falco 0.27.0-15+8c4040b (deb, rpm, tarball).