On Thu, Oct 15, 2026 at 11:54:21AM +1100, Blake Jiang wrote:
Hello,
In the Go plugin bindings, Debug and Error pass a C.CString to the varargs wrappers and never free it (plugins/golang/src/libguestfs.org/nbdkit/utils.go, commit 9af733d0fa3ae7aa5c32f51355185486840f82bc):
func Debug(s string) { C._nbdkit_debug(C.CString(s)) }
func Error(s string) { C._nbdkit_error(C.CString(s)) }
The wrappers in the same file only format the string (nbdkit_debug ("%s", s) / nbdkit_error ("%s", s)), and nbdkit does not keep the pointer, so each call leaves len(s)+1 bytes allocated. C.CString memory is never collected by the Go runtime.
Error is also called implicitly by set_error (nbdkit.go) whenever a plugin callback returns a Go error, so a long-running plugin leaks one string per failed callback, plus one per Debug call (the string is allocated even when -v is not given).
ParseBool, ParseSize and ReadPassword in the same file already use the usual pattern:
With a small test program that copies Debug, Error and the two wrappers, with nbdkit_debug/nbdkit_error replaced by stubs that format into a local buffer, 100000 Debug+Error pairs grew the C heap by about 6.4 MB (64 bytes per pair). With the frees below the growth went away.
Suggested change:
func Debug(s string) { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) C._nbdkit_debug(cs) }
func Error(s string) { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) C._nbdkit_error(cs) }
Thanks, fixed in: https://gitlab.com/nbdkit/nbdkit/-/commit/42f73ac862b18888c8f0b27b7676434499... Rich.
If you could credit me as a reporter for my contributions to security advisory I will be thankful.
Thanks,
Penghao Jiang
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into KVM guests. http://libguestfs.org/virt-v2v