Skip to content

[defect]: Memory leak for rad_vp2data using thread-local buffer #5772

@LeeChuHsuan

Description

@LeeChuHsuan

What type of defect/bug is this?

Crash or memory corruption (segv, abort, etc...)

How can the issue be reproduced?

We found the memory usage by the application is keep growing in our environment,
so we suspected there were some memory leak in the application.
To verify it, I ran the application with Xcode instrument for detection in my machine.
Following is the summary that generated with the help of AI.
Please help verify it, thanks.

Memory Leak: rad_vp2data Thread-Local Buffer

Applies to: macOS and Linux (and any platform where TLS_STORAGE_CLASS / __THREAD is used; see src/include/threads.h).

Summary

A memory leak (~90 KB in Xcode Instruments) was traced to the thread-local buffer used by rad_vp2data() in the attribute/xlat expansion path. The potential fix ensures that every worker thread registers its buffer with a pthread key when it first allocates, so the destructor runs at thread exit and the buffer is freed.


Leak Call Path (Xcode Instruments)

Layer Symbol Library/Binary
Thread thread_start_pthread_start libsystem_pthread.dylib
Request request_handler_threadproxy_runningrequest_finishrad_postauthprocess_post_auth radiusd
Module indexed_modcallmodcallmodcall_recursemodcall_child (recursive) radiusd
Map/xlat map_to_requestmap_to_vpradius_axlat_structxlat_expand_structxlat_processxlat_aprintxlat_string libfreeradius-server.dylib
Allocation rad_vp2data_malloc_zone_malloc_instrumented_or_legacy libfreeradius-radius.dylib → libsystem_malloc.dylib

The leak occurs during post-auth processing when mapping or expanding attributes (e.g. %{string:...} or attribute references) and value-pairs.


Root Cause

  • rad_vp2data() (in src/lib/radius.c) uses a per-thread buffer to convert VALUE_PAIR data to network byte order. It allocates with malloc() and stores the pointer in thread-local storage via fr_thread_local_setup / fr_thread_local_init / fr_thread_local_set.
  • The buffer is intended to be freed when the thread exits via a pthread key destructor registered in src/include/threads.h.
  • When using compiler TLS (__THREAD / TLS_STORAGE_CLASS, e.g. on macOS with clang), only the first thread that ran the one-time key init had ever called pthread_setspecific(). All other worker threads never registered their buffer with the key.
  • When a thread exits, the destructor is only run for that thread if it had previously called pthread_setspecific for that key. Worker threads that never did so had their buffers never freed → leak.

Potential Fix (threads.h __THREAD branch)

File: src/include/threads.h

  1. Removed pthread_setspecific from the one-time key init, so init only creates the key.
  2. Added a setter __fr_thread_local_set_##_n that:
    • Assigns the new value to the thread-local variable.
    • Ensures the key exists via pthread_once.
    • If the new value is non-NULL, calls pthread_setspecific(key, &_n) so this thread’s pointer is registered.
  3. Wired fr_thread_local_set to use this setter in the __THREAD branch.

Now, when a worker thread first allocates its buffer in rad_vp2data and calls fr_thread_local_set(rad_vp2data_buff, buffer), that thread registers its buffer with the pthread key; when the thread exits, the destructor runs and frees the buffer.


Log output from the FreeRADIUS daemon

N/A

Relevant log output from client utilities

No response

Backtrace from LLDB or GDB

Metadata

Metadata

Assignees

No one assigned

    Labels

    defectcategory: a defect or misbehaviour

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions