From eff645275d27c51ad54591ae3581df1ff0f9466d Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Fri, 21 Jul 2023 00:20:37 +1000 Subject: [PATCH] Refactor code to wrap gopsutils (#2824) The gopsutils library shells out to various programs (like ps, lsof) under some situations. This makes it unpredictable how expensive an operation is. This PR wraps gopsutils carefully ensuring that no expensive operations occur. Fixes: #2822 --- actions/client_info.go | 6 +- actions/throttler.go | 60 +++--- bin/golden.go | 8 +- {freebsd => docs/freebsd}/velociraptor.rc | 0 executor/executor.go | 2 +- uploads/file_based.go | 11 +- vql/filesystem/filesystem.go | 4 +- vql/filesystem/filesystems.go | 20 +- vql/functions/pskill.go | 7 +- vql/info.go | 8 +- vql/linux/connections.go | 7 +- vql/process.go | 49 +---- vql/psutils/common.go | 52 ++++++ vql/psutils/connections.go | 1 + vql/psutils/cpu.go | 11 ++ vql/psutils/disk.go | 42 +++++ vql/psutils/doc.go | 16 ++ vql/psutils/gopsutil_LICENSE.txt | 61 ++++++ vql/psutils/host.go | 19 ++ vql/psutils/net.go | 25 +++ vql/psutils/process.go | 40 ++++ vql/psutils/process_darwin.go | 114 ++++++++++++ vql/psutils/process_darwin_cgo.go | 216 ++++++++++++++++++++++ vql/psutils/process_darwin_nocgo.go | 32 ++++ vql/psutils/process_posix.go | 119 ++++++++++++ vql/psutils/process_windows.go | 153 +++++++++++++++ vql/psutils/process_windows_amd64.go | 17 ++ vql/psutils/process_windows_i386.go | 17 ++ vql/tools/collector/collector_manager.go | 4 +- 29 files changed, 1000 insertions(+), 121 deletions(-) rename {freebsd => docs/freebsd}/velociraptor.rc (100%) create mode 100644 vql/psutils/common.go create mode 100644 vql/psutils/connections.go create mode 100644 vql/psutils/cpu.go create mode 100644 vql/psutils/disk.go create mode 100644 vql/psutils/doc.go create mode 100644 vql/psutils/gopsutil_LICENSE.txt create mode 100644 vql/psutils/host.go create mode 100644 vql/psutils/net.go create mode 100644 vql/psutils/process.go create mode 100644 vql/psutils/process_darwin.go create mode 100644 vql/psutils/process_darwin_cgo.go create mode 100644 vql/psutils/process_darwin_nocgo.go create mode 100644 vql/psutils/process_posix.go create mode 100644 vql/psutils/process_windows.go create mode 100644 vql/psutils/process_windows_amd64.go create mode 100644 vql/psutils/process_windows_i386.go diff --git a/actions/client_info.go b/actions/client_info.go index 3735b88ea72..85d9c384e12 100644 --- a/actions/client_info.go +++ b/actions/client_info.go @@ -1,12 +1,13 @@ package actions import ( + "context" "runtime" "github.com/Showmax/go-fqdn" - "github.com/shirou/gopsutil/v3/host" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/vql/psutils" ) // Return essential information about the client used for indexing @@ -15,10 +16,11 @@ import ( // server periodically to avoid having to issue Generic.Client.Info // hunts all the time. func GetClientInfo( + ctx context.Context, config_obj *config_proto.Config) *actions_proto.ClientInfo { result := &actions_proto.ClientInfo{} - info, err := host.Info() + info, err := psutils.InfoWithContext(ctx) if err == nil { result = &actions_proto.ClientInfo{ Hostname: info.Hostname, diff --git a/actions/throttler.go b/actions/throttler.go index 243d88f3e21..cf59c401c0f 100644 --- a/actions/throttler.go +++ b/actions/throttler.go @@ -8,16 +8,17 @@ package actions import ( "context" "os" + "runtime" "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/process" "www.velocidex.com/golang/velociraptor/utils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/types" + + "www.velocidex.com/golang/velociraptor/vql/psutils" ) var ( @@ -56,7 +57,8 @@ type statsCollector struct { cond *sync.Cond id uint64 - proc *process.Process + // Our own pid so we can check stats etc + pid int32 samples [2]sample @@ -68,22 +70,12 @@ type statsCollector struct { number_of_cores float64 } -func newStatsCollector() (*statsCollector, error) { - proc, err := process.NewProcess(int32(os.Getpid())) - if err != nil || proc == nil { - return nil, err - } - - number_of_cores, err := cpu.Counts(true) - if err != nil || number_of_cores <= 0 { - return nil, err - } - +func newStatsCollector(ctx context.Context) (*statsCollector, error) { result := &statsCollector{ check_duration_msec: 300, id: utils.GetId(), - proc: proc, - number_of_cores: float64(number_of_cores), + pid: int32(os.Getpid()), + number_of_cores: float64(runtime.NumCPU()), } result.cond = sync.NewCond(&result.mu) @@ -136,15 +128,15 @@ func (self *statsCollector) GetAverageIOPS() float64 { // process. This is called not that frequently in order to minimize // the overheads of making a system call. func (self *statsCollector) getCpuTime(ctx context.Context) float64 { - cpu_time, err := self.proc.TimesWithContext(ctx) + cpu_time, err := psutils.TimesWithContext(ctx, self.pid) if err != nil { return 0 } - return cpu_time.Total() + return cpu_time.User + cpu_time.System } func (self *statsCollector) getIops(ctx context.Context) float64 { - counters, err := self.proc.IOCountersWithContext(ctx) + counters, err := psutils.IOCountersWithContext(ctx, self.pid) if err != nil { return 0 } @@ -259,7 +251,7 @@ func NewThrottler( if stats == nil { var err error - stats, err = newStatsCollector() + stats, err = newStatsCollector(ctx) if err != nil { return nil } @@ -298,12 +290,9 @@ func init() { return stats.GetAverageIOPS() } - proc, err := process.NewProcess(int32(os.Getpid())) - if err != nil || proc == nil { - return 0 - } - - counters, err := proc.IOCounters() + ctx := context.Background() + pid := int32(os.Getpid()) + counters, err := psutils.IOCountersWithContext(ctx, pid) if err != nil { return 0 } @@ -313,26 +302,19 @@ func init() { _ = prometheus.Register(promauto.NewGaugeFunc( prometheus.GaugeOpts{ Name: "process_cpu_used", - Help: "Current CPU utilization by this process", + Help: "Total CPU utilization by this process", }, func() float64 { if stats != nil { return stats.GetAverageCPULoad() } - proc, err := process.NewProcess(int32(os.Getpid())) - if err != nil || proc == nil { - return 0 - } - - number_of_cores, err := cpu.Counts(true) - if err != nil || number_of_cores <= 0 { - return 0 - } - - cpu_time, err := proc.CPUPercent() + ctx := context.Background() + pid := int32(os.Getpid()) + number_of_cores := runtime.NumCPU() + cpu_time, err := psutils.TimesWithContext(ctx, pid) if err != nil { return 0 } - return cpu_time / float64(number_of_cores) + return float64(cpu_time.User+cpu_time.System) / float64(number_of_cores) })) } diff --git a/bin/golden.go b/bin/golden.go index 2c346a6fbe1..3b17dd48940 100644 --- a/bin/golden.go +++ b/bin/golden.go @@ -35,7 +35,6 @@ import ( "github.com/Velocidex/yaml/v2" errors "github.com/go-errors/errors" "github.com/sergi/go-diff/diffmatchpatch" - "github.com/shirou/gopsutil/v3/process" "www.velocidex.com/golang/velociraptor/actions" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" @@ -49,6 +48,7 @@ import ( "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/velociraptor/vql/acl_managers" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/velociraptor/vql/remapping" vfilter "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" @@ -128,9 +128,9 @@ func makeCtxWithTimeout( // the goroutines and mutex and hard exit. case <-time.After(time.Second): if time.Now().Before(deadline) { - proc, _ := process.NewProcess(int32(os.Getpid())) - total_time, _ := proc.Percent(0) - memory, _ := proc.MemoryInfo() + pid := int32(os.Getpid()) + total_time, _ := psutils.TimesWithContext(ctx, pid) + memory, _ := psutils.MemoryInfoWithContext(ctx, pid) fmt.Printf("Not time to fire yet %v %v %v\n", time.Now(), total_time, memory) diff --git a/freebsd/velociraptor.rc b/docs/freebsd/velociraptor.rc similarity index 100% rename from freebsd/velociraptor.rc rename to docs/freebsd/velociraptor.rc diff --git a/executor/executor.go b/executor/executor.go index 366cc59551c..b324f916f2d 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -77,7 +77,7 @@ type ClientExecutor struct { } func (self *ClientExecutor) GetClientInfo() *actions_proto.ClientInfo { - return actions.GetClientInfo(self.config_obj) + return actions.GetClientInfo(self.ctx, self.config_obj) } func (self *ClientExecutor) FlowManager() *responder.FlowManager { diff --git a/uploads/file_based.go b/uploads/file_based.go index 0dffbd138c1..da9c52b7555 100644 --- a/uploads/file_based.go +++ b/uploads/file_based.go @@ -80,8 +80,7 @@ func (self *FileBasedUploader) Upload( atime time.Time, ctime time.Time, btime time.Time, - reader io.Reader) ( - *UploadResponse, error) { + reader io.Reader) (*UploadResponse, error) { if self.UploadDir == "" { scope.Log("UploadDir is not set") @@ -152,7 +151,10 @@ func (self *FileBasedUploader) Upload( } // It is not an error if we cant set the timestamps - best effort. - _ = setFileTimestamps(file_path, mtime, atime, ctime) + err = setFileTimestamps(file_path, mtime, atime, ctime) + if err != nil { + scope.Log("FileBasedUploader: %v", err) + } scope.Log("Uploaded %v (%v bytes)", file_path, offset) result = &UploadResponse{ @@ -171,8 +173,7 @@ func (self *FileBasedUploader) maybeCollectSparseFile( ctx context.Context, reader io.Reader, store_as_name *accessors.OSPath, - sanitized_name string) ( - *UploadResponse, error) { + sanitized_name string) (*UploadResponse, error) { // Can the reader produce ranges? range_reader, ok := reader.(RangeReader) diff --git a/vql/filesystem/filesystem.go b/vql/filesystem/filesystem.go index b047e939852..c640ad868ae 100644 --- a/vql/filesystem/filesystem.go +++ b/vql/filesystem/filesystem.go @@ -26,13 +26,13 @@ import ( "github.com/Velocidex/ordereddict" "github.com/go-errors/errors" - "github.com/shirou/gopsutil/v3/disk" "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/acls" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/glob" "www.velocidex.com/golang/velociraptor/vql" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -448,7 +448,7 @@ func init() { scope vfilter.Scope, args *ordereddict.Dict) []vfilter.Row { var result []vfilter.Row - partitions, err := disk.Partitions(true) + partitions, err := psutils.PartitionsWithContext(ctx) if err == nil { for _, item := range partitions { result = append(result, item) diff --git a/vql/filesystem/filesystems.go b/vql/filesystem/filesystems.go index 1a4b6612786..236c7115be1 100644 --- a/vql/filesystem/filesystems.go +++ b/vql/filesystem/filesystems.go @@ -21,18 +21,18 @@ import ( "context" "github.com/Velocidex/ordereddict" - "github.com/shirou/gopsutil/v3/disk" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) type ExtendedFileSystemInfo struct { - Partition disk.PartitionStat + Partition psutils.PartitionStat } -func (self ExtendedFileSystemInfo) Usage() *disk.UsageStat { - usage, err := disk.Usage(self.Partition.Mountpoint) +func (self ExtendedFileSystemInfo) Usage() *psutils.UsageStat { + usage, err := psutils.Usage(self.Partition.Mountpoint) if err != nil { return nil } @@ -40,15 +40,12 @@ func (self ExtendedFileSystemInfo) Usage() *disk.UsageStat { return usage } -/* func (self ExtendedFileSystemInfo) SerialNumber() string { - return disk.GetDiskSerialNumber(self.Partition.Device) + res, _ := psutils.SerialNumber(self.Partition.Device) + return res } -*/ -type PartitionsArgs struct { - All bool `vfilter:"optional,field=all,doc=If specified list all Partitions"` -} +type PartitionsArgs struct{} func init() { vql_subsystem.RegisterPlugin( @@ -67,7 +64,8 @@ func init() { return result } - if partitions, err := disk.Partitions(arg.All); err == nil { + partitions, err := psutils.PartitionsWithContext(ctx) + if err == nil { for _, item := range partitions { extended_info := ExtendedFileSystemInfo{item} result = append(result, extended_info) diff --git a/vql/functions/pskill.go b/vql/functions/pskill.go index bb86c544e18..8534ab4d1dd 100644 --- a/vql/functions/pskill.go +++ b/vql/functions/pskill.go @@ -21,10 +21,10 @@ import ( "context" "github.com/Velocidex/ordereddict" - "github.com/shirou/gopsutil/v3/process" "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/vql" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -54,13 +54,12 @@ func (self *PsKillFunction) Call(ctx context.Context, return vfilter.Null{} } - process_obj, err := process.NewProcess(int32(arg.Pid)) + err = psutils.Kill(int32(arg.Pid)) if err != nil { scope.Log("pskill: %v", err) return vfilter.Null{} } - - return process_obj.Kill() + return arg.Pid } func (self PsKillFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo { diff --git a/vql/info.go b/vql/info.go index cf688339038..fe76bfadc0c 100644 --- a/vql/info.go +++ b/vql/info.go @@ -25,9 +25,9 @@ import ( fqdn "github.com/Showmax/go-fqdn" "github.com/Velocidex/ordereddict" - "github.com/shirou/gopsutil/v3/host" "www.velocidex.com/golang/velociraptor/acls" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -36,7 +36,7 @@ var ( start_time = time.Now() ) -func GetInfo(host *host.InfoStat) *ordereddict.Dict { +func GetInfo(host *psutils.InfoStat) *ordereddict.Dict { me, _ := os.Executable() return ordereddict.NewDict(). Set("Hostname", host.Hostname). @@ -84,9 +84,9 @@ func init() { // It turns out that host.Info() is // actually rather slow so we cache it // in the scope cache. - info, ok := CacheGet(scope, "__info").(*host.InfoStat) + info, ok := CacheGet(scope, "__info").(*psutils.InfoStat) if !ok { - info, err = host.Info() + info, err = psutils.InfoWithContext(ctx) if err != nil { scope.Log("info: %s", err) return result diff --git a/vql/linux/connections.go b/vql/linux/connections.go index 44bd4372cdd..982ddba5b7d 100755 --- a/vql/linux/connections.go +++ b/vql/linux/connections.go @@ -23,14 +23,14 @@ import ( "syscall" "github.com/Velocidex/ordereddict" - "github.com/shirou/gopsutil/v3/net" "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/vql" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" ) -func makeDict(in net.ConnectionStat) *ordereddict.Dict { +func makeDict(in psutils.ConnectionStat) *ordereddict.Dict { var family, conn_type string switch in.Family { @@ -90,7 +90,8 @@ func init() { return result } - if cons, err := net.Connections("all"); err == nil { + cons, err := psutils.ConnectionsWithContext(ctx, "all") + if err == nil { for _, item := range cons { result = append(result, makeDict(item)) } diff --git a/vql/process.go b/vql/process.go index 7673d296f1a..60d8b87c0a5 100755 --- a/vql/process.go +++ b/vql/process.go @@ -18,16 +18,14 @@ along with this program. If not, see . */ -// This module is built on gopsutils but this is too slow and -// inefficient. Eventually we will remove it from the codebase. package vql import ( "context" "github.com/Velocidex/ordereddict" - "github.com/shirou/gopsutil/v3/process" "www.velocidex.com/golang/velociraptor/acls" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -62,17 +60,17 @@ func init() { // If the user asked for one process // just return that one. if arg.Pid != 0 { - process_obj, err := process.NewProcess(int32(arg.Pid)) + process_obj, err := psutils.GetProcess(ctx, int32(arg.Pid)) if err == nil { - result = append(result, getProcessData(process_obj)) + result = append(result, process_obj) } return result } - processes, err := process.Processes() + processes, err := psutils.ListProcesses(ctx) if err == nil { for _, item := range processes { - result = append(result, getProcessData(item)) + result = append(result, item) } } return result @@ -81,40 +79,3 @@ func init() { Doc: "List processes", }) } - -// Only get a few fields from the process object otherwise we will -// spend too much time calling into virtual methods. -func getProcessData(process *process.Process) *ordereddict.Dict { - result := ordereddict.NewDict().SetCaseInsensitive(). - Set("Pid", process.Pid) - - name, _ := process.Name() - result.Set("Name", name) - - ppid, _ := process.Ppid() - result.Set("Ppid", ppid) - - // Make it compatible with the Windows pslist() - cmdline, _ := process.Cmdline() - result.Set("CommandLine", cmdline) - - create_time, _ := process.CreateTime() - result.Set("CreateTime", create_time) - - times, _ := process.Times() - result.Set("Times", times) - - exe, _ := process.Exe() - result.Set("Exe", exe) - - cwd, _ := process.Cwd() - result.Set("Cwd", cwd) - - user, _ := process.Username() - result.Set("Username", user) - - memory_info, _ := process.MemoryInfo() - result.Set("MemoryInfo", memory_info) - - return result -} diff --git a/vql/psutils/common.go b/vql/psutils/common.go new file mode 100644 index 00000000000..4abdd353491 --- /dev/null +++ b/vql/psutils/common.go @@ -0,0 +1,52 @@ +package psutils + +import ( + "fmt" + "os" +) + +// PathExistsWithContents returns the filename exists and it is not +// empty +func PathExistsWithContents(filename string) bool { + info, err := os.Stat(filename) + if err != nil { + return false + } + return info.Size() > 4 // at least 4 bytes +} + +func GetHostProc(pid int32) string { + return fmt.Sprintf("%s/%d", GetEnv("HOST_PROC", "/proc"), pid) +} + +func GetEnv(key string, dfault string) string { + value := os.Getenv(key) + if value == "" { + return dfault + } + + return value +} + +func ByteToString(orig []byte) string { + n := -1 + l := -1 + for i, b := range orig { + // skip left side null + if l == -1 && b == 0 { + continue + } + if l == -1 { + l = i + } + + if b == 0 { + break + } + n = i + 1 + } + if n == -1 { + return string(orig) + } + return string(orig[l:n]) +} diff --git a/vql/psutils/connections.go b/vql/psutils/connections.go new file mode 100644 index 00000000000..261cf125037 --- /dev/null +++ b/vql/psutils/connections.go @@ -0,0 +1 @@ +package psutils diff --git a/vql/psutils/cpu.go b/vql/psutils/cpu.go new file mode 100644 index 00000000000..f0ad7452cf9 --- /dev/null +++ b/vql/psutils/cpu.go @@ -0,0 +1,11 @@ +package psutils + +import ( + "context" + + "github.com/shirou/gopsutil/v3/cpu" +) + +func CountsWithContext(ctx context.Context, logical bool) (int, error) { + return cpu.CountsWithContext(ctx, logical) +} diff --git a/vql/psutils/disk.go b/vql/psutils/disk.go new file mode 100644 index 00000000000..24a88501826 --- /dev/null +++ b/vql/psutils/disk.go @@ -0,0 +1,42 @@ +package psutils + +import ( + "context" + + "github.com/shirou/gopsutil/v3/disk" +) + +type UsageStat struct { + disk.UsageStat +} + +type PartitionStat struct { + disk.PartitionStat +} + +func Usage(mount string) (*UsageStat, error) { + usage, err := disk.Usage(mount) + if err != nil { + return nil, err + } + + return &UsageStat{*usage}, nil +} + +func SerialNumber(disk_name string) (string, error) { + return disk.SerialNumber(disk_name) +} + +func PartitionsWithContext(ctx context.Context) ([]PartitionStat, error) { + res, err := disk.PartitionsWithContext(ctx, true) + if err != nil { + return nil, err + } + + result := make([]PartitionStat, 0, len(res)) + for _, i := range res { + result = append(result, PartitionStat{i}) + } + + return result, nil +} diff --git a/vql/psutils/doc.go b/vql/psutils/doc.go new file mode 100644 index 00000000000..37976548df4 --- /dev/null +++ b/vql/psutils/doc.go @@ -0,0 +1,16 @@ +package psutils + +/* + This is a wrapper package around gopsutils. gopsutils chooses to + shell out to various commands (ps, lsof etc) on some platforms which + makes it very hard to predict how expensive an operation is likely + to be. + + The purpose of this wrapper is to better control what operations are + allowed on different platforms to avoid this shelling behavior. + + We normally delegate through to gopsutils but in some situations we + would prefer to fail the operations rather than shell out to + external tools. Eventually we can remove all dependency on gopsutils + from this wrapper and simply reimplement the API surface we need. +*/ diff --git a/vql/psutils/gopsutil_LICENSE.txt b/vql/psutils/gopsutil_LICENSE.txt new file mode 100644 index 00000000000..6f06adcbff3 --- /dev/null +++ b/vql/psutils/gopsutil_LICENSE.txt @@ -0,0 +1,61 @@ +gopsutil is distributed under BSD license reproduced below. + +Copyright (c) 2014, WAKAYAMA Shirou +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the gopsutil authors nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +------- +internal/common/binary.go in the gopsutil is copied and modified from golang/encoding/binary.go. + + + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vql/psutils/host.go b/vql/psutils/host.go new file mode 100644 index 00000000000..8136eeb839a --- /dev/null +++ b/vql/psutils/host.go @@ -0,0 +1,19 @@ +package psutils + +import ( + "context" + + "github.com/shirou/gopsutil/v3/host" +) + +type InfoStat struct { + host.InfoStat +} + +func InfoWithContext(ctx context.Context) (*InfoStat, error) { + res, err := host.InfoWithContext(ctx) + if err != nil { + return nil, err + } + return &InfoStat{InfoStat: *res}, err +} diff --git a/vql/psutils/net.go b/vql/psutils/net.go new file mode 100644 index 00000000000..0bcdcc11612 --- /dev/null +++ b/vql/psutils/net.go @@ -0,0 +1,25 @@ +package psutils + +import ( + "context" + + "github.com/shirou/gopsutil/v3/net" +) + +type ConnectionStat struct { + net.ConnectionStat +} + +func ConnectionsWithContext( + ctx context.Context, kind string) ([]ConnectionStat, error) { + res, err := net.ConnectionsWithContext(ctx, kind) + if err != nil { + return nil, err + } + + result := make([]ConnectionStat, 0, len(res)) + for _, i := range res { + result = append(result, ConnectionStat{ConnectionStat: i}) + } + return result, nil +} diff --git a/vql/psutils/process.go b/vql/psutils/process.go new file mode 100644 index 00000000000..cdc8ce201d3 --- /dev/null +++ b/vql/psutils/process.go @@ -0,0 +1,40 @@ +package psutils + +import ( + "errors" + "os" +) + +var ( + ErrorProcessNotRunning = errors.New("ErrorProcessNotRunning") + ErrorNotPermitted = errors.New("operation not permitted") + NotImplementedError = errors.New("NotImplementedError") +) + +type TimesStat struct { + CPU string `json:"cpu"` + User float64 `json:"user"` + System float64 `json:"system"` +} + +type MemoryInfoStat struct { + RSS uint64 `json:"rss"` // bytes + VMS uint64 `json:"vms"` // bytes + Swap uint64 `json:"swap"` // bytes +} + +type IOCountersStat struct { + ReadCount uint64 `json:"readCount"` + WriteCount uint64 `json:"writeCount"` + ReadBytes uint64 `json:"readBytes"` + WriteBytes uint64 `json:"writeBytes"` +} + +// This works on all OSs +func Kill(pid int32) error { + process, err := os.FindProcess(int(pid)) + if err != nil { + return err + } + return process.Kill() +} diff --git a/vql/psutils/process_darwin.go b/vql/psutils/process_darwin.go new file mode 100644 index 00000000000..b024ab1fba8 --- /dev/null +++ b/vql/psutils/process_darwin.go @@ -0,0 +1,114 @@ +//go:build darwin +// +build darwin + +package psutils + +import ( + "context" + "os/user" + "strconv" + "strings" + "time" + + "golang.org/x/sys/unix" + + "github.com/Velocidex/ordereddict" + "www.velocidex.com/golang/velociraptor/utils" +) + +func GetProcess(ctx context.Context, pid int32) (*ordereddict.Dict, error) { + proc, err := getKProc(pid) + if err != nil { + return nil, err + } + + return getProcessData(ctx, proc), nil +} + +func ListProcesses(ctx context.Context) ([]*ordereddict.Dict, error) { + result := []*ordereddict.Dict{} + processes, err := Processes() + if err != nil { + return nil, err + } + + for _, item := range processes { + if false { + utils.Debug(item) + } + result = append(result, getProcessData(ctx, &item)) + } + + return result, nil +} + +func Processes() ([]unix.KinfoProc, error) { + var ret []unix.KinfoProc + + kprocs, err := unix.SysctlKinfoProcSlice("kern.proc.all") + if err != nil { + return ret, err + } + + for _, proc := range kprocs { + ret = append(ret, proc) + } + + return ret, nil +} + +func getKProc(pid int32) (*unix.KinfoProc, error) { + return unix.SysctlKinfoProc("kern.proc.pid", int(pid)) +} + +func getProcessData(ctx context.Context, + proc *unix.KinfoProc) *ordereddict.Dict { + pid := proc.Proc.P_pid + + name, err := cmdNameWithContext(ctx, pid) + if err != nil { + name = ByteToString(proc.Proc.P_comm[:]) + } + + cmdline, err := cmdlineSliceWithContext(ctx, pid) + if err != nil { + cmdline = append(cmdline, name) + } + + times, _ := TimesWithContext(ctx, pid) + exe, _ := ExeWithContext(ctx, pid) + cwd, _ := CwdWithContext(ctx, pid) + + uid := proc.Eproc.Ucred.Uid + + username := "" + user, err := user.LookupId(strconv.Itoa(int(uid))) + if err == nil { + username = user.Username + } + + memory_info, _ := MemoryInfoWithContext(ctx, pid) + + result := ordereddict.NewDict(). + SetCaseInsensitive(). + Set("Pid", pid). + Set("Name", name). + Set("Ppid", proc.Eproc.Ppid). + + // Make it compatible with the Windows pslist() + Set("CommandLine", strings.Join(cmdline, " ")). + Set("_Argv", cmdline). + Set("CreateTime", time.Unix(proc.Proc.P_starttime.Sec, int64(proc.Proc.P_starttime.Usec)/1000)). + Set("Times", times). + Set("Exe", exe). + Set("Cwd", cwd). + Set("Uid", uid). + Set("Username", username). + Set("MemoryInfo", memory_info) + + return result +} + +func IOCountersWithContext(ctx context.Context, pid int32) (*IOCountersStat, error) { + return nil, NotImplementedError +} diff --git a/vql/psutils/process_darwin_cgo.go b/vql/psutils/process_darwin_cgo.go new file mode 100644 index 00000000000..e27e0171ce6 --- /dev/null +++ b/vql/psutils/process_darwin_cgo.go @@ -0,0 +1,216 @@ +//go:build darwin && cgo +// +build darwin,cgo + +package psutils + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +import "C" + +import ( + "bytes" + "context" + "fmt" + "strings" + "syscall" + "unsafe" +) + +var ( + argMax int + timescaleToNanoSeconds float64 +) + +func init() { + argMax = getArgMax() + timescaleToNanoSeconds = getTimeScaleToNanoSeconds() +} + +func getArgMax() int { + var ( + mib = [...]C.int{C.CTL_KERN, C.KERN_ARGMAX} + argmax C.int + size C.size_t = C.ulong(unsafe.Sizeof(argmax)) + ) + retval := C.sysctl(&mib[0], 2, unsafe.Pointer(&argmax), &size, C.NULL, 0) + if retval == 0 { + return int(argmax) + } + return 0 +} + +func getTimeScaleToNanoSeconds() float64 { + var timeBaseInfo C.struct_mach_timebase_info + + C.mach_timebase_info(&timeBaseInfo) + + return float64(timeBaseInfo.numer) / float64(timeBaseInfo.denom) +} + +func ExeWithContext(ctx context.Context, pid int32) (string, error) { + var c C.char // need a var for unsafe.Sizeof need a var + const bufsize = C.PROC_PIDPATHINFO_MAXSIZE * unsafe.Sizeof(c) + buffer := (*C.char)(C.malloc(C.size_t(bufsize))) + defer C.free(unsafe.Pointer(buffer)) + + ret, err := C.proc_pidpath(C.int(pid), unsafe.Pointer(buffer), C.uint32_t(bufsize)) + if err != nil { + return "", err + } + if ret <= 0 { + return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret) + } + + return C.GoString(buffer), nil +} + +// CwdWithContext retrieves the Current Working Directory for the given process. +// It uses the proc_pidinfo from libproc and will only work for processes the +// EUID can access. Otherwise "operation not permitted" will be returned as the +// error. +// Note: This might also work for other *BSD OSs. +func CwdWithContext(ctx context.Context, pid int32) (string, error) { + const vpiSize = C.sizeof_struct_proc_vnodepathinfo + vpi := (*C.struct_proc_vnodepathinfo)(C.malloc(vpiSize)) + defer C.free(unsafe.Pointer(vpi)) + ret, err := C.proc_pidinfo(C.int(pid), C.PROC_PIDVNODEPATHINFO, 0, unsafe.Pointer(vpi), vpiSize) + if err != nil { + // fmt.Printf("ret: %d %T\n", ret, err) + if err == syscall.EPERM { + return "", ErrorNotPermitted + } + return "", err + } + if ret <= 0 { + return "", fmt.Errorf("unknown error: proc_pidinfo returned %d", ret) + } + if ret != C.sizeof_struct_proc_vnodepathinfo { + return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret) + } + return C.GoString(&vpi.pvi_cdir.vip_path[0]), err +} + +func procArgs(pid int32) ([]byte, int, error) { + var ( + mib = [...]C.int{C.CTL_KERN, C.KERN_PROCARGS2, C.int(pid)} + size C.size_t = C.ulong(argMax) + nargs C.int + result []byte + ) + procargs := (*C.char)(C.malloc(C.ulong(argMax))) + defer C.free(unsafe.Pointer(procargs)) + retval, err := C.sysctl(&mib[0], 3, unsafe.Pointer(procargs), &size, C.NULL, 0) + if retval == 0 { + C.memcpy(unsafe.Pointer(&nargs), unsafe.Pointer(procargs), C.sizeof_int) + result = C.GoBytes(unsafe.Pointer(procargs), C.int(size)) + // fmt.Printf("size: %d %d\n%s\n", size, nargs, hex.Dump(result)) + return result, int(nargs), nil + } + return nil, 0, err +} + +func cmdlineSliceWithContext(ctx context.Context, pid int32) ([]string, error) { + pargs, nargs, err := procArgs(pid) + if err != nil { + return nil, err + } + // The first bytes hold the nargs int, skip it. + args := bytes.Split((pargs)[C.sizeof_int:], []byte{0}) + var argStr string + // The first element is the actual binary/command path. + // command := args[0] + var argSlice []string + // var envSlice []string + // All other, non-zero elements are arguments. The first "nargs" elements + // are the arguments. Everything else in the slice is then the environment + // of the process. + for _, arg := range args[1:] { + argStr = string(arg[:]) + if len(argStr) > 0 { + if nargs > 0 { + argSlice = append(argSlice, argStr) + nargs-- + continue + } + break + // envSlice = append(envSlice, argStr) + } + } + return argSlice, err +} + +// cmdNameWithContext returns the command name (including spaces) without any arguments +func cmdNameWithContext(ctx context.Context, pid int32) (string, error) { + r, err := cmdlineSliceWithContext(ctx, pid) + if err != nil { + return "", err + } + + if len(r) == 0 { + return "", nil + } + + return r[0], err +} + +func CmdlineWithContext(ctx context.Context, pid int32) (string, error) { + r, err := cmdlineSliceWithContext(ctx, pid) + if err != nil { + return "", err + } + return strings.Join(r, " "), err +} + +func NumThreadsWithContext(ctx context.Context, pid int32) (int32, error) { + const tiSize = C.sizeof_struct_proc_taskinfo + ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) + defer C.free(unsafe.Pointer(ti)) + + _, err := C.proc_pidinfo(C.int(pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) + if err != nil { + return 0, err + } + + return int32(ti.pti_threadnum), nil +} + +func TimesWithContext(ctx context.Context, pid int32) (*TimesStat, error) { + const tiSize = C.sizeof_struct_proc_taskinfo + ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) + defer C.free(unsafe.Pointer(ti)) + + _, err := C.proc_pidinfo(C.int(pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) + if err != nil { + return nil, err + } + + ret := &TimesStat{ + CPU: "cpu", + User: float64(ti.pti_total_user) * timescaleToNanoSeconds / 1e9, + System: float64(ti.pti_total_system) * timescaleToNanoSeconds / 1e9, + } + return ret, nil +} + +func MemoryInfoWithContext(ctx context.Context, pid int32) (*MemoryInfoStat, error) { + const tiSize = C.sizeof_struct_proc_taskinfo + ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) + defer C.free(unsafe.Pointer(ti)) + + _, err := C.proc_pidinfo(C.int(pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) + if err != nil { + return nil, err + } + + ret := &MemoryInfoStat{ + RSS: uint64(ti.pti_resident_size), + VMS: uint64(ti.pti_virtual_size), + Swap: uint64(ti.pti_pageins), + } + return ret, nil +} diff --git a/vql/psutils/process_darwin_nocgo.go b/vql/psutils/process_darwin_nocgo.go new file mode 100644 index 00000000000..c00a8105ee7 --- /dev/null +++ b/vql/psutils/process_darwin_nocgo.go @@ -0,0 +1,32 @@ +//go:build darwin && !cgo +// +build darwin,!cgo + +package psutils + +import ( + "context" +) + +func cmdNameWithContext(ctx context.Context, pid int32) (string, error) { + return "", NotImplementedError +} + +func cmdlineSliceWithContext(ctx context.Context, pid int32) ([]string, error) { + return nil, NotImplementedError +} + +func TimesWithContext(ctx context.Context, pid int32) (*TimesStat, error) { + return nil, NotImplementedError +} + +func ExeWithContext(ctx context.Context, pid int32) (string, error) { + return "", NotImplementedError +} + +func CwdWithContext(ctx context.Context, pid int32) (string, error) { + return "", NotImplementedError +} + +func MemoryInfoWithContext(ctx context.Context, pid int32) (*MemoryInfoStat, error) { + return nil, NotImplementedError +} diff --git a/vql/psutils/process_posix.go b/vql/psutils/process_posix.go new file mode 100644 index 00000000000..ab1f748db40 --- /dev/null +++ b/vql/psutils/process_posix.go @@ -0,0 +1,119 @@ +//go:build linux || freebsd +// +build linux freebsd + +// This file is for operating systems with /proc + +package psutils + +import ( + "context" + + "github.com/Velocidex/ordereddict" + "github.com/shirou/gopsutil/v3/process" +) + +func GetProcess(ctx context.Context, pid int32) (*ordereddict.Dict, error) { + process_obj, err := process.NewProcessWithContext(ctx, pid) + if err != nil { + return nil, err + } + + return getProcessData(process_obj), nil +} + +func ListProcesses(ctx context.Context) ([]*ordereddict.Dict, error) { + result := []*ordereddict.Dict{} + processes, err := process.Processes() + if err != nil { + return nil, err + } + + for _, item := range processes { + result = append(result, getProcessData(item)) + } + + return result, nil +} + +// Only get a few fields from the process object otherwise we will +// spend too much time calling into virtual methods. +func getProcessData(process *process.Process) *ordereddict.Dict { + result := ordereddict.NewDict().SetCaseInsensitive(). + Set("Pid", process.Pid) + + name, _ := process.Name() + result.Set("Name", name) + + ppid, _ := process.Ppid() + result.Set("Ppid", ppid) + + // Make it compatible with the Windows pslist() + cmdline, _ := process.Cmdline() + result.Set("CommandLine", cmdline) + + create_time, _ := process.CreateTime() + result.Set("CreateTime", create_time) + + times, _ := process.Times() + result.Set("Times", times) + + exe, _ := process.Exe() + result.Set("Exe", exe) + + cwd, _ := process.Cwd() + result.Set("Cwd", cwd) + + user, _ := process.Username() + result.Set("Username", user) + + memory_info, _ := process.MemoryInfo() + result.Set("MemoryInfo", memory_info) + + return result +} + +func MemoryInfoWithContext(ctx context.Context, pid int32) (*MemoryInfoStat, error) { + delegate := &process.Process{Pid: pid} + mem, err := delegate.MemoryInfoWithContext(ctx) + if err != nil { + return nil, err + } + + ret := &MemoryInfoStat{ + RSS: mem.RSS, + VMS: mem.VMS, + Swap: mem.Swap, + } + + return ret, nil +} + +func TimesWithContext(ctx context.Context, pid int32) (*TimesStat, error) { + delegate := &process.Process{Pid: pid} + times, err := delegate.TimesWithContext(ctx) + if err != nil { + return nil, err + } + + return &TimesStat{ + CPU: times.CPU, + User: times.User, + System: times.System, + }, nil +} + +func IOCountersWithContext(ctx context.Context, pid int32) (*IOCountersStat, error) { + delegate := &process.Process{Pid: pid} + counters, err := delegate.IOCountersWithContext(ctx) + + if err != nil { + return nil, err + } + + return &IOCountersStat{ + ReadCount: counters.ReadCount, + WriteCount: counters.WriteCount, + ReadBytes: counters.ReadBytes, + WriteBytes: counters.WriteBytes, + }, nil +} diff --git a/vql/psutils/process_windows.go b/vql/psutils/process_windows.go new file mode 100644 index 00000000000..c21ac79a8d1 --- /dev/null +++ b/vql/psutils/process_windows.go @@ -0,0 +1,153 @@ +//go:build windows +// +build windows + +package psutils + +import ( + "context" + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + processQueryInformation = windows.PROCESS_QUERY_LIMITED_INFORMATION +) + +var ( + Modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + procGetProcessIoCounters = Modkernel32.NewProc("GetProcessIoCounters") + + modpsapi = windows.NewLazySystemDLL("psapi.dll") + procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") +) + +func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { + if pid == 0 { // special case for pid 0 System Idle Process + return true, nil + } + + if pid < 0 { + return false, fmt.Errorf("invalid pid %v", pid) + } + + if pid%4 != 0 { + // OpenProcess will succeed even on non-existing pid here https://devblogs.microsoft.com/oldnewthing/20080606-00/?p=22043 + // Valid pids are multiple of 4 so we can reject these immediately. + return false, nil + } + + h, err := windows.OpenProcess(windows.SYNCHRONIZE, false, uint32(pid)) + if err == windows.ERROR_ACCESS_DENIED { + return true, nil + } + + if err == windows.ERROR_INVALID_PARAMETER { + return false, nil + } + + if err != nil { + return false, err + } + + defer windows.CloseHandle(h) + event, err := windows.WaitForSingleObject(h, 0) + return event == uint32(windows.WAIT_TIMEOUT), err +} + +func TimesWithContext(ctx context.Context, pid int32) (*TimesStat, error) { + var times struct { + CreateTime syscall.Filetime + ExitTime syscall.Filetime + KernelTime syscall.Filetime + UserTime syscall.Filetime + } + + h, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) + if err != nil { + return nil, err + } + defer windows.CloseHandle(h) + + err = syscall.GetProcessTimes( + syscall.Handle(h), + ×.CreateTime, + ×.ExitTime, + ×.KernelTime, + ×.UserTime, + ) + + user := float64(times.UserTime.HighDateTime)*429.4967296 + float64(times.UserTime.LowDateTime)*1e-7 + kernel := float64(times.KernelTime.HighDateTime)*429.4967296 + float64(times.KernelTime.LowDateTime)*1e-7 + + return &TimesStat{ + User: user, + System: kernel, + }, nil +} + +func getProcessMemoryInfo(h windows.Handle, mem *PROCESS_MEMORY_COUNTERS) (err error) { + r1, _, e1 := syscall.Syscall(procGetProcessMemoryInfo.Addr(), 3, uintptr(h), uintptr(unsafe.Pointer(mem)), uintptr(unsafe.Sizeof(*mem))) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func MemoryInfoWithContext(ctx context.Context, pid int32) (*MemoryInfoStat, error) { + var mem PROCESS_MEMORY_COUNTERS + + c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) + if err != nil { + return nil, err + } + defer windows.CloseHandle(c) + if err := getProcessMemoryInfo(c, &mem); err != nil { + return nil, err + } + + ret := &MemoryInfoStat{ + RSS: uint64(mem.WorkingSetSize), + VMS: uint64(mem.PagefileUsage), + } + + return ret, nil +} + +func IOCountersWithContext(ctx context.Context, pid int32) (*IOCountersStat, error) { + // ioCounters is an equivalent representation of IO_COUNTERS in the Windows API. + // https://docs.microsoft.com/windows/win32/api/winnt/ns-winnt-io_counters + var ioCounters struct { + ReadOperationCount uint64 + WriteOperationCount uint64 + OtherOperationCount uint64 + ReadTransferCount uint64 + WriteTransferCount uint64 + OtherTransferCount uint64 + } + + c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) + if err != nil { + return nil, err + } + defer windows.CloseHandle(c) + + ret, _, err := procGetProcessIoCounters.Call(uintptr(c), uintptr(unsafe.Pointer(&ioCounters))) + if ret == 0 { + return nil, err + } + stats := &IOCountersStat{ + ReadCount: ioCounters.ReadOperationCount, + ReadBytes: ioCounters.ReadTransferCount, + WriteCount: ioCounters.WriteOperationCount, + WriteBytes: ioCounters.WriteTransferCount, + } + + return stats, nil +} diff --git a/vql/psutils/process_windows_amd64.go b/vql/psutils/process_windows_amd64.go new file mode 100644 index 00000000000..5bd752fe537 --- /dev/null +++ b/vql/psutils/process_windows_amd64.go @@ -0,0 +1,17 @@ +//go:build (windows && amd64) || (windows && arm64) +// +build windows,amd64 windows,arm64 + +package psutils + +type PROCESS_MEMORY_COUNTERS struct { + CB uint32 + PageFaultCount uint32 + PeakWorkingSetSize uint64 + WorkingSetSize uint64 + QuotaPeakPagedPoolUsage uint64 + QuotaPagedPoolUsage uint64 + QuotaPeakNonPagedPoolUsage uint64 + QuotaNonPagedPoolUsage uint64 + PagefileUsage uint64 + PeakPagefileUsage uint64 +} diff --git a/vql/psutils/process_windows_i386.go b/vql/psutils/process_windows_i386.go new file mode 100644 index 00000000000..c75c41013fa --- /dev/null +++ b/vql/psutils/process_windows_i386.go @@ -0,0 +1,17 @@ +//go:build (windows && 386) || (windows && arm) +// +build windows,386 windows,arm + +package psutils + +type PROCESS_MEMORY_COUNTERS struct { + CB uint32 + PageFaultCount uint32 + PeakWorkingSetSize uint32 + WorkingSetSize uint32 + QuotaPeakPagedPoolUsage uint32 + QuotaPagedPoolUsage uint32 + QuotaPeakNonPagedPoolUsage uint32 + QuotaNonPagedPoolUsage uint32 + PagefileUsage uint32 + PeakPagefileUsage uint32 +} diff --git a/vql/tools/collector/collector_manager.go b/vql/tools/collector/collector_manager.go index e9d45e5c48e..accda7a6fea 100644 --- a/vql/tools/collector/collector_manager.go +++ b/vql/tools/collector/collector_manager.go @@ -7,7 +7,6 @@ import ( "time" "github.com/Velocidex/ordereddict" - "github.com/shirou/gopsutil/v3/host" "www.velocidex.com/golang/velociraptor/actions" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" @@ -27,6 +26,7 @@ import ( "www.velocidex.com/golang/velociraptor/utils" "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/velociraptor/vql/acl_managers" + "www.velocidex.com/golang/velociraptor/vql/psutils" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/types" ) @@ -200,7 +200,7 @@ func (self *collectionManager) storeHostInfo() error { version := config.GetVersion() var info_dict *ordereddict.Dict - host_info, err := host.Info() + host_info, err := psutils.InfoWithContext(self.ctx) if err != nil { info_dict = ordereddict.NewDict() } else {