Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: pr-checks

on:
pull_request:
branches:
- main

permissions:
contents: read

jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: go.mod

- name: Check formatting
run: |
set -euo pipefail
unformatted="$(gofmt -l .)"
if [[ -n "$unformatted" ]]; then
echo "The following files need gofmt:"
echo "$unformatted"
exit 1
fi

test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: go.mod

- name: Run tests
run: make test

lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: go.mod

- name: Run golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: v2.11.4
args: --timeout=5m
23 changes: 23 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: "2"

run:
timeout: 5m

linters:
default: none
enable:
- bodyclose
- copyloopvar
- errcheck
- errorlint
- gosec
- govet
- ineffassign
- misspell
- nilerr
- nilnil
- rowserrcheck
- sqlclosecheck
- staticcheck
- unconvert
- unused
70 changes: 35 additions & 35 deletions cmd/logira/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,45 +109,45 @@ func runHelp(ctx context.Context, prog string, args []string) int {
_ = cli.ExplainCommand(ctx, []string{"-h"})
return 0
default:
fmt.Fprintf(os.Stderr, "unknown command %q\n\n", sub)
_, _ = fmt.Fprintf(os.Stderr, "unknown command %q\n\n", sub)
printRootHelp(os.Stderr, prog)
return 2
}
}

func printRootHelp(w io.Writer, prog string) {
fmt.Fprintf(w, "%s: Linux-only CLI auditor (exec/file/net)\n\n", prog)
fmt.Fprintln(w, "Usage:")
fmt.Fprintf(w, " %s <command> [args]\n", prog)
fmt.Fprintf(w, " %s help [command]\n\n", prog)

fmt.Fprintln(w, "Commands:")
fmt.Fprintln(w, " run Run a command under audit (auto-saves a new run).")
fmt.Fprintln(w, " status Check if logira is ready on this machine.")
fmt.Fprintln(w, " runs List saved runs.")
fmt.Fprintln(w, " view View a run summary (default: last).")
fmt.Fprintln(w, " query Query events in a run (default: last).")
fmt.Fprintln(w, " explain Explain detections for a run (default: last).")
fmt.Fprintln(w)

fmt.Fprintln(w, "Examples:")
fmt.Fprintf(w, " %s run -- bash -lc 'echo hi > x.txt; curl -s https://example.com >/dev/null'\n", prog)
fmt.Fprintf(w, " %s status\n", prog)
fmt.Fprintf(w, " %s runs\n", prog)
fmt.Fprintf(w, " %s view last\n", prog)
fmt.Fprintf(w, " %s query --run last --type net --dest 93.184.216.34:443\n", prog)
fmt.Fprintf(w, " %s explain last\n\n", prog)

fmt.Fprintln(w, "Environment:")
fmt.Fprintln(w, " LOGIRA_HOME Base directory (default: ~/.logira)")
fmt.Fprintln(w, " LOGIRA_SOCK logirad socket path (default: /run/logira.sock)")
fmt.Fprintln(w, " LOGIRA_EXEC_BPF_OBJ Override exec BPF object path (Linux only)")
fmt.Fprintln(w, " LOGIRA_NET_BPF_OBJ Override net BPF object path (Linux only)")
fmt.Fprintln(w, " LOGIRA_FILE_BPF_OBJ Override file BPF object path (Linux only)")
fmt.Fprintln(w)

fmt.Fprintln(w, "Help:")
fmt.Fprintf(w, " %s -h\n", prog)
fmt.Fprintf(w, " %s <command> -h\n", prog)
fmt.Fprintf(w, " %s <command> help\n", prog)
_, _ = fmt.Fprintf(w, "%s: Linux-only CLI auditor (exec/file/net)\n\n", prog)
_, _ = fmt.Fprintln(w, "Usage:")
_, _ = fmt.Fprintf(w, " %s <command> [args]\n", prog)
_, _ = fmt.Fprintf(w, " %s help [command]\n\n", prog)

_, _ = fmt.Fprintln(w, "Commands:")
_, _ = fmt.Fprintln(w, " run Run a command under audit (auto-saves a new run).")
_, _ = fmt.Fprintln(w, " status Check if logira is ready on this machine.")
_, _ = fmt.Fprintln(w, " runs List saved runs.")
_, _ = fmt.Fprintln(w, " view View a run summary (default: last).")
_, _ = fmt.Fprintln(w, " query Query events in a run (default: last).")
_, _ = fmt.Fprintln(w, " explain Explain detections for a run (default: last).")
_, _ = fmt.Fprintln(w)

_, _ = fmt.Fprintln(w, "Examples:")
_, _ = fmt.Fprintf(w, " %s run -- bash -lc 'echo hi > x.txt; curl -s https://example.com >/dev/null'\n", prog)
_, _ = fmt.Fprintf(w, " %s status\n", prog)
_, _ = fmt.Fprintf(w, " %s runs\n", prog)
_, _ = fmt.Fprintf(w, " %s view last\n", prog)
_, _ = fmt.Fprintf(w, " %s query --run last --type net --dest 93.184.216.34:443\n", prog)
_, _ = fmt.Fprintf(w, " %s explain last\n\n", prog)

_, _ = fmt.Fprintln(w, "Environment:")
_, _ = fmt.Fprintln(w, " LOGIRA_HOME Base directory (default: ~/.logira)")
_, _ = fmt.Fprintln(w, " LOGIRA_SOCK logirad socket path (default: /run/logira.sock)")
_, _ = fmt.Fprintln(w, " LOGIRA_EXEC_BPF_OBJ Override exec BPF object path (Linux only)")
_, _ = fmt.Fprintln(w, " LOGIRA_NET_BPF_OBJ Override net BPF object path (Linux only)")
_, _ = fmt.Fprintln(w, " LOGIRA_FILE_BPF_OBJ Override file BPF object path (Linux only)")
_, _ = fmt.Fprintln(w)

_, _ = fmt.Fprintln(w, "Help:")
_, _ = fmt.Fprintf(w, " %s -h\n", prog)
_, _ = fmt.Fprintf(w, " %s <command> -h\n", prog)
_, _ = fmt.Fprintf(w, " %s <command> help\n", prog)
}
6 changes: 5 additions & 1 deletion cmd/logirad/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ func realMain() int {
fmt.Fprintf(os.Stderr, "collector start: %v\n", err)
return 1
}
defer col.Stop(context.Background())
defer func() {
if err := col.Stop(context.Background()); err != nil {
fmt.Fprintf(os.Stderr, "collector stop: %v\n", err)
}
}()

mgr := logirad.NewSessionManager(col)
go func() {
Expand Down
4 changes: 2 additions & 2 deletions collector/linux/exec/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (t *Tracer) Start(ctx context.Context) (<-chan collector.Event, error) {
for _, a := range attach {
prog, ok := coll.Programs[a.prog]
if !ok {
rdr.Close()
_ = rdr.Close()
coll.Close()
return nil, fmt.Errorf("exec program %s not found", a.prog)
}
Expand All @@ -138,7 +138,7 @@ func (t *Tracer) Start(ctx context.Context) (<-chan collector.Event, error) {
for _, l := range links {
_ = l.Close()
}
rdr.Close()
_ = rdr.Close()
coll.Close()
return nil, fmt.Errorf("attach tracepoint %s/%s: %w", a.group, a.name, err)
}
Expand Down
15 changes: 9 additions & 6 deletions collector/linux/file/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -90,7 +91,7 @@ func (w *Watcher) Start(ctx context.Context) (<-chan collector.Event, error) {
w.mu.Lock()
w.started = false
w.mu.Unlock()
return nil, fmt.Errorf("fanotify failed: %v; inotify failed: %w", fanErr, inoErr)
return nil, fmt.Errorf("fanotify failed: %w; inotify failed: %w", fanErr, inoErr)
}
w.mode = "inotify"
} else {
Expand Down Expand Up @@ -194,7 +195,7 @@ func (w *Watcher) runFanotify(ctx context.Context) {

offset := 0
for offset+fanMetaSize <= n {
meta := (*unix.FanotifyEventMetadata)(unsafe.Pointer(&buf[offset]))
meta := (*unix.FanotifyEventMetadata)(unsafe.Pointer(&buf[offset])) //nolint:gosec // fanotify metadata is provided as a kernel byte buffer.
if int(meta.Event_len) < fanMetaSize {
break
}
Expand Down Expand Up @@ -280,7 +281,7 @@ func (w *Watcher) runInotify(ctx context.Context) {

offset := 0
for offset+unix.SizeofInotifyEvent <= n {
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) //nolint:gosec // inotify metadata is provided as a kernel byte buffer.
offset += unix.SizeofInotifyEvent

name := ""
Expand Down Expand Up @@ -352,11 +353,13 @@ func (w *Watcher) makeFileEvent(op, path string, pid int) collector.Event {
}

func hashFile(path string, maxBytes int64) (hash string, size int64, truncated bool, err error) {
f, err := os.Open(path)
f, err := os.Open(path) //nolint:gosec // logira intentionally hashes watched user-selected paths.
if err != nil {
return "", 0, false, err
}
defer f.Close()
defer func() {
_ = f.Close()
}()

st, err := f.Stat()
if err == nil {
Expand All @@ -366,7 +369,7 @@ func hashFile(path string, maxBytes int64) (hash string, size int64, truncated b
h := sha256.New()
if maxBytes > 0 {
_, err = io.CopyN(h, f, maxBytes)
if err != nil && err != io.EOF {
if err != nil && !errors.Is(err, io.EOF) {
return "", size, false, err
}
if size > maxBytes {
Expand Down
4 changes: 2 additions & 2 deletions collector/linux/filetrace/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (t *Tracer) Start(ctx context.Context) (<-chan collector.Event, error) {
for _, a := range attach {
prog, ok := coll.Programs[a.prog]
if !ok {
rdr.Close()
_ = rdr.Close()
coll.Close()
return nil, fmt.Errorf("file program %s not found", a.prog)
}
Expand All @@ -115,7 +115,7 @@ func (t *Tracer) Start(ctx context.Context) (<-chan collector.Event, error) {
for _, l := range links {
_ = l.Close()
}
rdr.Close()
_ = rdr.Close()
coll.Close()
return nil, fmt.Errorf("attach tracepoint %s/%s: %w", a.group, a.name, err)
}
Expand Down
6 changes: 3 additions & 3 deletions collector/linux/net/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (t *Tracer) Start(ctx context.Context) (<-chan collector.Event, error) {
for _, l := range links {
_ = l.Close()
}
rdr.Close()
_ = rdr.Close()
coll.Close()
return nil, fmt.Errorf("net program %s not found", a.prog)
}
Expand All @@ -133,7 +133,7 @@ func (t *Tracer) Start(ctx context.Context) (<-chan collector.Event, error) {
for _, l := range links {
_ = l.Close()
}
rdr.Close()
_ = rdr.Close()
coll.Close()
return nil, fmt.Errorf("attach tracepoint %s/%s: %w", a.group, a.name, err)
}
Expand Down Expand Up @@ -265,7 +265,7 @@ func ipv4String(ip uint32) string {
if ip == 0 {
return ""
}
b := []byte{byte(ip), byte(ip >> 8), byte(ip >> 16), byte(ip >> 24)}
b := []byte{byte(ip), byte(ip >> 8), byte(ip >> 16), byte(ip >> 24)} //nolint:gosec // bytes are intentionally truncated from each IPv4 octet.
return net.IPv4(b[0], b[1], b[2], b[3]).String()
}

Expand Down
10 changes: 5 additions & 5 deletions internal/cgroupv2/cgroupv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Create(runID string) (*Cgroup, error) {
return nil, fmt.Errorf("empty runID")
}
dir := filepath.Join(MountPoint, "logira", runID)
if err := os.MkdirAll(dir, 0o755); err != nil {
if err := os.MkdirAll(dir, 0o755); err != nil { //nolint:gosec // cgroupfs directories must be searchable for delegated process joins.
return nil, fmt.Errorf("mkdir %s: %w", dir, err)
}
return &Cgroup{Path: dir}, nil
Expand All @@ -57,17 +57,17 @@ func CreateDelegated(runID string, uid, gid int, sessionID string) (*Cgroup, err
_ = strings.TrimSpace(runID)

dir := filepath.Join(MountPoint, "logira", strconv.Itoa(uid), sessionID)
if err := os.MkdirAll(dir, 0o755); err != nil {
if err := os.MkdirAll(dir, 0o755); err != nil { //nolint:gosec // cgroupfs delegation requires user-visible cgroup directories.
return nil, fmt.Errorf("mkdir %s: %w", dir, err)
}

// Best-effort delegation: chown the directory and key control files.
_ = os.Chown(dir, uid, gid)
_ = os.Chmod(dir, 0o755)
_ = os.Chmod(dir, 0o755) //nolint:gosec // delegated users need search access to their cgroup directory.
for _, f := range []string{"cgroup.procs", "cgroup.threads"} {
p := filepath.Join(dir, f)
_ = os.Chown(p, uid, gid)
_ = os.Chmod(p, 0o664)
_ = os.Chmod(p, 0o664) //nolint:gosec // cgroup.procs must be writable by the delegated user's group.
}
return &Cgroup{Path: dir}, nil
}
Expand All @@ -77,7 +77,7 @@ func (cg *Cgroup) JoinPID(pid int) error {
return fmt.Errorf("invalid pid %d", pid)
}
p := filepath.Join(cg.Path, "cgroup.procs")
return os.WriteFile(p, []byte(strconv.Itoa(pid)), 0o644)
return os.WriteFile(p, []byte(strconv.Itoa(pid)), 0o644) //nolint:gosec // cgroup.procs permissions are controlled by cgroupfs.
}

// JoinSelf moves the current process into cg by writing its pid to cgroup.procs.
Expand Down
10 changes: 6 additions & 4 deletions internal/cli/exec_in_cgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ func ExecInCgroupCommand(ctx context.Context, args []string) error {
if err != nil {
return err
}
return syscall.Exec(target, cmdArgs, os.Environ())
return syscall.Exec(target, cmdArgs, os.Environ()) //nolint:gosec // logira run intentionally execs the audited user command.
}

func joinCgroupSelf(ctx context.Context, cgroupPath, sessionID string) error {
p := filepath.Join(cgroupPath, "cgroup.procs")
if err := os.WriteFile(p, []byte(strconv.Itoa(os.Getpid())), 0o644); err == nil {
if err := os.WriteFile(p, []byte(strconv.Itoa(os.Getpid())), 0o644); err == nil { //nolint:gosec // cgroup.procs permissions are controlled by cgroupfs.
return nil
}

Expand All @@ -71,12 +71,14 @@ func joinCgroupSelf(ctx context.Context, cgroupPath, sessionID string) error {
if err != nil {
return err
}
defer c.Close()
defer func() {
_ = c.Close()
}()
if err := c.AttachPID(ctx, sessionID, os.Getpid()); err != nil {
return err
}
// Best-effort re-check.
if err := os.WriteFile(p, []byte(strconv.Itoa(os.Getpid())), 0o644); err != nil && !errors.Is(err, os.ErrPermission) {
if err := os.WriteFile(p, []byte(strconv.Itoa(os.Getpid())), 0o644); err != nil && !errors.Is(err, os.ErrPermission) { //nolint:gosec // cgroup.procs permissions are controlled by cgroupfs.
return err
}
return nil
Expand Down
Loading