Skip to content

Leak fixes#596

Draft
quitschbo wants to merge 9 commits into
gyroidos:mainfrom
quitschbo:leak-fixes
Draft

Leak fixes#596
quitschbo wants to merge 9 commits into
gyroidos:mainfrom
quitschbo:leak-fixes

Conversation

@quitschbo

Copy link
Copy Markdown
Member

No description provided.

quitschbo added 7 commits May 27, 2026 22:23
cmld_wrapped_keys_path is allocated via mem_printf() in
cmld_init_stage_container() but was never released, leaking the
asprintf-allocated string on daemon exit.

Change the declaration from const char * to char * to match the
mem_printf() return type (cmld_get_wrapped_keys_dir() keeps its const
char * return type via implicit conversion), and release the buffer in
cmld_cleanup() alongside the other path globals.

Fixes: 4977717 ("daemon: Moved smartcard module to c_smartcard module")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
unit.c keeps a static list of registered unit compartment modules.
Modules are registered at INIT time via unit_register_compartment_module()
and the list_t nodes were never freed at shutdown.

Add unit_cleanup() which list_delete()s the list (the module data
pointers themselves reference file-scope statics in the u_* modules and
must not be freed), and register it via atexit() from
cmld_init_stage_unit() so the nodes are released on cmld exit.

Fixes: fb6de43 ("daemon/unit: Introduce new unit module for minimal compartments")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
CONTAINER_MODULE_REGISTER_WRAPPER_IMPL allocates a small handler struct
via mem_new0() for every container_register_*_handler() call. About 40
such handlers are registered during cmld startup (one per
implementation in c_user, c_smartcard, c_cgroups_v2, c_net, c_vol,
c_audit, c_run, c_time, c_cap, c_idmapped, c_service and
c_cgroups_dev), and none of them were ever released.

Track each newly-allocated handler in a static list inside container.c
via container_module_track_handler(), invoked from the macro right
after the mem_new0(). Add container_cleanup() which walks the tracking
list and mem_free0()s each handler, then list_delete()s both the
handler tracking list and the compartment_module_list. The module data
pointers themselves are file-scope statics in the c_* modules and must
not be freed.

Register container_cleanup() via atexit() from cmld_init_stage_unit().
Ordering is safe: cmld_cleanup() (which calls container_free() ->
compartment_free() -> module hooks) runs before container_cleanup() in
the LIFO atexit chain, so handler dereferences during compartment
teardown still see live handler structs.

Fixes: f728f8b ("daemon/container: Added macro magic for module wrapper implementation")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
compartment_new() builds compartment->description via mem_printf()
("name (uuid)") but compartment_free() never released it, leaking one
asprintf-allocated string per compartment lifetime.

The leak predates the container/compartment split; the comment above
the allocation already warned that future uuid/name setters must update
description, but no setter ever did, so the field has always been a
write-once mem_printf result that nothing reclaims.

Add the mem_free0() alongside the other char* fields freed in
compartment_free().

Fixes: ed54281 ("trustme github release (based on Android 5.1.1)")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
compartment_unregister_observer() releases an individual
compartment_callback_t via mem_free0() when it is removed from the
list, but compartment_free() never iterates the residual observers.
Any callback still registered when a compartment is destroyed leaks
its struct (and the list_t node holding it).

In practice this triggers on every cmld shutdown: the
config-sync observer registered via container_register_observer() in
cmld_reload_container_internal() is never unregistered for the
container's lifetime, and additional observers may be left over when a
container is torn down early (e.g. a failed network move during start,
where some but not all observers have been removed).

Walk observer_list before freeing the compartment and release each
callback, then list_delete() the list.

Fixes: ed54281 ("trustme github release (based on Android 5.1.1)")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
container_config_get_dev_allow_list_new() and
container_config_get_dev_assign_list_new() return NULL-terminated
char ** arrays where both the outer array and every entry are
heap-allocated (mem_new0 + mem_strdup per element).

container_free() only released the outer array via mem_free0(), so
every per-entry mem_strdup() (one per rule in <allow_dev> /
<assign_dev>) leaked when the container was destroyed.

Walk each list and mem_free0() the entry strings before releasing the
array. The trailing NULL terminator placed by the _new() helpers makes
the for-loop bound implicit.

Fixes: beb7f2c ("whitelist based configuration rules to allow containers (exclusive or non-exclusive) access to hardware devices")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
main() allocates two event_signal_t handlers (SIGINT and SIGTERM) and
an event_timer_t for the periodic logfile rename. Their lifetime is
"until the process exits", but cmld_handle_device_shutdown()
short-circuits the event loop by calling exit(0) directly, so
event_remove_signal()/event_remove_timer() are never invoked and the
structs plus their list_t nodes in event_signal_list/event_timer_list
are leaked at shutdown.

Promote sig_int, sig_term and logfile_timer to file-scope statics and
add main_event_cleanup() which removes and frees each, registered via
atexit() right after the handlers are added. main_event_cleanup() runs
before main_logf_cleanup() in LIFO order, keeping the log path
available for any teardown logging.

Fixes: ed54281 ("trustme github release (based on Android 5.1.1)")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
@quitschbo quitschbo marked this pull request as draft May 29, 2026 09:36
quitschbo added 2 commits May 29, 2026 13:44
container_vnet_cfg_new() mem_strdup()s rootns_name when present (e.g.
the host0 vnet of testcontainer, or vnet0/vnet1 of containers that
specify an rootns binding), but the cleanup loop in container_free()
only released vnet_name and the cfg struct itself, leaking one
mem_strdup per vnet on every container teardown.

Add the missing mem_free0(vnet_cfg->rootns_name); mem_free0() is
NULL-safe so it also covers entries with no rootns binding.

Fixes: 197b6f6 ("Provide runtime veth config through control interface")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
container_config_get_net_ifaces_list_new() builds a local
mac_whitelist of mem_new()'d uint8_t * MAC entries and hands it to
container_pnet_cfg_new(). The new() function deep-copies each entry
into its own list rather than taking ownership, so the local list
(both the list_t nodes and the MAC byte arrays) must be released by
the caller.

Currently the caller drops the list on the floor on every loop
iteration where mac_filter is enabled, leaking one uint8_t * per
whitelisted MAC plus a list_t node per entry.

Walk and free the entries, then list_delete() the list, after every
container_pnet_cfg_new() call (regardless of whether it returned a
valid pnet_cfg).

Fixes: 90274a3 ("daemon/container: propagate mac filter to c_net for physical netifs")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Weiß <michael.weiss@aisec.fraunhofer.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant