diff --git a/bpf/lsm.c b/bpf/lsm.c index b77d7ec..040ddc6 100644 --- a/bpf/lsm.c +++ b/bpf/lsm.c @@ -31,6 +31,7 @@ struct { __uint(max_entries, FILE_PROTECT_MAX); __type(key, file_protect_state); __type(value, __u64); + __uint(pinning, LIBBPF_PIN_BY_NAME); } states SEC(".maps"); struct { @@ -38,6 +39,7 @@ struct { __uint(max_entries, 256); __type(key, unsigned long); __type(value, __u8); + __uint(pinning, LIBBPF_PIN_BY_NAME); } roots SEC(".maps"); struct { @@ -45,6 +47,7 @@ struct { __uint(max_entries, 1024 * 1024); __type(key, unsigned long); __type(value, __u64); + __uint(pinning, LIBBPF_PIN_BY_NAME); } banned_access SEC(".maps"); #define MAX_PATH_FRAGEMENTS 256 @@ -123,6 +126,8 @@ int BPF_PROG(check_file_open, struct file *file, int ret) { data.need_to_be_checked = 1; bpf_for_each_map_elem(&states, check_service_status, &data, 0); + // TODO: write perf data + if (!data.need_to_be_checked) { return 0; } diff --git a/fileprotector_bpfeb.o b/fileprotector_bpfeb.o index 381f279..5a46db8 100644 Binary files a/fileprotector_bpfeb.o and b/fileprotector_bpfeb.o differ diff --git a/fileprotector_bpfel.o b/fileprotector_bpfel.o index e3064aa..f6533eb 100644 Binary files a/fileprotector_bpfel.o and b/fileprotector_bpfel.o differ diff --git a/main.go b/main.go index eeae04e..5e08d3d 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,17 @@ const ( // bpf2go cannot restore enum to corresponding names. so i do it manuall FileProtectorFileProtectStateMax ) +const ( + FileProtectorFilePathDisabled uint8 = iota + FileProtectorFilePathEnabled +) + +const ( + // pin path + PinPath = "/sys/fs/bpf/file_protector" + LinkPinPath = PinPath + "/link_file_open" +) + func main() { fDebug := flag.Bool("debug", false, "toggle to show full ebpf verifier error") flag.Parse() @@ -42,11 +53,20 @@ func main() { panic(err) } + if err := os.MkdirAll(PinPath, os.ModePerm); err != nil { + log.Printf("W: failed to create ebpf pin path: %v. directory will not be protected if userspace daemon exited", err) + } else { + log.Printf("ebpf pin path is ensured to be a directory: %v", PinPath) + } + fileProtectorObjects := FileProtectorObjects{} if err := LoadFileProtectorObjects(&fileProtectorObjects, &ebpf.CollectionOptions{ Programs: ebpf.ProgramOptions{ LogSize: ebpf.DefaultVerifierLogSize * 1024, }, + Maps: ebpf.MapOptions{ + PinPath: PinPath, + }, }); err != nil { var ve *ebpf.VerifierError if *fDebug && errors.As(err, &ve) { @@ -59,16 +79,30 @@ func main() { defer fileProtectorObjects.Close() log.Println("lsm loaded") - lsm, err := link.AttachLSM(link.LSMOptions{ - Program: fileProtectorObjects.CheckFileOpen, - }) + progLink, err := link.LoadPinnedLink(LinkPinPath, nil) if err != nil { - log.Panic("failed to attach lsm: ", err) + log.Printf("failed load pinned link(%s): %v. trying to attach the program", LinkPinPath, err) + progLink, err = link.AttachLSM(link.LSMOptions{ + Program: fileProtectorObjects.CheckFileOpen, + }) + if err != nil { + log.Panic("failed to attach lsm: ", err) + } + log.Println("lsm attached") + + if err := progLink.Pin(LinkPinPath); err != nil { + log.Printf("W: failed to pin program: %v. ebpf will not survive after daemon exits.", err) + } else { + log.Printf("link pinned to %v", LinkPinPath) + } + log.Printf("to decrease refcount to release objects, simply run: sudo rm -r %v", PinPath) + } else { + log.Printf("lsm link loaded from existing pinned objects(%v)", LinkPinPath) } - defer lsm.Close() - log.Println("lsm attached") + defer progLink.Close() log.Println("configure maps...") + newRoots := make(map[uint64]uint8) for _, path := range flag.Args() { path, _ = filepath.Abs(path) log.Println("-", path) @@ -79,15 +113,30 @@ func main() { } switch stat := stat.Sys().(type) { case *syscall.Stat_t: - if err := fileProtectorObjects.FileProtectorMaps.Roots.Update(stat.Ino, uint8(1), ebpf.UpdateAny); err != nil { + if err := fileProtectorObjects.FileProtectorMaps.Roots.Update(stat.Ino, FileProtectorFilePathEnabled, ebpf.UpdateAny); err != nil { panic(err) } + newRoots[stat.Ino] = FileProtectorFilePathEnabled default: log.Printf("W: incompatible type of stat: %T", stat) continue } } - // you should also protect the daemon config itself + + var ( + inoKey uint64 + inoEnabled uint8 + ) + iter := fileProtectorObjects.Roots.Iterate() + for iter.Next(&inoKey, &inoEnabled) { + if newRoots[inoKey] != FileProtectorFilePathEnabled && inoEnabled == FileProtectorFilePathEnabled { + log.Printf("disabling root: %v", inoKey) + if err := fileProtectorObjects.Roots.Update(inoKey, FileProtectorFilePathDisabled, 0); err != nil { + log.Printf("W: failed to update root table when disable root: %v", err) + } + } + } + // TODO: you should also protect the daemon config itself log.Println("configuration done. enabling...") @@ -95,12 +144,14 @@ func main() { panic(err) } - ticker := time.NewTicker(time.Second * 10) + ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() log.Println("ticking...but with one step slower intentionally!") for { now := uint64(C.get_nsecs()) - log.Println("tick:", now) + // TODO: read and show perf data + + log.Printf("tick:%v", now) if err := fileProtectorObjects.FileProtectorMaps.States.Update(FileProtectorFileProtectStateTick, uint64(now), ebpf.UpdateAny); err != nil { panic(err)