Skip to content

Commit e09cdff

Browse files
authored
Merge pull request #76 from fahadkhan-fk/fix/registry-dynamic-buffer-handling
Fix registry enumeration failure for large values (ERROR_MORE_DATA)
2 parents 3790c52 + c4afef8 commit e09cdff

1 file changed

Lines changed: 122 additions & 55 deletions

File tree

agent/registry_windows.go

Lines changed: 122 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ https://license.tacticalrmm.com
1212
package agent
1313

1414
import (
15+
"encoding/binary"
1516
"encoding/hex"
1617
"encoding/json"
1718
"errors"
@@ -271,69 +272,135 @@ func scanSubkeys(k registry.Key, names []string) []RegistryNode {
271272
}
272273

273274
func readRegistryValues(hKey windows.Handle) ([]RegistryValue, error) {
275+
const (
276+
initialNameLen = 256
277+
initialDataLen = 2048
278+
279+
// Safety caps to avoid runaway allocations / memory abuse.
280+
maxNameLen = 16 * 1024 // UTF-16 characters
281+
maxDataLen = 4 * 1024 * 1024 // 4 MB
282+
)
283+
274284
var values []RegistryValue
275-
var index uint32 = 0
285+
var index uint32
286+
276287
for {
277-
var valueName [256]uint16
278-
valueNameLen := uint32(len(valueName))
279-
var valType uint32
280-
var data [1024]byte
281-
dataLen := uint32(len(data))
282-
283-
err := regEnumValue(hKey, index, &valueName[0], &valueNameLen, &valType, (*byte)(unsafe.Pointer(&data[0])), &dataLen)
284-
if err == windows.ERROR_NO_MORE_ITEMS {
285-
break
286-
}
287-
if err != nil {
288-
return nil, fmt.Errorf("RegEnumValue failed: %w", err)
289-
}
288+
nameLen := uint32(initialNameLen)
289+
dataLen := uint32(initialDataLen)
290290

291-
name := syscall.UTF16ToString(valueName[:valueNameLen])
292-
entry := RegistryValue{Name: name}
293-
294-
switch valType {
295-
case windows.REG_SZ, windows.REG_EXPAND_SZ:
296-
str := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(&data[0]))[:dataLen/2])
297-
entry.Type = typeName(valType)
298-
entry.Data = str
299-
case windows.REG_MULTI_SZ:
300-
utf16s := (*[1 << 20]uint16)(unsafe.Pointer(&data[0]))[:dataLen/2]
301-
parts := []string{}
302-
start := 0
303-
for i, c := range utf16s {
304-
if c == 0 {
305-
if start < i {
306-
parts = append(parts, syscall.UTF16ToString(utf16s[start:i]))
307-
}
308-
start = i + 1
291+
for {
292+
// Hard caps for safety
293+
if nameLen == 0 || dataLen == 0 || nameLen > maxNameLen || dataLen > maxDataLen {
294+
return nil, fmt.Errorf("registry value too large to display (nameLen=%d dataLen=%d)", nameLen, dataLen)
295+
}
296+
297+
valueName := make([]uint16, nameLen)
298+
data := make([]byte, dataLen)
299+
300+
tmpNameLen := nameLen
301+
tmpDataLen := dataLen
302+
var valType uint32
303+
304+
err := regEnumValue(
305+
hKey,
306+
index,
307+
&valueName[0],
308+
&tmpNameLen,
309+
&valType,
310+
&data[0],
311+
&tmpDataLen,
312+
)
313+
314+
if err == windows.ERROR_NO_MORE_ITEMS {
315+
return values, nil
316+
}
317+
318+
if err == windows.ERROR_MORE_DATA {
319+
// Grow using required sizes if provided, else exponential backoff.
320+
if tmpNameLen > nameLen {
321+
nameLen = tmpNameLen + 16
322+
} else {
323+
nameLen *= 2
324+
}
325+
326+
if tmpDataLen > dataLen {
327+
dataLen = tmpDataLen + 256
328+
} else {
329+
dataLen *= 2
309330
}
331+
332+
// Retry same index
333+
continue
310334
}
311-
entry.Type = RegTypeMultiSZ
312-
entry.Data = parts
313-
case windows.REG_DWORD:
314-
val := *(*uint32)(unsafe.Pointer(&data[0]))
315-
entry.Type = RegTypeDWORD
316-
entry.Data = fmt.Sprintf("0x%08X (%d)", val, val)
317-
case windows.REG_QWORD:
318-
val := *(*uint64)(unsafe.Pointer(&data[0]))
319-
entry.Type = RegTypeQWORD
320-
entry.Data = formatQWORD(val)
321-
case windows.REG_BINARY:
322-
hexBytes := make([]string, dataLen)
323-
for i := 0; i < int(dataLen); i++ {
324-
hexBytes[i] = fmt.Sprintf("%02X", data[i])
335+
336+
if err != nil {
337+
return nil, fmt.Errorf("RegEnumValue failed: %w", err)
325338
}
326-
entry.Type = RegTypeBinary
327-
entry.Data = strings.Join(hexBytes, " ")
328-
default:
329-
entry.Type = fmt.Sprintf("UNKNOWN_%d", valType)
330-
entry.Data = "<unsupported>"
331-
}
332339

333-
values = append(values, entry)
334-
index++
340+
// Success: decode using tmpNameLen/tmpDataLen
341+
name := syscall.UTF16ToString(valueName[:tmpNameLen])
342+
entry := RegistryValue{Name: name}
343+
344+
switch valType {
345+
case windows.REG_SZ, windows.REG_EXPAND_SZ:
346+
// tmpDataLen is bytes; strings are UTF-16
347+
u16 := (*[1 << 20]uint16)(unsafe.Pointer(&data[0]))[:tmpDataLen/2]
348+
entry.Type = typeName(valType)
349+
entry.Data = syscall.UTF16ToString(u16)
350+
351+
case windows.REG_MULTI_SZ:
352+
u16 := (*[1 << 20]uint16)(unsafe.Pointer(&data[0]))[:tmpDataLen/2]
353+
parts := []string{}
354+
start := 0
355+
for i, c := range u16 {
356+
if c == 0 {
357+
if start < i {
358+
parts = append(parts, syscall.UTF16ToString(u16[start:i]))
359+
}
360+
start = i + 1
361+
}
362+
}
363+
entry.Type = RegTypeMultiSZ
364+
entry.Data = parts
365+
366+
case windows.REG_DWORD:
367+
entry.Type = RegTypeDWORD
368+
if tmpDataLen < 4 {
369+
entry.Data = "<invalid DWORD>"
370+
} else {
371+
val := binary.LittleEndian.Uint32(data[:4])
372+
entry.Data = fmt.Sprintf("0x%08X (%d)", val, val)
373+
}
374+
375+
case windows.REG_QWORD:
376+
entry.Type = RegTypeQWORD
377+
if tmpDataLen < 8 {
378+
entry.Data = "<invalid QWORD>"
379+
} else {
380+
val := binary.LittleEndian.Uint64(data[:8])
381+
entry.Data = formatQWORD(val)
382+
}
383+
384+
case windows.REG_BINARY:
385+
entry.Type = RegTypeBinary
386+
// Only use the bytes Windows says are valid
387+
b := data[:tmpDataLen]
388+
hexBytes := make([]string, len(b))
389+
for i := 0; i < len(b); i++ {
390+
hexBytes[i] = fmt.Sprintf("%02X", b[i])
391+
}
392+
entry.Data = strings.Join(hexBytes, " ")
393+
394+
default:
395+
entry.Type = fmt.Sprintf("UNKNOWN_%d", valType)
396+
entry.Data = "<unsupported>"
397+
}
398+
399+
values = append(values, entry)
400+
index++
401+
break
402+
}
335403
}
336-
return values, nil
337404
}
338405

339406
func formatQWORD(val uint64) string {

0 commit comments

Comments
 (0)