diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index b9d8976c404..390c2ece7d0 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -67,6 +67,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix formatting for `event.duration`, "human readable" was not working well for this. {pull}11675[11675] - Fix initialization of the TCP input logger. {pull}11605[11605] - Fix flaky service_integration_windows_test test by introducing a confidence factor and enriching the error message with more service details. {issue}8880[8880] and {issue}7977[7977] +- Replace wmi queries with win32 api calls as they were consuming CPU resources {issue}3249[3249] and {issue}11840[11840] *Auditbeat* diff --git a/NOTICE.txt b/NOTICE.txt index c173e8b7ab2..de395baa9df 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -765,8 +765,8 @@ Elasticsearch, B.V. (https://www.elastic.co/). -------------------------------------------------------------------- Dependency: github.com/elastic/gosigar -Version: v0.10.0 -Revision: f2a90fc413720c43da9c4fe1a47513c73f45ac3d +Version: v0.10.1 +Revision: fc57ef8c6efc0b4fdc6d7c623173073a6d3d4736 License type (autodetected): Apache-2.0 ./vendor/github.com/elastic/gosigar/LICENSE: -------------------------------------------------------------------- diff --git a/vendor/github.com/elastic/gosigar/CHANGELOG.md b/vendor/github.com/elastic/gosigar/CHANGELOG.md index 127046ceeda..cfa804c9205 100644 --- a/vendor/github.com/elastic/gosigar/CHANGELOG.md +++ b/vendor/github.com/elastic/gosigar/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.10.1] + +### Fixed +- Replaced the WMI queries with win32 apis due to high CPU usage. #11840 + ## [0.10.0] ### Added diff --git a/vendor/github.com/elastic/gosigar/sigar_windows.go b/vendor/github.com/elastic/gosigar/sigar_windows.go index 6da30808a37..66f294e9c14 100644 --- a/vendor/github.com/elastic/gosigar/sigar_windows.go +++ b/vendor/github.com/elastic/gosigar/sigar_windows.go @@ -12,26 +12,10 @@ import ( "syscall" "time" - "github.com/StackExchange/wmi" "github.com/elastic/gosigar/sys/windows" "github.com/pkg/errors" ) -// Win32_Process represents a process on the Windows operating system. If -// additional fields are added here (that match the Windows struct) they will -// automatically be populated when calling getWin32Process. -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa394372(v=vs.85).aspx -type Win32_Process struct { - CommandLine *string -} - -// Win32_OperatingSystem WMI class represents a Windows-based operating system -// installed on a computer. -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa394239(v=vs.85).aspx -type Win32_OperatingSystem struct { - LastBootUpTime time.Time -} - var ( // version is Windows version of the host OS. version = windows.GetWindowsVersion() @@ -83,11 +67,12 @@ func (self *Uptime) Get() error { bootTimeLock.Lock() defer bootTimeLock.Unlock() if bootTime == nil { - os, err := getWin32OperatingSystem() + uptime, err := windows.GetTickCount64() if err != nil { - return errors.Wrap(err, "failed to get boot time using WMI") + return errors.Wrap(err, "failed to get boot time using win32 api") } - bootTime = &os.LastBootUpTime + var boot = time.Unix(int64(uptime), 0) + bootTime = &boot } self.Length = time.Since(*bootTime).Seconds() @@ -251,7 +236,7 @@ func getProcStatus(pid int) (RunState, error) { var exitCode uint32 err = syscall.GetExitCodeProcess(handle, &exitCode) if err != nil { - return RunStateUnknown, errors.Wrapf(err, "GetExitCodeProcess failed for pid=%v") + return RunStateUnknown, errors.Wrapf(err, "GetExitCodeProcess failed for pid=%v", pid) } if exitCode == 259 { //still active @@ -371,15 +356,28 @@ func (self *ProcArgs) Get(pid int) error { if !version.IsWindowsVistaOrGreater() { return ErrNotImplemented{runtime.GOOS} } - - process, err := getWin32Process(int32(pid)) + handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess|windows.PROCESS_VM_READ, false, uint32(pid)) + if err != nil { + return errors.Wrapf(err, "OpenProcess failed for pid=%v", pid) + } + defer syscall.CloseHandle(handle) + pbi, err := windows.NtQueryProcessBasicInformation(handle) if err != nil { - return errors.Wrapf(err, "ProcArgs failed for pid=%v", pid) + return errors.Wrapf(err, "NtQueryProcessBasicInformation failed for pid=%v", pid) } - if process.CommandLine != nil { - self.List = []string{*process.CommandLine} + if err != nil { + return nil + } + userProcParams, err := windows.GetUserProcessParams(handle, pbi) + if err != nil { + return nil + } + if argsW, err := windows.ReadProcessUnicodeString(handle, &userProcParams.CommandLine); err == nil { + self.List, err = windows.ByteSliceToStringSlice(argsW) + if err != nil { + return err + } } - return nil } @@ -396,35 +394,6 @@ func (self *FileSystemUsage) Get(path string) error { return nil } -// getWin32Process gets information about the process with the given process ID. -// It uses a WMI query to get the information from the local system. -func getWin32Process(pid int32) (Win32_Process, error) { - var dst []Win32_Process - query := fmt.Sprintf("WHERE ProcessId = %d", pid) - q := wmi.CreateQuery(&dst, query) - err := wmi.Query(q, &dst) - if err != nil { - return Win32_Process{}, fmt.Errorf("could not get Win32_Process %s: %v", query, err) - } - if len(dst) < 1 { - return Win32_Process{}, fmt.Errorf("could not get Win32_Process %s: Process not found", query) - } - return dst[0], nil -} - -func getWin32OperatingSystem() (Win32_OperatingSystem, error) { - var dst []Win32_OperatingSystem - q := wmi.CreateQuery(&dst, "") - err := wmi.Query(q, &dst) - if err != nil { - return Win32_OperatingSystem{}, errors.Wrap(err, "wmi query for Win32_OperatingSystem failed") - } - if len(dst) != 1 { - return Win32_OperatingSystem{}, errors.New("wmi query for Win32_OperatingSystem failed") - } - return dst[0], nil -} - func (self *Rusage) Get(who int) error { if who != 0 { return ErrNotImplemented{runtime.GOOS} diff --git a/vendor/github.com/elastic/gosigar/sys/windows/syscall_windows.go b/vendor/github.com/elastic/gosigar/sys/windows/syscall_windows.go index 36be45b306e..655836701f2 100644 --- a/vendor/github.com/elastic/gosigar/sys/windows/syscall_windows.go +++ b/vendor/github.com/elastic/gosigar/sys/windows/syscall_windows.go @@ -23,6 +23,10 @@ const ( PROCESS_VM_READ uint32 = 0x0010 ) +// SizeOfRtlUserProcessParameters gives the size +// of the RtlUserProcessParameters struct. +const SizeOfRtlUserProcessParameters = unsafe.Sizeof(RtlUserProcessParameters{}) + // MAX_PATH is the maximum length for a path in Windows. // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx const MAX_PATH = 260 @@ -43,6 +47,26 @@ const ( DRIVE_RAMDISK ) +// UnicodeString is Go's equivalent for the _UNICODE_STRING struct. +type UnicodeString struct { + Size uint16 + MaximumLength uint16 + Buffer uintptr +} + +// RtlUserProcessParameters is Go's equivalent for the +// _RTL_USER_PROCESS_PARAMETERS struct. +// A few undocumented fields are exposed. +type RtlUserProcessParameters struct { + Reserved1 [16]byte + Reserved2 [5]uintptr + CurrentDirectoryPath UnicodeString + CurrentDirectoryHandle uintptr + DllPath UnicodeString + ImagePathName UnicodeString + CommandLine UnicodeString +} + func (dt DriveType) String() string { names := map[DriveType]string{ DRIVE_UNKNOWN: "unknown", @@ -441,6 +465,95 @@ func UTF16SliceToStringSlice(buffer []uint16) []string { return result } +func GetUserProcessParams(handle syscall.Handle, pbi ProcessBasicInformation) (params RtlUserProcessParameters, err error) { + const is32bitProc = unsafe.Sizeof(uintptr(0)) == 4 + + // Offset of params field within PEB structure. + // This structure is different in 32 and 64 bit. + paramsOffset := 0x20 + if is32bitProc { + paramsOffset = 0x10 + } + + // Read the PEB from the target process memory + pebSize := paramsOffset + 8 + peb := make([]byte, pebSize) + nRead, err := ReadProcessMemory(handle, pbi.PebBaseAddress, peb) + if err != nil { + return params, err + } + if nRead != uintptr(pebSize) { + return params, errors.Errorf("PEB: short read (%d/%d)", nRead, pebSize) + } + + // Get the RTL_USER_PROCESS_PARAMETERS struct pointer from the PEB + paramsAddr := *(*uintptr)(unsafe.Pointer(&peb[paramsOffset])) + + // Read the RTL_USER_PROCESS_PARAMETERS from the target process memory + paramsBuf := make([]byte, SizeOfRtlUserProcessParameters) + nRead, err = ReadProcessMemory(handle, paramsAddr, paramsBuf) + if err != nil { + return params, err + } + if nRead != uintptr(SizeOfRtlUserProcessParameters) { + return params, errors.Errorf("RTL_USER_PROCESS_PARAMETERS: short read (%d/%d)", nRead, SizeOfRtlUserProcessParameters) + } + + params = *(*RtlUserProcessParameters)(unsafe.Pointer(¶msBuf[0])) + return params, nil +} + +func ReadProcessUnicodeString(handle syscall.Handle, s *UnicodeString) ([]byte, error) { + buf := make([]byte, s.Size) + nRead, err := ReadProcessMemory(handle, s.Buffer, buf) + if err != nil { + return nil, err + } + if nRead != uintptr(s.Size) { + return nil, errors.Errorf("unicode string: short read: (%d/%d)", nRead, s.Size) + } + return buf, nil +} + +// Use Windows' CommandLineToArgv API to split an UTF-16 command line string +// into a list of parameters. +func ByteSliceToStringSlice(utf16 []byte) ([]string, error) { + if len(utf16) == 0 { + return nil, nil + } + var numArgs int32 + argsWide, err := syscall.CommandLineToArgv((*uint16)(unsafe.Pointer(&utf16[0])), &numArgs) + if err != nil { + return nil, err + } + args := make([]string, numArgs) + for idx := range args { + args[idx] = syscall.UTF16ToString(argsWide[idx][:]) + } + return args, nil +} + +// ReadProcessMemory reads from another process memory. The Handle needs to have +// the PROCESS_VM_READ right. +// A zero-byte read is a no-op, no error is returned. +func ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, dest []byte) (numRead uintptr, err error) { + n := len(dest) + if n == 0 { + return 0, nil + } + if err = _ReadProcessMemory(handle, baseAddress, uintptr(unsafe.Pointer(&dest[0])), uintptr(n), &numRead); err != nil { + return 0, err + } + return numRead, nil +} + +func GetTickCount64() (uptime uint64, err error) { + if uptime, err = _GetTickCount64(); err != nil { + return 0, err + } + return uptime, nil +} + // Use "GOOS=windows go generate -v -x ." to generate the source. // Add -trace to enable debug prints around syscalls. @@ -467,3 +580,5 @@ func UTF16SliceToStringSlice(buffer []uint16) []string { //sys _FindNextVolume(handle syscall.Handle, volumeName *uint16, size uint32) (err error) = kernel32.FindNextVolumeW //sys _FindVolumeClose(handle syscall.Handle) (err error) = kernel32.FindVolumeClose //sys _GetVolumePathNamesForVolumeName(volumeName string, buffer *uint16, bufferSize uint32, length *uint32) (err error) = kernel32.GetVolumePathNamesForVolumeNameW +//sys _ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, buffer uintptr, size uintptr, numRead *uintptr) (err error) = kernel32.ReadProcessMemory +//sys _GetTickCount64() (uptime uint64, err error) = kernel32.GetTickCount64 diff --git a/vendor/github.com/elastic/gosigar/sys/windows/zsyscall_windows.go b/vendor/github.com/elastic/gosigar/sys/windows/zsyscall_windows.go index 8da079aba36..cd5d9ca32ee 100644 --- a/vendor/github.com/elastic/gosigar/sys/windows/zsyscall_windows.go +++ b/vendor/github.com/elastic/gosigar/sys/windows/zsyscall_windows.go @@ -1,4 +1,4 @@ -// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT +// Code generated by 'go generate'; DO NOT EDIT. package windows @@ -60,6 +60,8 @@ var ( procFindNextVolumeW = modkernel32.NewProc("FindNextVolumeW") procFindVolumeClose = modkernel32.NewProc("FindVolumeClose") procGetVolumePathNamesForVolumeNameW = modkernel32.NewProc("GetVolumePathNamesForVolumeNameW") + procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") + procGetTickCount64 = modkernel32.NewProc("GetTickCount64") ) func _GlobalMemoryStatusEx(buffer *MemoryStatusEx) (err error) { @@ -347,3 +349,28 @@ func __GetVolumePathNamesForVolumeName(volumeName *uint16, buffer *uint16, buffe } return } + +func _ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, buffer uintptr, size uintptr, numRead *uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procReadProcessMemory.Addr(), 5, uintptr(handle), uintptr(baseAddress), uintptr(buffer), uintptr(size), uintptr(unsafe.Pointer(numRead)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _GetTickCount64() (uptime uint64, err error) { + r0, _, e1 := syscall.Syscall(procGetTickCount64.Addr(), 0, 0, 0, 0) + uptime = uint64(r0) + if uptime == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 8b70b52fec1..e2b411c919b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -952,7 +952,7 @@ }, { "path": "github.com/elastic/go-seccomp-bpf/cmd/seccomp-profile", - "revision": "", + "revision": "v1.1.0", "tree": true, "version": "v1.1.0", "versionExact": "v1.1.0" @@ -1246,44 +1246,44 @@ "revisionTime": "2018-08-31T13:10:45Z" }, { - "checksumSHA1": "ux0d5xvItes2abrOlpEyQWeJeqM=", + "checksumSHA1": "SyDYkg4fXTa27fsNrjnEQBKoDEQ=", "path": "github.com/elastic/gosigar", - "revision": "f2a90fc413720c43da9c4fe1a47513c73f45ac3d", - "revisionTime": "2019-01-18T12:09:37Z", - "version": "v0.10.0", - "versionExact": "v0.10.0" + "revision": "fc57ef8c6efc0b4fdc6d7c623173073a6d3d4736", + "revisionTime": "2019-04-24T11:51:21Z", + "version": "v0.10.1", + "versionExact": "v0.10.1" }, { - "checksumSHA1": "TX9y4oPL5YmT4Gb/OU4GIPTdQB4=", + "checksumSHA1": "y4HBfgWAm5cwRN3Mn7S3Bn1/pDc=", "path": "github.com/elastic/gosigar/cgroup", - "revision": "f2a90fc413720c43da9c4fe1a47513c73f45ac3d", - "revisionTime": "2019-01-18T12:09:37Z", - "version": "v0.10.0", - "versionExact": "v0.10.0" + "revision": "fc57ef8c6efc0b4fdc6d7c623173073a6d3d4736", + "revisionTime": "2019-04-24T11:51:21Z", + "version": "v0.10.1", + "versionExact": "v0.10.1" }, { - "checksumSHA1": "hPqGM3DENaGfipEODoyZ4mKogTQ=", + "checksumSHA1": "axEkf+eo2z5V+R0nvgr1z+rty4w=", "path": "github.com/elastic/gosigar/sys", - "revision": "f2a90fc413720c43da9c4fe1a47513c73f45ac3d", - "revisionTime": "2019-01-18T12:09:37Z", - "version": "v0.10.0", - "versionExact": "v0.10.0" + "revision": "fc57ef8c6efc0b4fdc6d7c623173073a6d3d4736", + "revisionTime": "2019-04-24T11:51:21Z", + "version": "v0.10.1", + "versionExact": "v0.10.1" }, { - "checksumSHA1": "mLq5lOyD0ZU39ysXuf1ETOLJ+f0=", + "checksumSHA1": "BtggD07yUvVppRyX4HTrRi6sqnQ=", "path": "github.com/elastic/gosigar/sys/linux", - "revision": "f2a90fc413720c43da9c4fe1a47513c73f45ac3d", - "revisionTime": "2019-01-18T12:09:37Z", - "version": "v0.10.0", - "versionExact": "v0.10.0" + "revision": "fc57ef8c6efc0b4fdc6d7c623173073a6d3d4736", + "revisionTime": "2019-04-24T11:51:21Z", + "version": "v0.10.1", + "versionExact": "v0.10.1" }, { - "checksumSHA1": "Xx3lvjjqPwlFQ2aBjxtXSeDmD4c=", + "checksumSHA1": "CbP36vKSgkQruWF8PODFN9+SANI=", "path": "github.com/elastic/gosigar/sys/windows", - "revision": "f2a90fc413720c43da9c4fe1a47513c73f45ac3d", - "revisionTime": "2019-01-18T12:09:37Z", - "version": "v0.10.0", - "versionExact": "v0.10.0" + "revision": "fc57ef8c6efc0b4fdc6d7c623173073a6d3d4736", + "revisionTime": "2019-04-24T11:51:21Z", + "version": "v0.10.1", + "versionExact": "v0.10.1" }, { "checksumSHA1": "Klc34HULvwvY4cGA/D8HmqtXLqw=",