initial commit
This commit is contained in:
commit
72ead3f149
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# 练手用的 LSM-BPF 模块
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 环境
|
||||||
|
|
||||||
|
- clang
|
||||||
|
- 我的在 clang15,但是理论上略微旧一点没关系。如果提示头文件缺失或者是不支持 bpf target,请升级
|
||||||
|
- kernel with BTF
|
||||||
|
- 参考:https://github.com/aquasecurity/btfhub/blob/main/docs/supported-distros.md
|
||||||
|
- golang 1.17+
|
||||||
|
- bpftool
|
||||||
|
- `bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h`
|
||||||
|
- bpf2go
|
||||||
|
- cilium 的工具
|
||||||
|
|
||||||
|
### 启动
|
||||||
|
|
||||||
|
- 一个终端
|
||||||
|
```bash
|
||||||
|
go build -o bin/demo
|
||||||
|
sudo ./bin/demo $PWD
|
||||||
|
```
|
||||||
|
- 另一个终端
|
||||||
|
```bash
|
||||||
|
watch ls
|
||||||
|
```
|
||||||
|
- 如果要看日志(不要tail)
|
||||||
|
```bash
|
||||||
|
sudo cat /sys/kernel/debug/tracing/trace_pipe
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码解释
|
||||||
|
|
||||||
|
### 内核态的 eBPF 程序: [./bpf/lsm.c](./bpf/lsm.c)
|
||||||
|
|
||||||
|
内核态程序主要是三部分:
|
||||||
|
- 生成的 BTF 定义,这个理论上可以在支持 BTF 的发行版上直接用,不需要改
|
||||||
|
- map: 内核和用户态通信的数据结构
|
||||||
|
- hook: 入口点
|
||||||
|
|
||||||
|
和普通的 C 非常像,可以当 C 来写。但是栈空间只有 512 B。我没打错,就 512 Byte。
|
||||||
|
|
||||||
|
#### BTF 定义生成
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
|
||||||
|
```
|
||||||
|
|
||||||
|
#### map
|
||||||
|
|
||||||
|
map 不在栈空间中,一般用来让
|
||||||
|
- 用户态配置 eBPF 程序
|
||||||
|
- eBPF 程序向用户态传递结果或者性能数据
|
||||||
|
|
||||||
|
参考
|
||||||
|
- https://docs.kernel.org/bpf/maps.html#map-types
|
||||||
|
|
||||||
|
#### hook
|
||||||
|
|
||||||
|
hook 就是一个函数,可以配置为在特定时机被 hook 点调起。
|
||||||
|
|
||||||
|
我们这个 demo 主要是希望尝试写 lsm 模块,比如说和 file_open 这个 hook 有关的信息在
|
||||||
|
- https://github.com/torvalds/linux/blob/master/security/security.c#L2793
|
||||||
|
|
||||||
|
主要是需要关注 hook 需要接受的参数。
|
||||||
|
|
||||||
|
也有很多其他的 hook 点,比如说 kprobe 和 xdp 有关的。我们这个仓库暂时不关心。
|
||||||
|
|
||||||
|
### 用户态
|
||||||
|
|
||||||
|
用户态我选择了用 cilium 的 wrapper,理论上可以用 libbpf 啊或者类似的其他工具来实现。
|
||||||
|
|
||||||
|
### 构建
|
||||||
|
|
||||||
|
整体来说项目是
|
||||||
|
- (一次性)生成 vmlinux.h
|
||||||
|
- bpf2go 工具编译 ebpf 字节码,生成 stub
|
||||||
|
- 编译 go 的二进制
|
2
bin/.gitignore
vendored
Normal file
2
bin/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
130
bpf/lsm.c
Normal file
130
bpf/lsm.c
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// generated by command: bpftool btf dump file /sys/kernel/btf/vmlinux format c
|
||||||
|
// > vmlinux.h
|
||||||
|
#include "vmlinux.h"
|
||||||
|
|
||||||
|
#include <bpf/bpf_core_read.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_tracing.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
|
||||||
|
char __license[] SEC("license") = "Dual MIT/GPL";
|
||||||
|
|
||||||
|
char log_fmt_timeout[] = "timeout: %lld %lld";
|
||||||
|
|
||||||
|
#define SECOND (1000 * 1000 * 1000)
|
||||||
|
|
||||||
|
typedef enum status {
|
||||||
|
FILE_PROTECT_ENABLED,
|
||||||
|
FILE_PROTECT_TICK,
|
||||||
|
FILE_PROTECT_MAX,
|
||||||
|
} file_protect_state;
|
||||||
|
|
||||||
|
typedef struct check_ctx {
|
||||||
|
struct dentry *dentry;
|
||||||
|
__u64 need_to_be_checked;
|
||||||
|
__u64 return_value;
|
||||||
|
} check_ctx;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_HASH);
|
||||||
|
__uint(max_entries, FILE_PROTECT_MAX);
|
||||||
|
__type(key, file_protect_state);
|
||||||
|
__type(value, __u64);
|
||||||
|
} states SEC(".maps");
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_HASH);
|
||||||
|
__uint(max_entries, 256);
|
||||||
|
__type(key, unsigned long);
|
||||||
|
__type(value, __u8);
|
||||||
|
} roots SEC(".maps");
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_HASH);
|
||||||
|
__uint(max_entries, 1024 * 1024);
|
||||||
|
__type(key, unsigned long);
|
||||||
|
__type(value, __u64);
|
||||||
|
} banned_access SEC(".maps");
|
||||||
|
|
||||||
|
#define MAX_PATH_FRAGEMENTS 256
|
||||||
|
|
||||||
|
static __u64 check_file_need_protection(struct bpf_map *map,
|
||||||
|
unsigned long *inode, __u8 *enabled,
|
||||||
|
check_ctx *ctx) {
|
||||||
|
if (!*enabled) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dentry *dentry = ctx->dentry;
|
||||||
|
int count_down = MAX_PATH_FRAGEMENTS;
|
||||||
|
|
||||||
|
// enumerate from the leaf to root
|
||||||
|
while (count_down-- > 0 && dentry != NULL) {
|
||||||
|
if (dentry->d_inode->i_ino == *inode) {
|
||||||
|
ctx->need_to_be_checked = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __u64 check_service_status(struct bpf_map *map, file_protect_state *kind,
|
||||||
|
__u64 *state, check_ctx *data) {
|
||||||
|
__u64 now;
|
||||||
|
switch (*kind) {
|
||||||
|
case FILE_PROTECT_ENABLED:
|
||||||
|
if (!*state) {
|
||||||
|
data->need_to_be_checked = 0;
|
||||||
|
data->return_value = 0;
|
||||||
|
return 1; // early return to improve performance. return 1 means to stop
|
||||||
|
// iteration.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FILE_PROTECT_TICK:
|
||||||
|
now = bpf_ktime_get_ns();
|
||||||
|
// now - last > 3 seconds
|
||||||
|
// but 3 * SECOND will overflow
|
||||||
|
if ((now - *state) / 3 > SECOND) {
|
||||||
|
data->return_value = EPERM;
|
||||||
|
bpf_trace_printk(log_fmt_timeout, sizeof(log_fmt_timeout), now, *state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FILE_PROTECT_MAX: // this branch just tell clang to not complaint about
|
||||||
|
// FILE_PROTECT_MAX
|
||||||
|
// noop
|
||||||
|
break;
|
||||||
|
// default: // TAKE CARE!!!! default branch disable enum branch checking.
|
||||||
|
// // noop
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("lsm/file_open")
|
||||||
|
int BPF_PROG(check_file_open, struct file *file, int ret) {
|
||||||
|
if (ret != 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
check_ctx data = {
|
||||||
|
.dentry = file->f_path.dentry,
|
||||||
|
.need_to_be_checked = 0,
|
||||||
|
.return_value = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
bpf_for_each_map_elem(&roots, check_file_need_protection, &data, 0);
|
||||||
|
|
||||||
|
if (!data.need_to_be_checked) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.need_to_be_checked = 1;
|
||||||
|
bpf_for_each_map_elem(&states, check_service_status, &data, 0);
|
||||||
|
|
||||||
|
if (!data.need_to_be_checked) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return data.return_value;
|
||||||
|
}
|
130595
bpf/vmlinux.h
Normal file
130595
bpf/vmlinux.h
Normal file
File diff suppressed because it is too large
Load Diff
127
fileprotector_bpfeb.go
Normal file
127
fileprotector_bpfeb.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileProtectorFileProtectState uint32
|
||||||
|
|
||||||
|
// LoadFileProtector returns the embedded CollectionSpec for FileProtector.
|
||||||
|
func LoadFileProtector() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_FileProtectorBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load FileProtector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFileProtectorObjects loads FileProtector and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *FileProtectorObjects
|
||||||
|
// *FileProtectorPrograms
|
||||||
|
// *FileProtectorMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func LoadFileProtectorObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := LoadFileProtector()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type FileProtectorSpecs struct {
|
||||||
|
FileProtectorProgramSpecs
|
||||||
|
FileProtectorMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type FileProtectorProgramSpecs struct {
|
||||||
|
CheckFileOpen *ebpf.ProgramSpec `ebpf:"check_file_open"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type FileProtectorMapSpecs struct {
|
||||||
|
BannedAccess *ebpf.MapSpec `ebpf:"banned_access"`
|
||||||
|
Roots *ebpf.MapSpec `ebpf:"roots"`
|
||||||
|
States *ebpf.MapSpec `ebpf:"states"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to LoadFileProtectorObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type FileProtectorObjects struct {
|
||||||
|
FileProtectorPrograms
|
||||||
|
FileProtectorMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *FileProtectorObjects) Close() error {
|
||||||
|
return _FileProtectorClose(
|
||||||
|
&o.FileProtectorPrograms,
|
||||||
|
&o.FileProtectorMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to LoadFileProtectorObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type FileProtectorMaps struct {
|
||||||
|
BannedAccess *ebpf.Map `ebpf:"banned_access"`
|
||||||
|
Roots *ebpf.Map `ebpf:"roots"`
|
||||||
|
States *ebpf.Map `ebpf:"states"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileProtectorMaps) Close() error {
|
||||||
|
return _FileProtectorClose(
|
||||||
|
m.BannedAccess,
|
||||||
|
m.Roots,
|
||||||
|
m.States,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to LoadFileProtectorObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type FileProtectorPrograms struct {
|
||||||
|
CheckFileOpen *ebpf.Program `ebpf:"check_file_open"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FileProtectorPrograms) Close() error {
|
||||||
|
return _FileProtectorClose(
|
||||||
|
p.CheckFileOpen,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _FileProtectorClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//
|
||||||
|
//go:embed fileprotector_bpfeb.o
|
||||||
|
var _FileProtectorBytes []byte
|
BIN
fileprotector_bpfeb.o
Normal file
BIN
fileprotector_bpfeb.o
Normal file
Binary file not shown.
127
fileprotector_bpfel.go
Normal file
127
fileprotector_bpfel.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileProtectorFileProtectState uint32
|
||||||
|
|
||||||
|
// LoadFileProtector returns the embedded CollectionSpec for FileProtector.
|
||||||
|
func LoadFileProtector() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_FileProtectorBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load FileProtector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFileProtectorObjects loads FileProtector and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *FileProtectorObjects
|
||||||
|
// *FileProtectorPrograms
|
||||||
|
// *FileProtectorMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func LoadFileProtectorObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := LoadFileProtector()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type FileProtectorSpecs struct {
|
||||||
|
FileProtectorProgramSpecs
|
||||||
|
FileProtectorMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type FileProtectorProgramSpecs struct {
|
||||||
|
CheckFileOpen *ebpf.ProgramSpec `ebpf:"check_file_open"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type FileProtectorMapSpecs struct {
|
||||||
|
BannedAccess *ebpf.MapSpec `ebpf:"banned_access"`
|
||||||
|
Roots *ebpf.MapSpec `ebpf:"roots"`
|
||||||
|
States *ebpf.MapSpec `ebpf:"states"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to LoadFileProtectorObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type FileProtectorObjects struct {
|
||||||
|
FileProtectorPrograms
|
||||||
|
FileProtectorMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *FileProtectorObjects) Close() error {
|
||||||
|
return _FileProtectorClose(
|
||||||
|
&o.FileProtectorPrograms,
|
||||||
|
&o.FileProtectorMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to LoadFileProtectorObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type FileProtectorMaps struct {
|
||||||
|
BannedAccess *ebpf.Map `ebpf:"banned_access"`
|
||||||
|
Roots *ebpf.Map `ebpf:"roots"`
|
||||||
|
States *ebpf.Map `ebpf:"states"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileProtectorMaps) Close() error {
|
||||||
|
return _FileProtectorClose(
|
||||||
|
m.BannedAccess,
|
||||||
|
m.Roots,
|
||||||
|
m.States,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileProtectorPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to LoadFileProtectorObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type FileProtectorPrograms struct {
|
||||||
|
CheckFileOpen *ebpf.Program `ebpf:"check_file_open"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FileProtectorPrograms) Close() error {
|
||||||
|
return _FileProtectorClose(
|
||||||
|
p.CheckFileOpen,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _FileProtectorClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//
|
||||||
|
//go:embed fileprotector_bpfel.o
|
||||||
|
var _FileProtectorBytes []byte
|
BIN
fileprotector_bpfel.o
Normal file
BIN
fileprotector_bpfel.o
Normal file
Binary file not shown.
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module git.jeffthecoder.xyz/public/demo-ebpf-lsm
|
||||||
|
|
||||||
|
go 1.21.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cilium/ebpf v0.11.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
|
||||||
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
|
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||||
|
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||||
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
|
||||||
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
111
main.go
Normal file
111
main.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
"github.com/cilium/ebpf/link"
|
||||||
|
"github.com/cilium/ebpf/rlimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go FileProtector bpf/lsm.c -- -I./bpf -O2
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <time.h>
|
||||||
|
static unsigned long long get_nsecs(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (unsigned long long)ts.tv_sec * 1000000000UL + ts.tv_nsec;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const ( // bpf2go cannot restore enum to corresponding names. so i do it manually
|
||||||
|
FileProtectorFileProtectStateEnabled FileProtectorFileProtectState = iota
|
||||||
|
FileProtectorFileProtectStateTick
|
||||||
|
FileProtectorFileProtectStateMax
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fDebug := flag.Bool("debug", false, "toggle to show full ebpf verifier error")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := rlimit.RemoveMemlock(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileProtectorObjects := FileProtectorObjects{}
|
||||||
|
if err := LoadFileProtectorObjects(&fileProtectorObjects, &ebpf.CollectionOptions{
|
||||||
|
Programs: ebpf.ProgramOptions{
|
||||||
|
LogSize: ebpf.DefaultVerifierLogSize * 1024,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
var ve *ebpf.VerifierError
|
||||||
|
if *fDebug && errors.As(err, &ve) {
|
||||||
|
// Using %+v will print the whole verifier error, not just the last
|
||||||
|
// few lines.
|
||||||
|
fmt.Printf("Verifier error: %+v\n", ve)
|
||||||
|
}
|
||||||
|
log.Panic("failed to attach lsm: ", err)
|
||||||
|
}
|
||||||
|
defer fileProtectorObjects.Close()
|
||||||
|
log.Println("lsm loaded")
|
||||||
|
|
||||||
|
lsm, err := link.AttachLSM(link.LSMOptions{
|
||||||
|
Program: fileProtectorObjects.CheckFileOpen,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("failed to attach lsm: ", err)
|
||||||
|
}
|
||||||
|
defer lsm.Close()
|
||||||
|
log.Println("lsm attached")
|
||||||
|
|
||||||
|
log.Println("configure maps...")
|
||||||
|
for _, path := range flag.Args() {
|
||||||
|
path, _ = filepath.Abs(path)
|
||||||
|
log.Println("-", path)
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("W: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch stat := stat.Sys().(type) {
|
||||||
|
case *syscall.Stat_t:
|
||||||
|
if err := fileProtectorObjects.FileProtectorMaps.Roots.Update(stat.Ino, uint8(1), ebpf.UpdateAny); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("W: incompatible type of stat: %T", stat)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// you should also protect the daemon config itself
|
||||||
|
|
||||||
|
log.Println("configuration done. enabling...")
|
||||||
|
|
||||||
|
if err := fileProtectorObjects.FileProtectorMaps.States.Update(FileProtectorFileProtectStateEnabled, uint64(1), ebpf.UpdateAny); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Second * 10)
|
||||||
|
defer ticker.Stop()
|
||||||
|
log.Println("ticking...but with one step slower intentionally!")
|
||||||
|
for {
|
||||||
|
now := uint64(C.get_nsecs())
|
||||||
|
log.Println("tick:", now)
|
||||||
|
|
||||||
|
if err := fileProtectorObjects.FileProtectorMaps.States.Update(FileProtectorFileProtectStateTick, uint64(now), ebpf.UpdateAny); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ticker.C
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user