@@ -12,6 +12,7 @@ https://license.tacticalrmm.com
1212package agent
1313
1414import (
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
273274func 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
339406func formatQWORD (val uint64 ) string {
0 commit comments