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)
}
If you could credit me as a reporter for my contributions to security advisory I will be thankful.
Thanks,
Penghao Jiang