#8

Possible memory leak: the handler result C string is never freed after each invocation

OpenOvOhaoopened 19d ago

At commit 40f9678f50228f594e7b7bff63286adbd9e5c919, the Go bridge returns the handler's output as a new C string (lambda/lambda.go:26-28):

buf := fn(event, context)
result = C.CString(string(buf)) // TODO(xlab): optimize if needed
size = (C.size_t)(len(buf))

lambda_handle_call (module/module.go:28-33) passes it back to the Python module, and lambda_handler_tp_call (module/module.c:44-54) copies it into a Python string and does not free it:

ret = lambda_handle_call(self->handle, event, context);
PyObject *result = PyString_FromStringAndSize(ret.data, ret.size);
return result;

PyString_FromStringAndSize makes its own copy, so ret.data is leaked on every invocation (the output size + 1 bytes), which accumulates in a warm Lambda container. An empty handler result still allocates and leaks 1 byte. A byte-identical copy of module.c is embedded in bindata.go; go-lambda create/update write that embedded copy into the build directory (buildModuleBridge, misc.go:128), so it also needs the change.

Details

Built the module from module/module.c + module/module.go with example/example.go as the handler (-buildmode c-shared, Python 2.7, golang:1.22-bullseye, linux/arm64; current Go needs CGO_CFLAGS_ALLOW/CGO_LDFLAGS_ALLOW for the python-2.7 pkg-config flags). Calling the handler 20,000 times from Python (an xrange loop) with a ~1 KB event and measuring mallinfo:

result len=35; 20000 calls: malloc in-use delta=963232 bytes (48/call)

With free(ret.data); added after PyString_FromStringAndSize:

result len=35; 20000 calls: malloc in-use delta=3200 bytes (0/call)

Possible change in module/module.c (and the embedded copy):

PyObject *result = PyString_FromStringAndSize(ret.data, ret.size);
free(ret.data);
return result;

(<stdlib.h> is available through Python.h.)

If you could credit me as a reporter for my contributions to security advisory I will be thankful.

Activity

0

No activity yet.