eBPF for Cybersecurity - Part 4

Introducing ebpfguard : Rust library for writing Linux security policies using eBPF

eBPF for Cybersecurity - Part 4

What is LSM?

The Linux Security Module is a framework that allows the kernel to support multiple security models simultaneously. It provides a way for security policies, such as access control or integrity checking, to be added to the kernel without requiring modifications to the kernel itself.

LSM enables the kernel to enforce security policies based on the security attributes of various objects, such as files, processes, or network connections. Some examples of security modules that use the LSM framework include SELinux (Security-Enhanced Linux), AppArmor, and Smack.

The use of LSM makes it easier to develop and integrate security policies in the Linux kernel and provides a more flexible and extensible approach to enforcing security.

LSM for eBPF

The eBPF LSM provides a security model that is based on the eBPF bytecode, which is a flexible and extensible mechanism for processing and filtering network packets and system calls. The LSM for eBPF allows users to attach eBPF programs to specific security hooks in the kernel, enabling fine-grained control over the behaviour of the system.

The eBPF LSM can be used to enforce security policies related to networking, system calls, file access, and other system resources. For example, it can be used to filter network traffic based on specific criteria or to monitor and restrict the behaviour of user processes.

The LSM for eBPF is a powerful tool for enhancing the security of Linux systems, and it is gaining popularity among developers and security experts due to its flexibility and versatility.

LSM Hooks and Documentation

all lsm hooks are documented here by categories

https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hooks.h

Contain full parameter passes to hook

https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h

hooks named different from caller

https://elixir.bootlin.com/linux/latest/source/include/linux/security.h

LSM + BPF

LSM hooks

Linux Security Modules (LSM) are a set of kernel features that allow for the customization of the security policy of the Linux kernel. LSM hooks are points in the kernel where LSM modules can be inserted to intercept and modify system calls. EBPF is a technology that allows programs to be written in the form of bytecode that can be executed directly in the Linux kernel.

Monitoring and enforcement via LSM hooks EBPF is a technique that uses LSM hooks and EBPF to monitor and enforce security policies on a Linux system. This technique can be used to monitor and enforce a wide variety of security policies, including:

  • File access

  • Network access

  • Process execution

  • System calls

Monitoring and enforcement via LSM hooks eBPF is a powerful technique that can be used to improve the security of a Linux system. This technique is relatively new, but it is gaining popularity due to its flexibility and performance.

Here are some of the benefits of using monitoring and enforcement via LSM hooks EBPF:

  • Flexibility: LSM hooks eBPF can be used to monitor and enforce a wide variety of security policies.

  • Performance: LSM hooks eBPF is a very efficient technique, and it does not have a significant impact on the performance of the system.

  • Scalability: LSM hooks eBPF can be used to monitor and enforce security policies on large systems.

Here are some of the challenges of using monitoring and enforcement via LSM hooks EBPF:

  • Complexity: LSM hooks eBPF is a complex technique, and it requires some knowledge of the Linux kernel.

  • Tooling: There are a limited number of tools available for developing and debugging LSM hooks EBPF programs.

  • Support: LSM hooks eBPF is a relatively new technique, and it is not yet widely supported by operating systems and security products.

Overall, monitoring and enforcement via LSM hooks EBPF is a powerful technique that can be used to improve the security of a Linux system. This technique is relatively new, but it is gaining popularity due to its flexibility and performance.

What can an LSM program do?

An LSM program can do a lot of things, but some of the most common things include:

  • Auditing system calls: An LSM program can be used to audit system calls, which means that it can track what system calls are being made and by whom. This can be useful for troubleshooting security problems or for gathering information about how a system is being used.

  • Controlling access to files and directories: An LSM program can be used to control access to files and directories. This can be useful for preventing unauthorized users from accessing sensitive data.

  • Monitoring system activity: An LSM program can be used to monitor system activity, such as which processes are running, which files are being accessed, and which network connections are being made. This can be useful for detecting security problems or for gathering information about how a system is being used.

  • Enforcing security policies: An LSM program can be used to enforce security policies. This can be useful for preventing unauthorized users from accessing sensitive data or for preventing malicious software from running on a system.

LSM programs are a powerful tool that can be used to improve the security of a Linux system. They are flexible and can be used to implement a wide variety of security policies.

Introducing eBPFGuard

Clone the ebpfguard repository

ubuntu@ip-172-31-56-217:~$ git clone https://github.com/deepfence/ebpfguard
Cloning into 'ebpfguard'...
remote: Enumerating objects: 820, done.
remote: Counting objects: 100% (147/147), done.
remote: Compressing objects: 100% (100/100), done.
remote: Total 820 (delta 61), reused 56 (delta 46), pack-reused 673
Receiving objects: 100% (820/820), 4.52 MiB | 25.14 MiB/s, done.
Resolving deltas: 100% (398/398), done.

update LLVM

sudo apt-get install libllvm-15-ocaml-dev libllvm15 llvm-15 llvm-15-dev llvm-15-doc llvm-15-examples llvm-15-runtime

check if your kernel has BPF LSM support:

$ cat /sys/kernel/security/lsm
lockdown,capability,landlock,yama,apparmor

in my case, it's not installed follow prerequisites

https://github.com/deepfence/ebpfguard/blob/main/docs/gh/prerequisites.md

updated bpf lsm

Install required packages and tools

$ sudo apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    clang \
    libclang-dev \
    linux-tools-$(uname -r)

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ rustup target add x86_64-unknown-linux-musl
$ cargo install bindgen-cli
$ cargo install bpf-linker --git https://github.com/noboruma/bpf-linker

mount

The mount example shows how to define a policy for sb_mount, sb_remount and sb_umount LSM hooks as Rust code. It denies the mount operations for all processes except for the optionally given one.

in following folder examples/mount/examples/mount.rs

use std::{
    fs::{create_dir_all, remove_dir_all},
    path::PathBuf,
};

use clap::Parser;
use ebpfguard::{
    policy::{PolicySubject, SbMount, SbRemount, SbUmount},
    PolicyManager,
};
use log::info;

#[derive(Debug, Parser)]
struct Opt {
    #[clap(long, default_value = "/sys/fs/bpf")]
    bpffs_path: PathBuf,
    #[clap(long, default_value = "example_sb_mount")]
    bpffs_dir: PathBuf,
    /// Binary which should be allowed to mount filesystems.
    #[clap(long)]
    allow: Option<PathBuf>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let opt = Opt::parse();

    env_logger::init();

    // Create a directory where ebpfguard policy manager can store its BPF
    // objects (maps).
    let bpf_path = opt.bpffs_path.join(opt.bpffs_dir);
    create_dir_all(&bpf_path)?;

    // Create a policy manager.
    let mut policy_manager = PolicyManager::new(&bpf_path)?;

    // Attach the policy manager to the mount LSM hooks.
    let mut sb_mount = policy_manager.attach_sb_mount()?;
    let mut sb_remount = policy_manager.attach_sb_remount()?;
    let mut sb_umount = policy_manager.attach_sb_umount()?;

    // Get the receiver end of the alerts channel (for the `file_open` LSM
    // hook).
    let mut sb_mount_rx = sb_mount.alerts().await?;
    let mut sb_remount_rx = sb_remount.alerts().await?;
    let mut sb_umount_rx = sb_umount.alerts().await?;

    // Define policies which deny mount operations for all processes (except
    // for the specified subject, if defined).
    sb_mount
        .add_policy(SbMount {
            subject: PolicySubject::All,
            allow: false,
        })
        .await?;
    sb_remount
        .add_policy(SbRemount {
            subject: PolicySubject::All,
            allow: false,
        })
        .await?;
    sb_umount
        .add_policy(SbUmount {
            subject: PolicySubject::All,
            allow: false,
        })
        .await?;
    if let Some(subject) = opt.allow {
        sb_mount
            .add_policy(SbMount {
                subject: PolicySubject::Binary(subject.clone()),
                allow: true,
            })
            .await?;
        sb_remount
            .add_policy(SbRemount {
                subject: PolicySubject::Binary(subject.clone()),
                allow: true,
            })
            .await?;
        sb_umount
            .add_policy(SbUmount {
                subject: PolicySubject::Binary(subject),
                allow: true,
            })
            .await?;
    }

    info!("Waiting for Ctrl-C...");

    // Wait for policy violation alerts (or for CTRL+C).
    loop {
        tokio::select! {
            Some(alert) = sb_mount_rx.recv() => {
                info!(
                    "sb_mount: pid={} subject={}",
                    alert.pid,
                    alert.subject,
                );
            }
            Some(alert) = sb_remount_rx.recv() => {
                info!(
                    "sb_remount: pid={} subject={}",
                    alert.pid,
                    alert.subject,
                );
            }
            Some(alert) = sb_umount_rx.recv() => {
                info!(
                    "sb_umount: pid={} subject={}",
                    alert.pid,
                    alert.subject,
                );
            }
            _ = tokio::signal::ctrl_c() => {
                break;
            }
        }
    }

    info!("Exiting...");
    remove_dir_all(&bpf_path)?;

    Ok(())
}

create two directory inside ebpfguard repo

$ mkdir /tmp/test1
$ mkdir /tmp/test2

run policy program without binary

~/ebpfguard$ RUST_LOG=info cargo xtask run --example mount
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/x86_64-unknown-linux-musl/debug/xtask run --example mount`
       Fresh unicode-ident v1.0.8
       Fresh proc-macro2 v1.0.58
       Fresh libc v0.2.144
       Fresh quote v1.0.27
       Fresh syn v2.0.16
       Fresh bitflags v1.3.2
       Fresh io-lifetimes v1.0.10
       Fresh linux-raw-sys v0.3.7
       Fresh cfg-if v1.0.0
       Fresh rustix v0.37.19
       Fresh glob v0.3.1
       Fresh utf8parse v0.2.1
       Fresh anstyle-parse v0.2.0
       Fresh memchr v2.5.0
       Fresh is-terminal v0.4.7
       Fresh colorchoice v1.0.0
       Fresh anstyle v1.0.0
       Fresh minimal-lexical v0.2.1
       Fresh core v0.0.0 (/home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core)
       Fresh anstyle-query v1.0.0
       Fresh rustc-std-workspace-core v1.99.0 (/home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/rustc-std-workspace-core)
       Fresh anstream v0.3.2
       Fresh nom v7.1.3
       Fresh libloading v0.7.4
       Fresh either v1.8.1
       Fresh regex-syntax v0.7.1
       Fresh clap_lex v0.4.1
       Fresh heck v0.4.1
       Fresh strsim v0.10.0
       Fresh clap_derive v4.2.0
       Fresh clap_builder v4.2.7
       Fresh regex v1.8.1
       Fresh which v4.4.0
       Fresh clang-sys v1.6.1
       Fresh cexpr v0.6.0
       Fresh log v0.4.17
       Fresh compiler_builtins v0.1.91
       Fresh prettyplease v0.2.5
       Fresh thiserror-impl v1.0.40
       Fresh shlex v1.1.0
       Fresh once_cell v1.17.1
       Fresh peeking_take_while v0.1.2
       Fresh lazy_static v1.4.0
       Fresh lazycell v1.3.0
       Fresh fastrand v1.9.0
       Fresh rustc-hash v1.1.0
       Fresh tempfile v3.5.0
       Fresh bindgen v0.65.1
       Fresh clap v4.2.7
       Fresh thiserror v1.0.40
       Fresh anyhow v1.0.71
       Fresh rustversion v1.0.12
       Fresh aya-tool v0.1.0 (https://github.com/deepfence/aya-rs?branch=btf-fixes#611950e7)
       Fresh aya-bpf-cty v0.2.1 (https://github.com/deepfence/aya-rs?branch=btf-fixes#611950e7)
       Fresh aya-bpf-bindings v0.1.0 (https://github.com/deepfence/aya-rs?branch=btf-fixes#611950e7)
       Fresh aya-bpf-macros v0.1.0 (https://github.com/deepfence/aya-rs?branch=btf-fixes#611950e7)
       Fresh aya-bpf v0.1.0 (https://github.com/deepfence/aya-rs?branch=btf-fixes#611950e7)
       Fresh ebpfguard-common v0.1.0 (/home/ubuntu/ebpfguard/ebpfguard-common)
       Fresh ebpfguard-ebpf v0.1.0 (/home/ubuntu/ebpfguard/ebpfguard-ebpf)
    Finished dev [optimized + debuginfo] target(s) in 0.16s
   Compiling ebpfguard v0.1.0 (/home/ubuntu/ebpfguard/ebpfguard)
   Compiling mount v0.1.0 (/home/ubuntu/ebpfguard/examples/mount)
    Finished dev [unoptimized + debuginfo] target(s) in 6.68s
[2023-05-19T21:35:30Z INFO  mount] Waiting for Ctrl-C...

open new terminal bind mount the directory it will fail

$ sudo mount --bind /tmp/test1 /tmp/test2
mount: /tmp/test2: permission denied.

check it out more example https://github.com/deepfence/ebpfguard/tree/main/examples

Reach out to me on Twitter @sangamtwts

Thanks for reading! give a star to https://github.com/deepfence/ebpfguard project !

Did you find this article valuable?

Support CloudNativeFolks Community by becoming a sponsor. Any amount is appreciated!