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