package procsnitch import ( "fmt" "io/ioutil" "os" "path" "strconv" "strings" "sync" "syscall" ) // Info is a struct containing the result of a socket proc query type Info struct { UID int GID int Pid int ParentPid int loaded bool ExePath string CmdLine string FirstArg string ParentCmdLine string ParentExePath string Sandbox string Inode uint64 FD int } type pidCache struct { cacheMap map[uint64]*Info lock sync.Mutex } func (pc *pidCache) lookup(inode uint64) *Info { pc.lock.Lock() defer pc.lock.Unlock() pi, ok := pc.cacheMap[inode] if ok && pi.loadProcessInfo() { return pi } pc.cacheMap = loadCache() pi, ok = pc.cacheMap[inode] if ok && pi.loadProcessInfo() { return pi } return nil } func loadCache() map[uint64]*Info { cmap := make(map[uint64]*Info) for _, n := range readdir("/proc") { pid := toPid(n) if pid != 0 { inodes, fds := inodesFromPid(pid) for iind, inode := range inodes { pinfo := &Info{Inode: inode, Pid: pid, FD: fds[iind]} cmap[inode] = pinfo } } } return cmap } func toPid(name string) int { pid, err := strconv.ParseUint(name, 10, 32) if err != nil { return 0 } fdpath := fmt.Sprintf("/proc/%d/fd", pid) fi, err := os.Stat(fdpath) if err != nil { return 0 } if !fi.IsDir() { return 0 } return (int)(pid) } func inodesFromPid(pid int) ([]uint64, []int) { var inodes []uint64 var fds []int fdpath := fmt.Sprintf("/proc/%d/fd", pid) for _, n := range readdir(fdpath) { if link, err := os.Readlink(path.Join(fdpath, n)); err != nil { if !os.IsNotExist(err) { log.Warningf("Error reading link %s: %v", n, err) } } else { fd, err := strconv.Atoi(n) if err != nil { log.Warningf("Error retrieving fd associated with pid %v: %v", pid, err) fd = -1 } if inode := extractSocket(link); inode > 0 { inodes = append(inodes, inode) fds = append(fds, fd) } } } return inodes, fds } func extractSocket(name string) uint64 { if !strings.HasPrefix(name, "socket:[") || !strings.HasSuffix(name, "]") { return 0 } val := name[8 : len(name)-1] inode, err := strconv.ParseUint(val, 10, 64) if err != nil { log.Warningf("Error parsing inode value from %s: %v", name, err) return 0 } return inode } func readdir(dir string) []string { d, err := os.Open(dir) if err != nil { log.Warningf("Error opening directory %s: %v", dir, err) return nil } defer d.Close() names, err := d.Readdirnames(0) if err != nil { log.Warningf("Error reading directory names from %s: %v", dir, err) return nil } return names } func (pi *Info) loadProcessInfo() bool { if pi.loaded { return true } exePath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pi.Pid)) if err != nil { log.Warningf("Error reading exe link for pid %d: %v", pi.Pid, err) return false } bcs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pi.Pid)) if err != nil { log.Warningf("Error reading cmdline for pid %d: %v", pi.Pid, err) return false } for i, b := range bcs { if b == 0 { bcs[i] = byte(' ') } } bs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", pi.Pid)) if err != nil { log.Warningf("Error reading cmdline for pid %d: %v", pi.Pid, err) return false } fs := strings.Fields(string(bs)) if len(fs) < 50 { log.Warningf("Unable to parse stat for pid %d: ", pi.Pid) return false } ppid := toPid(fs[3]) pexePath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", ppid)) if err != nil { log.Warningf("Error reading exe link for parent pid %d: %v", ppid, err) return false } pbs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", ppid)) if err != nil { log.Warningf("Error reading cmdline for parent pid %d: %v", ppid, err) return false } for i, b := range pbs { if b == 0 { pbs[i] = byte(' ') } } finfo, err := os.Stat(fmt.Sprintf("/proc/%d", pi.Pid)) if err != nil { log.Warningf("Could not stat /proc/%d: %v", pi.Pid, err) return false } sys := finfo.Sys().(*syscall.Stat_t) pi.UID = int(sys.Uid) pi.GID = int(sys.Gid) pi.ParentPid = ppid pi.ParentCmdLine = string(pbs) pi.ParentExePath = string(pexePath) pi.ExePath = exePath pi.CmdLine = string(bcs) pi.loaded = true return true }