Skip to content

Instantly share code, notes, and snippets.

@bored-engineer
Created January 17, 2020 23:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bored-engineer/ff428f9e41a8ff4743435e8ba30bc7fb to your computer and use it in GitHub Desktop.
Save bored-engineer/ff428f9e41a8ff4743435e8ba30bc7fb to your computer and use it in GitHub Desktop.
Watch new process spawns from userspace (without becoming root) via polling /proc (`GOOS=linux GOARCH=amd64 go build watch.go`)
package main
import (
"bufio"
"bytes"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
// Entry point
func main() {
// Read in /etc/passwd so we know which uid is which (os/user cannot x-compile easily)
passwdBytes, err := ioutil.ReadFile("/etc/passwd")
if err != nil {
log.Fatalf("failed to read /etc/passwd: %v", err)
}
scanner := bufio.NewScanner(bytes.NewReader(passwdBytes))
users := make(map[uint32]string)
for scanner.Scan() {
parts := strings.SplitN(scanner.Text(), ":", 4)
if uid, err := strconv.Atoi(parts[2]); err == nil {
users[uint32(uid)] = parts[0]
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("scanner failed: %v", err)
}
// Determine the max PID that can occur
maxPIDBytes, err := ioutil.ReadFile("/proc/sys/kernel/pid_max")
if err != nil {
log.Fatalf("failed to determine max PID: %v", err)
}
maxPID, err := strconv.Atoi(strings.TrimSpace(string(maxPIDBytes)))
if err != nil {
log.Fatalf("failed to parse max PID: %v", err)
}
// Slices are _fast_ compared to map[int]time.Time
processes := make([]time.Time, maxPID)
// Using time.Tick will skip ticks if we're still processing the last tick
for tick := range time.Tick(time.Millisecond * 100) {
// List out /proc to get a list of every running process
files, err := ioutil.ReadDir("/proc")
if err != nil {
log.Fatalf("failed to list proc: %v", err)
}
for _, file := range files {
// Skip anything that's not a PID/number
id, err := strconv.Atoi(file.Name())
if err != nil {
continue
}
// If this is the first time we've seen the process, emit it
if processes[id].IsZero() {
cmdline, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(id), "cmdline"))
if err != nil {
if os.IsNotExist(err) {
log.Printf("Process %d went away while we were reading it", id)
continue
}
log.Fatalf("failed to read PID: %v", err)
}
args := strings.Split(strings.TrimSpace(string(cmdline)), "\000")
username := "unknown"
if stat, ok := file.Sys().(*syscall.Stat_t); ok {
if name, ok := users[stat.Uid]; ok {
username = name
}
}
log.Printf("Process %d spawned by %s with arguments %v", id, username, args)
}
processes[id] = tick
}
// Expire any process we haven't seen in a second
for id, lastSeen := range processes {
if !lastSeen.IsZero() && lastSeen.Before(tick) {
log.Printf("Process %d seems to have stopped", id)
processes[id] = time.Time{}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment