April 19, 2024

Memory Leak in cgo

Memory Leak in cgo

Recently my team encountered memory leak in our system. We have a service that is written in golang and rust. The golang part invokes the functions in rust. To do so, we use cgo and implement ffi on both side to bridge the gap in between.

The current implementation passes the parameters and return values with json format. However, we need to convert the json objects to CString / GoString in the golang ffi, which introduces the memory leak.

Here's the snippet of the faulty implementation in go:

func call(input string) string {
	
  incstring := C.CString(input)
  defer C.free(unsafe.Pointer(incstring))

  result := fn(tracestring, incstring)
  goresult := C.GoString(result)

  return goresult
}

According to Rust document: "The pointer which this function returns must be returned to Rust and reconstituted using CString::from_raw to be properly deallocated. Specifically, one should not use the standard C free() function to deallocate this string.
Failure to call CString::from_raw will lead to a memory leak.
"

This is because Rust cannot deallocate the memory before returning the value, otherwise the allocated memory won't be valid by the time the caller accesses it

To mitigate the problem, we need a function in Rust to free the memory that can be called explicitly after we make the function call.

func call(input string) string {
	
  incstring := C.CString(input)
  defer C.free(unsafe.Pointer(incstring))

  result := fn(tracestring, incstring)
  goresult := C.GoString(result)
  freeRustMemory(result)

  return goresult
}

func freeRustMemory(ptr *C.char) {
  C.free_string(ptr)
}
#include <stdint.h>
#include <stdlib.h>

// cgo func 
char* free_string(const char *s);
#[no_mangle]
/// # Safety
/// This function should only be called via CGO. Use this rather than `C.free` to free strings
/// allocated by Rust.
pub unsafe extern "C" fn free_string(s: *mut c_char) {
  if s.is_null() {
      return;
  }
  drop(CString::from_raw(s));
}