pdh: add slack to counter array buffers for Windows Server 2012 R2 (#2615)

Signed-off-by: Alexandre Possebom <alexandrepossebom@gmail.com>
This commit is contained in:
Alexandre Possebom
2026-10-09 15:12:27 +02:00
committed by GitHub
parent be65ed6890
commit cd9242c4c7
+30 -16
View File
@@ -289,6 +289,13 @@ func (c *Collector) Collect(dst any) error {
return <-c.errorCh
}
// pdhBufferSlack is extra space allocated beyond the buffer size reported by PDH.
// On Windows Server 2012 R2, PdhGetRawCounterArrayW reports the size of the item array of a single-instance
// object (e.g. System) without the terminator of the empty instance name, and writes that terminator right after
// the array: 48 bytes reported, 2 bytes written past them. Without the slack, the write corrupts the neighboring
// heap object and SzName points into it, which the GC reports as "found pointer to free object".
const pdhBufferSlack = 8
func (c *Collector) collectWorkerRaw() {
var (
err error
@@ -297,7 +304,10 @@ func (c *Collector) collectWorkerRaw() {
bytesNeeded uint32
)
buf := make([]byte, 1)
// buf starts empty so that the first call only queries the required size with a nil buffer.
// PdhGetRawCounterArrayW writes 8 bytes into a non-nil buffer, even if lpdwBufferSize is smaller,
// which corrupts the neighboring heap memory of a tiny allocation.
var buf []byte
for data := range c.collectCh {
err = (func() error {
@@ -334,12 +344,13 @@ func (c *Collector) collectWorkerRaw() {
stringMap := map[*uint16]string{}
for _, counter := range c.counters {
instances:
for _, instance := range counter.Instances {
// Get the info with the current buffer size
bytesNeeded = uint32(cap(buf))
bytesNeeded = uint32(len(buf))
for {
ret := GetRawCounterArray(instance, &bytesNeeded, &itemCount, &buf[0])
ret := GetRawCounterArray(instance, &bytesNeeded, &itemCount, unsafe.SliceData(buf))
if ret == ErrorSuccess {
break
@@ -347,20 +358,20 @@ func (c *Collector) collectWorkerRaw() {
if err := NewPdhError(ret); ret != MoreData {
if isKnownCounterDataError(err) {
break
continue instances
}
return fmt.Errorf("GetRawCounterArray: %w", err)
}
if bytesNeeded <= uint32(cap(buf)) {
return fmt.Errorf("GetRawCounterArray reports buffer too small (%d), but buffer is large enough (%d): %w", uint32(cap(buf)), bytesNeeded, NewPdhError(ret))
if bytesNeeded <= uint32(len(buf)) {
return fmt.Errorf("GetRawCounterArray reports buffer too small (%d), but buffer is large enough (%d): %w", uint32(len(buf)), bytesNeeded, NewPdhError(ret))
}
buf = make([]byte, bytesNeeded)
buf = make([]byte, bytesNeeded+pdhBufferSlack)
}
items = unsafe.Slice((*RawCounterItem)(unsafe.Pointer(&buf[0])), itemCount)
items = unsafe.Slice((*RawCounterItem)(unsafe.Pointer(unsafe.SliceData(buf))), itemCount)
var (
instanceName string
@@ -458,7 +469,9 @@ func (c *Collector) collectWorkerFormatted() {
bytesNeeded uint32
)
buf := make([]byte, 1)
// buf starts empty so that the first call only queries the required size with a nil buffer.
// See collectWorkerRaw.
var buf []byte
for data := range c.collectCh {
err = (func() error {
@@ -495,12 +508,13 @@ func (c *Collector) collectWorkerFormatted() {
stringMap := map[*uint16]string{}
for _, counter := range c.counters {
instances:
for _, instance := range counter.Instances {
// Get the info with the current buffer size
bytesNeeded = uint32(cap(buf))
bytesNeeded = uint32(len(buf))
for {
ret := GetFormattedCounterArrayDouble(instance, &bytesNeeded, &itemCount, &buf[0])
ret := GetFormattedCounterArrayDouble(instance, &bytesNeeded, &itemCount, unsafe.SliceData(buf))
if ret == ErrorSuccess {
break
@@ -508,20 +522,20 @@ func (c *Collector) collectWorkerFormatted() {
if err := NewPdhError(ret); ret != MoreData {
if isKnownCounterDataError(err) {
break
continue instances
}
return fmt.Errorf("GetFormattedCounterArrayDouble: %w", err)
}
if bytesNeeded <= uint32(cap(buf)) {
return fmt.Errorf("GetFormattedCounterArrayDouble reports buffer too small (%d), but buffer is large enough (%d): %w", uint32(cap(buf)), bytesNeeded, NewPdhError(ret))
if bytesNeeded <= uint32(len(buf)) {
return fmt.Errorf("GetFormattedCounterArrayDouble reports buffer too small (%d), but buffer is large enough (%d): %w", uint32(len(buf)), bytesNeeded, NewPdhError(ret))
}
buf = make([]byte, bytesNeeded)
buf = make([]byte, bytesNeeded+pdhBufferSlack)
}
items = unsafe.Slice((*FmtCounterValueItemDouble)(unsafe.Pointer(&buf[0])), itemCount)
items = unsafe.Slice((*FmtCounterValueItemDouble)(unsafe.Pointer(unsafe.SliceData(buf))), itemCount)
var (
instanceName string