Skip to content

Commit

Permalink
lowered access on winapi syscalls
Browse files Browse the repository at this point in the history
  • Loading branch information
shalper2 committed Nov 12, 2024
1 parent b9e2ffc commit 3a0af4c
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 42 deletions.
13 changes: 6 additions & 7 deletions receiver/windowsservicereceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,25 @@ The Windows Service Receiver is a receiver for scraping information about servic

## Getting Started

By default the Windows Service Receiver will attempt to identify and monitor the status of all specified services on the host machine.
This behavior can be changed by a user setting the `monitor_all` field to true which will tell the receiver to attempt to build a list
and monitor the status of all services present on the host machine. The collection interval for the receiver may also be modified by
specifying a value. Default collection interval is every one minute.
By default the Windows Service Receiver will attempt to identify and monitor the status of all specified services on the host machine. If the `include_services` field is present but empty the receiver will assume that the user wishes to monitor _ALL_ services present on a machine. Should you wish to monitor a majority of services (but not all) an optional `exclude_services` services field may be included with a list of services you do not wish to see metrics from.

An example of monitoring three services on a host:
```yaml
windowsservice:
collection_interval: <duration> # default = 1m
services:
include_services:
- service1
- service2
- service3
...
```
The case where you wish to monitor all services present on a host machine:
The case where you wish to monitor all services present on a host machine, except for `service3`:
```yaml
windowsservice:
collection_interval: <duration> # default = 1m
monitor_all: true
include_services:
exclude_services:
- service3
...
```

Expand Down
4 changes: 2 additions & 2 deletions receiver/windowsservicereceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
// Config defines configuration for windowsservice receiver.
type Config struct {
scraperhelper.ControllerConfig `mapstructure:",squash"`
Services []string `mapstructure:"services"` // user provided list of services to monitor with receiver
MonitorAll bool `mapstructure:"monitor_all"` // monitor all services on host machine. supercedes services
IncludeServices []string `mapstructure:"include_services"` // user provided list of services to monitor with receiver
ExcludeServices []string `mapstructure:"exclude_services"` // user provided list of services to be excluded
}

// Validate checks the receiver configuration is valid
Expand Down
2 changes: 1 addition & 1 deletion receiver/windowsservicereceiver/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/windowsservicereceiver

go 1.23.1
go 1.22.0

require (
github.com/google/go-cmp v0.6.0
Expand Down
51 changes: 51 additions & 0 deletions receiver/windowsservicereceiver/winapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows

package windowsservicereceiver

import (
"syscall"

"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/mgr"
)

type Manager struct {
Handle windows.Handle
}

func SCConnect() (*Manager, error) {
var s *uint16

h, err := windows.OpenSCManager(s, nil, windows.GENERIC_READ)
if err != nil {
return nil, err
}

if err != nil {
return nil, err
}

return &Manager{
Handle: h,
}, nil
}

func (m *Manager) Disconnect() error {
return windows.CloseServiceHandle(m.Handle)
}

func (m *Manager) OpenService(sName string) (*mgr.Service, error) {
ptr, err := syscall.UTF16PtrFromString(sName)
if err != nil {
return nil, err
}

h, err := windows.OpenService(m.Handle, ptr, windows.GENERIC_READ)
if err != nil {
return nil, err
}

return &mgr.Service{Name: sName, Handle: h}, nil
}
43 changes: 11 additions & 32 deletions receiver/windowsservicereceiver/windowsservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,29 @@
package windowsservicereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/windowsservicereceiver"

import (
"unsafe"

"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/mgr"
)

// represents the service struct before we have converted it into a metric
type Service struct {
type ServiceStatus struct {
ServiceStatus uint32
StartType uint32
Name string
service *mgr.Service
}

func GetService(sname string) (*Service, error) {
manager, err := mgr.Connect()
defer manager.Disconnect()
func GetService(sname string) (*ServiceStatus, error) {
m, err := SCConnect()
defer m.Disconnect()
if err != nil {
return nil, err
}

service, err := manager.OpenService(sname)
service, err := m.OpenService(sname)
if err != nil {
return nil, err
}

s := Service{
Name: sname,
s := ServiceStatus{
service: service,
}

Expand All @@ -49,33 +44,17 @@ func GetService(sname string) (*Service, error) {
}

// populates fields from service status query
//
// docs for the functions used are found here
// https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-queryservicestatusex
func (s *Service) getStatus() error {
// we need to do this in order to get the size of the buffer we need to actually read the status
var bytesNeeded uint32

if err := windows.QueryServiceStatusEx(s.service.Handle, windows.SC_STATUS_PROCESS_INFO, nil, 0, &bytesNeeded); err != windows.ERROR_INSUFFICIENT_BUFFER {
return err
}

// allocate our buffer with this fun little trick
buf := make([]byte, bytesNeeded) // make an empty buffer of the required size
// remember: unsafe.Pointer() is basically like void * in C, so this is probably pretty sketchy.
lp := (*windows.SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0]))

// the second time around we know our required buffer (bytesRemaining is set to [bytes needed to read status] - [bytes of buffer supplied])
if err := windows.QueryServiceStatusEx(s.service.Handle, windows.SC_STATUS_PROCESS_INFO, &buf[0], uint32(len(buf)), &bytesNeeded); err != nil {
func (s *ServiceStatus) getStatus() error {
st, err := s.service.Query()
if err != nil {
return err
}

s.ServiceStatus = lp.CurrentState
s.ServiceStatus = uint32(st.State)
return nil
}

// popualtes fields from service config query
func (s *Service) getConfig() error {
func (s *ServiceStatus) getConfig() error {
c, err := s.service.Config()
if err != nil {
return err
Expand Down

0 comments on commit 3a0af4c

Please sign in to comment.