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