Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ if (NOT WIN32)
mark_as_advanced(NNG_SETSTACKSIZE)
endif ()

nng_defines_if(NNG_ENABLE_CUSTOM_ALLOC NNG_ENABLE_CUSTOM_ALLOC)

nng_defines_if(NNG_ENABLE_STATS NNG_ENABLE_STATS)

# IPv6 enable
Expand Down
3 changes: 3 additions & 0 deletions cmake/NNGOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ option(NNG_ENABLE_COVERAGE "Enable coverage reporting." OFF)
# for the public library.
option(NNG_ELIDE_DEPRECATED "Elide deprecated functionality." OFF)

option(NNG_ENABLE_CUSTOM_ALLOC "Enable overridable function pointers for allocation functions." OFF)
mark_as_advanced(NNG_ENABLE_CUSTOM_ALLOC)

option(NNG_ENABLE_STATS "Enable statistics." ON)
mark_as_advanced(NNG_ENABLE_STATS)

Expand Down
4 changes: 4 additions & 0 deletions include/nng/nng.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,10 @@ NNG_DECL int nng_ctx_set_int(nng_ctx, const char *, int);
NNG_DECL int nng_ctx_set_size(nng_ctx, const char *, size_t);
NNG_DECL int nng_ctx_set_ms(nng_ctx, const char *, nng_duration);

#ifdef NNG_ENABLE_CUSTOM_ALLOC
NNG_DECL void nng_alloc_set(void* (*malloc)(size_t), void* (*calloc)(size_t, size_t), void (*free)(void*));
#endif
Comment on lines +554 to +556
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't hide a public API behind NNG_ENABLE_CUSTOM_ALLOC.

With the current wiring, NNG_ENABLE_CUSTOM_ALLOC is private to nng and always public on nng_testing, so enabled builds can hide nng_alloc_set() from real consumers while OFF builds still expose it to tests. This declaration needs to be always visible with a stub/error implementation behind it, or the macro needs to come from a generated public config header that is exported to consumers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/nng/nng.h` around lines 554 - 556, The declaration of nng_alloc_set
is currently guarded by private macro NNG_ENABLE_CUSTOM_ALLOC which can hide the
public API; always expose the nng_alloc_set declaration (remove the conditional)
and implement a stub/error fallback in the library when custom alloc is not
enabled, or alternatively move the enabling macro into the generated public
config and use that public macro for the guard so consumers and tests see
consistent declarations; update the implementation (function body) to either
call the provided pointers when enabled or return/abort with a clear error when
custom alloc is not supported, ensuring tests can still link against the symbol.


// nng_alloc is used to allocate memory. It's intended purpose is for
// allocating memory suitable for message buffers with nng_send().
// Applications that need memory for other purposes should use their platform
Expand Down
6 changes: 6 additions & 0 deletions src/core/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ typedef nni_type nni_opt_type;
#endif // defined(__BYTE_ORDER)
#endif // defined() endianness

extern void* (*nni_malloc_fn)(size_t);

extern void* (*nni_calloc_fn)(size_t, size_t);

extern void (*nni_free_fn)(void*);

// nni_alloc allocates memory. In most cases this can just be malloc().
// However, you may provide a different allocator, for example it is
// possible to use a slab allocator or somesuch. It is permissible for this
Expand Down
19 changes: 19 additions & 0 deletions src/nng.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// found online at https://opensource.org/licenses/MIT.
//

#include "core/defs.h"
#include "core/nng_impl.h"
#include "core/pipe.h"
#include "core/platform.h"
Expand Down Expand Up @@ -47,6 +48,24 @@ nng_socket_id(nng_socket s)
return (((int) s.id > 0) ? (int) s.id : -1);
}

#ifdef NNG_ENABLE_CUSTOM_ALLOC
void
nng_alloc_set(void* (*malloc)(size_t), void* (*calloc)(size_t, size_t), void (*free)(void*))
{
if (malloc) {
nni_malloc_fn = malloc;
}

if (calloc) {
nni_calloc_fn = calloc;
}

if (free) {
nni_free_fn = free;
}
}
Comment on lines +51 to +66
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.c" | grep -E "(posix_alloc|win_thread|nng\.c)" | head -20

Repository: nanomsg/nng

Length of output: 137


🏁 Script executed:

# Check the exact location of src/nng.c and the nng_alloc_set function
fd "nng\.c$" --type f

Repository: nanomsg/nng

Length of output: 64


🏁 Script executed:

# List the structure to understand the layout
ls -la src/ 2>/dev/null | head -20

Repository: nanomsg/nng

Length of output: 707


🏁 Script executed:

# Read src/nng.c around lines 51-66 to see the nng_alloc_set function
sed -n '1,80p' src/nng.c | cat -n

Repository: nanomsg/nng

Length of output: 2379


🏁 Script executed:

# Search for the global function pointer declarations
rg "nni_malloc_fn|nni_calloc_fn|nni_free_fn" src/nng.c -B 2 -A 2 -n

Repository: nanomsg/nng

Length of output: 225


🏁 Script executed:

# Check posix_alloc.c to see how globals are used
cat -n src/platform/posix/posix_alloc.c

Repository: nanomsg/nng

Length of output: 1365


🏁 Script executed:

# Check win_thread.c to see allocator usage
sed -n '1,100p' src/platform/windows/win_thread.c | cat -n

Repository: nanomsg/nng

Length of output: 2973


🏁 Script executed:

# Check core/nng_impl.h or core/defs.h for declarations and synchronization
grep -r "nni_malloc_fn\|nni_calloc_fn\|nni_free_fn" src/core/ -B 2 -A 2 -n

Repository: nanomsg/nng

Length of output: 463


🏁 Script executed:

# Search for any mutex or synchronization around allocator setting
rg "nng_alloc_set|nni_malloc_fn|nni_calloc_fn|nni_free_fn" src/ --type c -B 3 -A 3 | head -100

Repository: nanomsg/nng

Length of output: 4318


🏁 Script executed:

# Check if there's any initialization guard or documentation
rg "ENABLE_CUSTOM_ALLOC" src/ -B 5 -A 5 | head -150

Repository: nanomsg/nng

Length of output: 1662


🏁 Script executed:

# Check if there's any documentation about intended usage or timing
grep -r "nng_alloc_set\|custom.*alloc" src/ --type c --type h -B 2 -A 2 | grep -i "comment\|doc\|usage\|before\|after" | head -30

Repository: nanomsg/nng

Length of output: 172


🏁 Script executed:

# Check if there are any thread synchronization mechanisms or atomic operations used
rg "atomic|mutex|lock|synchroni" src/core/defs.h -B 3 -A 3

Repository: nanomsg/nng

Length of output: 310


🏁 Script executed:

# Verify that the globals are truly not protected by any locks
rg "static.*nni_malloc_fn|static.*nni_calloc_fn|static.*nni_free_fn" src/ -B 5 -A 5

Repository: nanomsg/nng

Length of output: 37


🏁 Script executed:

# Check if there's any documentation about nng_alloc_set in header files
rg "nng_alloc_set" src/ --type c --type h -B 5 -A 5

Repository: nanomsg/nng

Length of output: 389


🏁 Script executed:

# Look for any public header that might document expected usage
fd "\.h$" src/ -x grep -l "nng_alloc_set" {}

Repository: nanomsg/nng

Length of output: 37


🏁 Script executed:

# Check if initialization happens before any allocations
rg "nng_init|nng_open_pipe|nng_socket" src/nng.c -A 5 | head -50

Repository: nanomsg/nng

Length of output: 966


🏁 Script executed:

# Look for README or documentation files mentioning custom allocators
fd -e md -e rst -e txt . | xargs grep -l "custom.*alloc\|nng_alloc_set" 2>/dev/null

Repository: nanomsg/nng

Length of output: 39


🏁 Script executed:

# Check git history or comments in the file for intent
head -100 src/nng.c | grep -A 10 -B 10 "NNG_ENABLE_CUSTOM_ALLOC"

Repository: nanomsg/nng

Length of output: 396


🏁 Script executed:

# Verify if allocations happen within socket/pipe creation (confirming early initialization would be required)
rg "nni_alloc|nni_zalloc" src/core/ --type c | head -30

Repository: nanomsg/nng

Length of output: 2114


🏁 Script executed:

# Double-check: Are the globals truly extern in defs.h, confirming they are shared across compilation units?
cat -n src/core/defs.h | sed -n '245,260p'

Repository: nanomsg/nng

Length of output: 732


🏁 Script executed:

# Verify there is no initialization guard or one-shot check anywhere
rg "nng_alloc_set|nni_malloc_fn.*=" src/nng.c -B 10 -A 10

Repository: nanomsg/nng

Length of output: 401


🏁 Script executed:

# Check if there's any documentation or comments about thread-safety in platform headers
cat -n src/core/platform.h | grep -A 10 -B 10 "alloc"

Repository: nanomsg/nng

Length of output: 4921


🏁 Script executed:

# Final verification: confirm multiple definitions in different compilation units
rg "void\* \(\*nni_malloc_fn\)" src/ --type c

Repository: nanomsg/nng

Length of output: 258


🏁 Script executed:

# Verify no atomic or synchronization wrapper exists around the setter
rg "static|atomic|volatile|lock|mutex" src/nng.c | grep -A 5 -B 5 "nng_alloc_set"

Repository: nanomsg/nng

Length of output: 37


Synchronize and restrict allocator changes to one-shot initialization.

The globals nni_malloc_fn, nni_calloc_fn, and nni_free_fn are read without synchronization on every alloc/free in src/platform/posix/posix_alloc.c (lines 30, 36, 43) and src/platform/windows/win_thread.c (lines 52, 58, 65). Calling nng_alloc_set() after NNG allocations have begun risks freeing old allocations through a different free callback than the one used for allocation, causing heap mismatch. The independent, partial updates (each function pointer checked separately for NULL) worsen the race window. The void return type prevents callers from detecting or reacting to unsafe timing. Require allocator installation before any NNG allocation occurs, or embed the matching free callback with each allocation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nng.c` around lines 51 - 66, The allocator setters (nng_alloc_set)
currently update nni_malloc_fn, nni_calloc_fn, and nni_free_fn independently and
without synchronization while platform allocators (posix_alloc.c and
win_thread.c) read them unsafely; change nng_alloc_set to be a one-shot, atomic
installation that must be called before any NNG allocations: require the caller
to install all three callbacks together, make nng_alloc_set return an error code
(e.g., int) if called after initialization/allocation has started, and perform a
single atomic swap or guarded check to set all three function pointers at once
(or reject the call). Alternatively, embed the matching free callback into each
allocation path (store the free pointer with the allocation metadata) so that
free uses the allocator that performed the allocation; update references to
nni_malloc_fn/nni_calloc_fn/nni_free_fn accordingly and ensure posix_alloc.c and
win_thread.c use the new safe mechanism.

#endif

void *
nng_alloc(size_t sz)
{
Expand Down
16 changes: 13 additions & 3 deletions src/platform/posix/posix_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,34 @@

#include <stdlib.h>

#ifdef NNG_ENABLE_CUSTOM_ALLOC
void* (*nni_malloc_fn)(size_t) = malloc;
void* (*nni_calloc_fn)(size_t, size_t) = calloc;
void (*nni_free_fn)(void*) = free;
#else
#define nni_malloc_fn malloc
#define nni_calloc_fn calloc
#define nni_free_fn free
#endif

// POSIX memory allocation. This is pretty much standard C.
void *
nni_alloc(size_t sz)
{
return (sz > 0 ? malloc(sz) : NULL);
return (sz > 0 ? nni_malloc_fn(sz) : NULL);
}

void *
nni_zalloc(size_t sz)
{
return (sz > 0 ? calloc(1, sz) : NULL);
return (sz > 0 ? nni_calloc_fn(1, sz) : NULL);
}

void
nni_free(void *ptr, size_t size)
{
NNI_ARG_UNUSED(size);
free(ptr);
nni_free_fn(ptr);
}

#endif // NNG_PLATFORM_POSIX
16 changes: 13 additions & 3 deletions src/platform/windows/win_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,33 @@ static pfnSetThreadDescription set_thread_desc;

#include <stdlib.h>

#ifdef NNG_ENABLE_CUSTOM_ALLOC
void* (*nni_malloc_fn)(size_t) = malloc;
void* (*nni_calloc_fn)(size_t, size_t) = calloc;
void (*nni_free_fn)(void*) = free;
#else
#define nni_malloc_fn malloc
#define nni_calloc_fn calloc
#define nni_free_fn free
#endif

void *
nni_alloc(size_t sz)
{
return (sz > 0 ? malloc(sz) : NULL);
return (sz > 0 ? nni_malloc_fn(sz) : NULL);
}

void *
nni_zalloc(size_t sz)
{
return (sz > 0 ? calloc(1, sz) : NULL);
return (sz > 0 ? nni_calloc_fn(1, sz) : NULL);
}

void
nni_free(void *b, size_t z)
{
NNI_ARG_UNUSED(z);
free(b);
nni_free_fn(b);
}

void
Expand Down