2 个版本

0.1.1 2024 年 4 月 9 日
0.1.0 2024 年 3 月 13 日

#498解析器实现


用于 falco_plugin

Apache-2.0

515KB
10K SLoC

Rust 9K SLoC // 0.0% comments C 1K SLoC // 0.1% comments Shell 35 SLoC // 0.1% comments

Falco 事件

此软件包提供与 Falco 事件一起工作的支持。

事件可以以多种形式出现

  • 原始字节数据缓冲区,来自插件 API 或外部源,使用与 Falco 库 ringbuffer 方案兼容的数据格式
  • 原始事件,包含有关事件的某些元数据,但所有参数都仅作为一系列字节数据缓冲区提供
  • 解析事件,将原始字段反序列化为 Rust 数据类型(可以是特定事件的类型,或包含所有已知事件类型的通用枚举)

自动生成的事件类型

字段类型

由于解析的事件具有强类型,我们需要为事件模式中存在的每个字段定义类型。这些类型在 fields::types 下以 C API 使用的名称提供。

有关可用特定类型的详细信息,请参阅 fields::types

自动生成的枚举和位标志

事件类型中的一些字段定义为 PT_FLAGSPT_ENUMFLAGS。这些在 Rust SDK 中作为枚举(PT_ENUMFLAGS)或在 bitflags 软件包生成的结构中(PT_FLAGS)提供。

所有这些类型都位于 fields::event_flags 模块中。

自动生成的动态值类型

一些事件字段根据例如系统调用参数采取不同的类型。这些在 Falco 事件表中编码为 PT_DYN 类型,并在 fields::dynamic_params 中作为 Rust 枚举提供。

在抽象层次结构中上下移动

字节数据切片到原始事件

要从 &[u8] 读取一个事件到 events::RawEvent,使用 events::RawEvent::from。它对切片进行了一些基本的一致性检查,但 不会 验证例如所有事件参数是否存在以及事件是否被截断。

还存在 events::RawEvent::from_ptr,如果所有你有的只是一个原始指针,但有两个不安全的原因

  • 它解引用了一个原始指针,这已经足够不安全了
  • 它根据事件头部确定要访问的内存长度

此方法从指针创建一个切片(基于发现的长度)并将其传递给 events::RawEvent::from

原始事件到类型化事件

根据您的用例,您可以使用两种方法进一步细化事件类型。

如果您期望一个特定类型(或少数几个类型)的事件,您可以匹配 events::RawEvent::event_type 并使用适当的泛型类型调用 events::RawEvent::load,例如

# use falco_event::events::RawEvent;
# let event = RawEvent {
#    metadata: Default::default(),
#    len: 0,
#    event_type: 0,
#    nparams: 0,
#    payload: &[],
# };
use falco_event::events::types::EventType;
use falco_event::events::types;
use falco_event::num_traits::FromPrimitive;

match EventType::from_u16(event.event_type) {
    Some(EventType::SYSCALL_OPENAT2_E) => {
        let openat2_e_event = event.load::<types::PPME_SYSCALL_OPENAT2_E>()?;
        // openat2_e_event is Event<types::PPME_SYSCALL_OPENAT2_E>
        // ...
    }
    Some(EventType::SYSCALL_OPENAT2_X) => {
        let openat2_x_event = event.load::<types::PPME_SYSCALL_OPENAT2_X>()?;
        // openat2_x_event is Event<types::PPME_SYSCALL_OPENAT2_X>
        // ...
    }
    _ => (),
}

# Result::<(), anyhow::Error>::Ok(())

注意events::RawEvent::load 也对事件类型进行内部验证,因此您也可以使用 if-let 链

# use falco_event::events::RawEvent;
# let event = RawEvent {
#    metadata: Default::default(),
#    len: 0,
#    event_type: 0,
#    nparams: 0,
#    payload: &[],
# };
use falco_event::events::types::EventType;
use falco_event::events::types;

if let Ok(openat2_e_event) = event.load::<types::PPME_SYSCALL_OPENAT2_E>() {
    // openat2_e_event is Event<types::PPME_SYSCALL_OPENAT2_E>
    // ...
} else if let Ok(openat2_x_event) = event.load::<types::PPME_SYSCALL_OPENAT2_X>() {
    // openat2_x_event is Event<types::PPME_SYSCALL_OPENAT2_X>
    // ...
}

另一方面,如果您不期望任何特定的事件类型,但仍希望将其作为强类型化结构体,您可以使用 events::RawEvent::load_any,它返回一个 Event<AnyEvent>,其中 events::types::AnyEvent 是一个大型枚举,包含所有已知的事件类型。

请注意,在这种情况下,可用的方法非常有限。从现实的角度来看,您只能期望一个 std::fmt::Debug 实现,尽管这可能会随时间而改变。您仍然可以匹配每个单独的变体并访问其字段,但请注意,显式匹配可能更受欢迎:您不必为构建不感兴趣的事件的类型安全表示支付成本。

事件(原始或类型化)到字节数组

有一个特质(events::EventToBytes),它将事件的序列化形式写入到写入器(即实现了 std::io::Write 的类型,例如 Vec<u8>)。

依赖项

~0.7–1.5MB
~30K SLoC