diff --git a/internal/windows/api/pdh.go b/internal/windows/api/pdh.go index 015444739..cd2081b28 100644 --- a/internal/windows/api/pdh.go +++ b/internal/windows/api/pdh.go @@ -476,15 +476,20 @@ func PdhGetRawCounterArray(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBu return uint32(ret) } -// UTF16PtrToString converts a UTF16 pointer to a Go string -func UTF16PtrToString(ptr *uint16) string { - if ptr == nil { +// UTF16PtrToString converts a UTF16 pointer to a Go string, reading at most maxLen uint16s. +// It stops at the first null terminator or when maxLen is reached. +func UTF16PtrToString(ptr *uint16, maxLen uint32) string { + if ptr == nil || maxLen == 0 { return "" } - // Find the length of the null-terminated wide string + // Find the length of the null-terminated wide string, up to maxLen length := 0 - for p := ptr; *p != 0; { + p := ptr + for i := uint32(0); i < maxLen; i++ { + if *p == 0 { + break + } length++ p = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 2)) } diff --git a/internal/windows/api/pdh_test.go b/internal/windows/api/pdh_test.go index f49e6ecc8..491480042 100644 --- a/internal/windows/api/pdh_test.go +++ b/internal/windows/api/pdh_test.go @@ -72,8 +72,8 @@ func TestUTF16PtrToString(t *testing.T) { utf16Ptr, err := syscall.UTF16PtrFromString(tt.input) require.NoError(t, err, "Failed to create UTF16 pointer from string") - // Test the function - result := UTF16PtrToString(utf16Ptr) + // Test the function with a large enough maxLen + result := UTF16PtrToString(utf16Ptr, 1024) // Verify the result assert.Equal(t, tt.expected, result) @@ -85,7 +85,7 @@ func TestUTF16PtrToString_NilPointer(t *testing.T) { t.Parallel() // Test with nil pointer - result := UTF16PtrToString(nil) + result := UTF16PtrToString(nil, 100) assert.Equal(t, "", result, "Should return empty string for nil pointer") } @@ -96,7 +96,7 @@ func TestUTF16PtrToString_ZeroLengthString(t *testing.T) { nullTerminator := uint16(0) ptr := &nullTerminator - result := UTF16PtrToString(ptr) + result := UTF16PtrToString(ptr, 100) assert.Equal(t, "", result, "Should return empty string for zero-length UTF16 string") } @@ -115,10 +115,36 @@ func TestUTF16PtrToString_ManualUTF16Array(t *testing.T) { // Get pointer to first element ptr := &utf16Array[0] - result := UTF16PtrToString(ptr) + result := UTF16PtrToString(ptr, 100) assert.Equal(t, "test", result, "Should correctly parse manually created UTF16 array") } +func TestUTF16PtrToString_BoundsCheck(t *testing.T) { + t.Parallel() + + // Manually create a UTF16 array: "test" WITHOUT null terminator (simulating buffer end) + // We'll put some garbage after it to ensure we don't read it + utf16Array := []uint16{ + 0x0074, // 't' + 0x0065, // 'e' + 0x0073, // 's' + 0x0074, // 't' + 0xFFFF, // Garbage + 0xFFFF, // Garbage + } + + // Get pointer to first element + ptr := &utf16Array[0] + + // Tell it to read only 4 chars + result := UTF16PtrToString(ptr, 4) + assert.Equal(t, "test", result, "Should stop reading at maxLen even without null terminator") + + // Tell it to read only 2 chars + result = UTF16PtrToString(ptr, 2) + assert.Equal(t, "te", result, "Should stop reading at maxLen") +} + func TestUTF16PtrToString_EdgeCases(t *testing.T) { t.Parallel() @@ -128,7 +154,7 @@ func TestUTF16PtrToString_EdgeCases(t *testing.T) { utf16Ptr, err := syscall.UTF16PtrFromString("A") require.NoError(t, err) - result := UTF16PtrToString(utf16Ptr) + result := UTF16PtrToString(utf16Ptr, 100) assert.Equal(t, "A", result) }) @@ -139,7 +165,7 @@ func TestUTF16PtrToString_EdgeCases(t *testing.T) { utf16Ptr, err := syscall.UTF16PtrFromString(input) require.NoError(t, err) - result := UTF16PtrToString(utf16Ptr) + result := UTF16PtrToString(utf16Ptr, 100) assert.Equal(t, input, result) }) @@ -150,7 +176,7 @@ func TestUTF16PtrToString_EdgeCases(t *testing.T) { utf16Ptr, err := syscall.UTF16PtrFromString(input) require.NoError(t, err) - result := UTF16PtrToString(utf16Ptr) + result := UTF16PtrToString(utf16Ptr, 100) assert.Equal(t, input, result) }) } @@ -165,7 +191,7 @@ func BenchmarkUTF16PtrToString(b *testing.B) { b.ResetTimer() for range b.N { - _ = UTF16PtrToString(utf16Ptr) + _ = UTF16PtrToString(utf16Ptr, 1024) } } @@ -180,7 +206,7 @@ func BenchmarkUTF16PtrToString_LongString(b *testing.B) { b.ResetTimer() for range b.N { - _ = UTF16PtrToString(utf16Ptr) + _ = UTF16PtrToString(utf16Ptr, 1024) } } @@ -211,7 +237,7 @@ func TestUTF16PtrToString_CompareWithSyscall(t *testing.T) { ptr := &utf16Slice[0] // Test our function - ourResult := UTF16PtrToString(ptr) + ourResult := UTF16PtrToString(ptr, 1024) // Compare with syscall's implementation syscallResult := syscall.UTF16ToString(utf16Slice[:len(utf16Slice)-1]) // Remove null terminator for syscall diff --git a/internal/windows/pdh_raw_poll.go b/internal/windows/pdh_raw_poll.go index dac8340c1..613d408aa 100644 --- a/internal/windows/pdh_raw_poll.go +++ b/internal/windows/pdh_raw_poll.go @@ -128,7 +128,22 @@ func (pdh *PdhRawPoll) PollRawArray() (map[string][]CPUGroupInfo, error) { item := (*winapi.PDH_RAW_COUNTER_ITEM)(unsafe.Pointer(uintptr(unsafe.Pointer(itemBuffer)) + offset)) if item.SzName != nil { - name := winapi.UTF16PtrToString(item.SzName) + var name string + + // Calculate remaining buffer size from the pointer to the end of the buffer + bufferStart := uintptr(unsafe.Pointer(&buffer[0])) + stringStart := uintptr(unsafe.Pointer(item.SzName)) + + // Calculate offset of string within buffer + if stringStart >= bufferStart && stringStart < bufferStart+uintptr(bufferSize) { + stringOffset := stringStart - bufferStart + remainingBytes := uintptr(bufferSize) - stringOffset + // Convert bytes to uint16 count (divide by 2) + maxLen := uint32(remainingBytes / 2) + + name = winapi.UTF16PtrToString(item.SzName, maxLen) + } + // Use constant instead of magic number 32 timestamp := uint64(item.RawValue.TimeStamp.HighDateTime)<