diff --git a/rules/process_freebsd_amd64.go b/rules/process_freebsd_amd64.go index 05ab7c3f..24c416a2 100644 --- a/rules/process_freebsd_amd64.go +++ b/rules/process_freebsd_amd64.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "syscall" "unsafe" @@ -17,7 +18,15 @@ import ( ) // store process name for when dealing with multiple PROCESS-NAME rules -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) +var ( + processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + errNotFound = errors.New("process not found") + matchMeta = func(p *Process, m *C.Metadata) bool { return false } + + defaultSearcher *searcher + + once sync.Once +) type Process struct { adapter string @@ -28,7 +37,7 @@ func (ps *Process) RuleType() C.RuleType { return C.Process } -func (ps *Process) Match(metadata *C.Metadata) bool { +func match(ps *Process, metadata *C.Metadata) bool { key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) cached, hit := processCache.Get(key) if !hit { @@ -45,6 +54,10 @@ func (ps *Process) Match(metadata *C.Metadata) bool { return strings.EqualFold(cached.(string), ps.process) } +func (ps *Process) Match(metadata *C.Metadata) bool { + return matchMeta(ps, metadata) +} + func (p *Process) Adapter() string { return p.adapter } @@ -58,6 +71,15 @@ func (p *Process) ShouldResolveIP() bool { } func NewProcess(process string, adapter string) (*Process, error) { + once.Do(func() { + err := initSearcher() + if err != nil { + log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) + log.Warnln("All PROCESS-NAME rules will be skipped") + return + } + matchMeta = match + }) return &Process{ adapter: adapter, process: process, @@ -85,28 +107,6 @@ func getExecPathFromPID(pid uint32) (string, error) { return filepath.Base(string(buf[:size-1])), nil } -func searchSocketPid(socket uint64) (uint32, error) { - value, err := syscall.Sysctl("kern.file") - if err != nil { - return 0, err - } - - buf := []byte(value) - - // struct xfile - itemSize := 128 - for i := 0; i+itemSize <= len(buf); i += itemSize { - // xfile.xf_data - data := binary.BigEndian.Uint64(buf[i+56 : i+64]) - if data == socket { - // xfile.xf_pid - pid := readNativeUint32(buf[i+8 : i+12]) - return pid, nil - } - } - return 0, errors.New("pid not found") -} - func getExecPathFromAddress(metadata *C.Metadata) (string, error) { ip := metadata.SrcIP port, err := strconv.Atoi(metadata.SrcPort) @@ -115,25 +115,18 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { } var spath string - var itemSize int - var inpOffset int + var isTCP bool switch metadata.NetWork { case C.TCP: spath = "net.inet.tcp.pcblist" - // struct xtcpcb - itemSize = 744 - inpOffset = 8 + isTCP = true case C.UDP: spath = "net.inet.udp.pcblist" - // struct xinpcb - itemSize = 400 - inpOffset = 0 + isTCP = false default: return "", ErrInvalidNetwork } - isIPv4 := ip.To4() != nil - value, err := syscall.Sysctl(spath) if err != nil { return "", err @@ -141,27 +134,73 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { buf := []byte(value) - // skip the first xinpgen(64 bytes) block - for i := 64; i+itemSize <= len(buf); i += itemSize { + pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP) + if err != nil { + return "", err + } + + return getExecPathFromPID(pid) +} + +func readNativeUint32(b []byte) uint32 { + return *(*uint32)(unsafe.Pointer(&b[0])) +} + +type searcher struct { + // sizeof(struct xinpgen) + headSize int + // sizeof(struct xtcpcb) + tcpItemSize int + // sizeof(struct xinpcb) + udpItemSize int + udpInpOffset int + port int + ip int + vflag int + socket int + + // sizeof(struct xfile) + fileItemSize int + data int + pid int +} + +func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) { + var itemSize int + var inpOffset int + + if isTCP { + // struct xtcpcb + itemSize = s.tcpItemSize + inpOffset = 8 + } else { + // struct xinpcb + itemSize = s.udpItemSize + inpOffset = s.udpInpOffset + } + + isIPv4 := ip.To4() != nil + // skip the first xinpgen block + for i := s.headSize; i+itemSize <= len(buf); i += itemSize { inp := i + inpOffset - srcPort := binary.BigEndian.Uint16(buf[inp+254 : inp+256]) + srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2]) - if uint16(port) != srcPort { + if port != srcPort { continue } // xinpcb.inp_vflag - flag := buf[inp+392] + flag := buf[inp+s.vflag] var srcIP net.IP switch { case flag&0x1 > 0 && isIPv4: // ipv4 - srcIP = net.IP(buf[inp+284 : inp+288]) + srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4]) case flag&0x2 > 0 && !isIPv4: // ipv6 - srcIP = net.IP(buf[inp+272 : inp+288]) + srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4]) default: continue } @@ -171,17 +210,85 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { } // xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison - socket := binary.BigEndian.Uint64(buf[inp+16 : inp+24]) - pid, err := searchSocketPid(socket) - if err != nil { - return "", err - } - return getExecPathFromPID(pid) + socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) + return s.searchSocketPid(socket) + } + return 0, errNotFound +} + +func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { + value, err := syscall.Sysctl("kern.file") + if err != nil { + return 0, err } - return "", errors.New("process not found") + buf := []byte(value) + + // struct xfile + itemSize := s.fileItemSize + for i := 0; i+itemSize <= len(buf); i += itemSize { + // xfile.xf_data + data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8]) + if data == socket { + // xfile.xf_pid + pid := readNativeUint32(buf[i+s.pid : i+s.pid+4]) + return pid, nil + } + } + return 0, errNotFound } -func readNativeUint32(b []byte) uint32 { - return *(*uint32)(unsafe.Pointer(&b[0])) +func newSearcher(major int) *searcher { + var s *searcher = nil + switch major { + case 11: + s = &searcher{ + headSize: 32, + tcpItemSize: 1304, + udpItemSize: 632, + port: 198, + ip: 228, + vflag: 116, + socket: 88, + fileItemSize: 80, + data: 56, + pid: 8, + udpInpOffset: 8, + } + case 12: + s = &searcher{ + headSize: 64, + tcpItemSize: 744, + udpItemSize: 400, + port: 254, + ip: 284, + vflag: 392, + socket: 16, + fileItemSize: 128, + data: 56, + pid: 8, + } + } + return s +} + +func initSearcher() error { + osRelease, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return err + } + + dot := strings.Index(osRelease, ".") + if dot != -1 { + osRelease = osRelease[:dot] + } + major, err := strconv.Atoi(osRelease) + if err != nil { + return err + } + defaultSearcher = newSearcher(major) + if defaultSearcher == nil { + return fmt.Errorf("unsupported freebsd version %d", major) + } + return nil }