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 {