Fix memory leaks reported by Coverity Scan - #13552
Draft
bryancall wants to merge 3 commits into
Draft
Conversation
Each leak is on a path where the leaked allocation is unreachable afterwards,
so releasing it changes no observable behavior.
- Plugin option parsing: a repeatable command line option overwrote the
previously duplicated string. Affects regex_revalidate, remap_purge,
xdebug, stale_response and the uri_signing issuer id. Every one of these
fields starts out null, so the first pass frees nothing.
- TSMgmtStringGet hands back a copy the caller owns. maxmind_acl and an API
regression test dropped it.
- jax_fingerprint leaked its configuration on three plugin initialization
failure paths.
- The YAML remap parser duplicated a redirect URL that nothing owned.
parse_format_redirect_url copies what it needs, so the local string's
storage can be passed directly.
- traffic_cache_tool never released its URL set or stripe hash table. Cache
is neither copyable nor movable, so the new destructor cannot double free.
Verified with a clean build (no new warnings) and the full unit test suite on
Fedora, GCC 16.1.1.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR is the final part of a Coverity-driven cleanup series, removing a set of confirmed memory leaks across core code, plugins, and the traffic_cache_tool by ensuring caller-owned allocations are freed and by correctly owning/freeing repeatable option values.
Changes:
- Add missing cleanup for owned allocations (notably
traffic_cache_tool::CacheURL set and stripe hash table). - Fix leaks caused by repeatable command-line options overwriting previously duplicated strings in multiple plugins.
- Free caller-owned strings returned by
TSMgmtStringGetand clean up plugin config on initialization failure paths.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/traffic_cache_tool/CacheTool.cc | Initializes stripes_hash_table and adds Cache destructor cleanup for URL set entries and the stripe hash table. |
| src/proxy/http/remap/RemapYamlConfig.cc | Avoids leaking a duplicated redirect URL buffer by passing mutable std::string storage to parse_format_redirect_url(). |
| src/api/InkAPITest.cc | Frees the caller-owned string returned by TSMgmtStringGet() in the regression test. |
| plugins/xdebug/xdebug.cc | Frees previously set header name when -h/--header is specified multiple times. |
| plugins/remap_purge/remap_purge.cc | Frees prior option values when repeatable options overwrite instance configuration strings. |
| plugins/regex_revalidate/regex_revalidate.cc | Frees prior option values for repeatable options that overwrite stored configuration strings. |
| plugins/experimental/uri_signing/config.cc | Frees previously assigned issuer id before overwriting to prevent leaks when multiple issuers set an id. |
| plugins/experimental/stale_response/stale_response.cc | Frees prior log filename when the log filename option is repeated (avoids leaking prior strdup). |
| plugins/experimental/maxmind_acl/mmdb.cc | Frees the caller-owned TSMgmtStringGet() result in both success and fallback paths. |
| plugins/experimental/jax_fingerprint/plugin.cc | Deletes plugin config on additional initialization failure paths to prevent leaks. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Replaces the explicit delete on each initialization failure path with a unique_ptr that releases at the point ownership actually transfers: to the instance handle in TSRemapNewInstance, and to the log field callback and continuation in TSPluginInit. This also closes a leak in TSPluginInit. The user argument reservation failure path returned without freeing the configuration, which is only correct when the log field callback has captured it, and that capture is conditional on a log symbol being configured. Without one, nothing owned the configuration and it leaked. Reserving the index before registering the log field puts every failure exit inside the span where the unique_ptr still owns the object, so no path needs to reason about who else holds it.
parse_format_redirect_url() nul terminates each chunk in place before copying it out and restores the byte afterwards. For a url containing no format specifier the scan runs to the end and that write lands on the terminating nul, and std::string does not permit a caller to assign through the reference at index size(). Pass a buffer this function owns instead, released once the parser returns. The chunk list holds its own copies, so nothing outlives the call, and the allocation that previously leaked here stays fixed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 3 of 3 splitting a Coverity Scan cleanup into independently reviewable pieces. Each leaked allocation is unreachable after the leak, so releasing it changes no observable behavior.
Repeatable command line options
regex_revalidate,remap_purge,xdebug,stale_responseand theuri_signingissuer id all overwrote a previously duplicated string when an option was given twice. Every one of these fields is null-initialized first (memset, an init helper, or an explicit= nullptr), so the first pass frees nothing and no string literal ever reachesfree.stale_responsecompares againstPLUGIN_TAGby pointer identity, which is the idiom its own destructor already uses.Ownership the callers were not honoring
TSMgmtStringGethands back a copy the caller owns (ats_strdupinternally).maxmind_acland an API regression test dropped it. Both are safe on the failure path too, since the function does not write*resulton failure and both callers pre-initialize to null.jax_fingerprintleaked its configuration on three plugin initialization failure paths. Note thereserve_user_argfailure path is deliberately not given adelete, because the config is captured by aTSLogFieldRegisterlambda above it.parse_format_redirect_urlcopies what it needs out of the buffer, so the local string's storage can be handed over directly. It does write into that buffer transiently (nul-terminating chunks in place and restoring them), so the buffer must be mutable and outlive the call, which it is and does.traffic_cache_toolnever released its URL set or stripe hash table.The one hunk where new code runs
~Cache()inCacheTool.ccis the only place here that adds executing code rather than deleting or substituting.Cacheholdsstd::list<std::unique_ptr<Span>>, so copy and move are implicitly deleted and the new destructor cannot double free; theURLsetentries arenewed in exactly one place and deleted nowhere else. The= nullptrinitializer onstripes_hash_tableis load bearing, since most instances never build the table.Two follow-ups I did not fold in, to keep this reviewable:
build_stripe_hash_table()overwritesstripes_hash_tablewithout freeing a previous table (harmless today, one call per instance, but now that the field is owning it is worth guarding), andregex_revalidate's-lis a fourth repeatable option that still leaks aTSTextLogObject.Verification
Clean build with no new warnings and the full unit test suite passing (137/137) on Fedora, GCC 16.1.1.
Two of the touched plugins are not built by default, so verifying them needed extra options:
uri_signingrequires cjose, andjax_fingerprintdefaults to off and needs-DENABLE_JAX_FINGERPRINT=ON. Both are compiled in the run above.Draft while CI runs.
Update
Pushed a follow-up commit that replaces the explicit deletes here with a
unique_ptr, since a reviewer pointed out the deletes were doing by hand what ownership should do on its own.That turned up a leak the explicit-delete version missed.
TSPluginInit's user-argument reservation failure path returned without freeing, which is only correct when the log field callback has captured the configuration, and that capture is conditional on a log symbol being configured. With no--log-field, nothing owned it and it leaked. Reserving the index before registering the log field puts every failure exit inside the span theunique_ptrstill owns, so no path has to reason about who else holds a reference. Note that reordering is a behavior change, small but real: a failed reservation no longer leaves a registered log field behind.