Skip to content

Storage backends: fix leaks, NULL dereferences, memcached expiry and HTTP proxy cache bugs - #504

Merged
hummeltech merged 3 commits into
openstreetmap:masterfrom
stevekennedyuk:pr/storage-backend-fixes
Oct 22, 2026
Merged

hummeltech merged 3 commits into
openstreetmap:masterfrom
stevekennedyuk:pr/storage-backend-fixes

Conversation

@stevekennedyuk

@stevekennedyuk stevekennedyuk commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes in the memcached, RADOS, composite and HTTP-proxy storage backends (split out of #497 as suggested there, plus two bugs found later).

  • Unchecked malloc results, a NULL dereference when strstr() finds no separator in store_ro_composite, and leaks on error paths in all four backends.
  • store_memcached: tile expiry passed an uninitialised CAS token to memcached_cas(), so it almost always failed; now uses memcached_replace().
  • store_ro_http_proxy: a 404 freed the cached tile without clearing the pointer (double free on the next fetch) and leaked the response body; responses other than 200/404 served the previous tile for the new coordinates; unbounded strcpy of the map name.
  • store_ro_composite (follow-up to Addressing free(store) in renderd_list not safe for some storages #524): close_storage() now frees the primary and secondary backends it closes, as render_list/render_expired do for the outer one, and the init error paths free the primary too. The context was malloc'd, so closing a composite store that had never read a tile free()d an uninitialised cache.tile pointer (segfault); it's now calloc'd. The parsed connection strings are freed after use. gen_tile_test gains real composite tests (open/close, malformed strings, failing primary/secondary backend), which crashed or reported leaks under ASan before this and now pass cleanly.

Rebased onto the current master.

Independent of the other PRs in this series; it applies to master on its own and merges cleanly with each of them in any order.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YForD7qSL9LT6FhMuAoKPV

stevekennedyuk and others added 2 commits October 22, 2026 10:38
- store_memcached.c: check malloc(header_len) before use
- store_rados.c: check malloc(header_len), malloc(sz2); fix ctx-only NULL
  check to also cover store; log error and return on failure
- store_ro_http_proxy.c: free ctx->baseurl on curl init error paths; free
  chunk.memory on curl error paths to prevent memory leak when transfer
  is partially completed before failure
- store_ro_composite.c: check strstr() results before pointer arithmetic
  (NULL deref if separator missing); check malloc for primary connection
  string; free intermediate strings on all error paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- store_memcached: metatile_expire passed an uninitialised CAS token to
  memcached_cas(); use memcached_replace() instead.
- store_ro_http_proxy: 404 freed the cached tile without clearing the
  pointer (double free on the next fetch) and leaked the response
  body; any status other than 200/404 cached the previous tile under
  the new coordinates; explicit handling of a cached 404 in tile_read;
  bounded copy of xmlconfig instead of strcpy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YForD7qSL9LT6FhMuAoKPV
@stevekennedyuk
stevekennedyuk force-pushed the pr/storage-backend-fixes branch from 51a88c8 to a1df08e Compare October 22, 2026 09:42
Comment thread src/store_memcached.c
char * buf_raw;

if (m == NULL) {
snprintf(log_msg, 1024, "Failed to allocate memory for metatile header\n");
Comment thread src/store_memcached.c
rc = memcached_cas(store->storage_ctx, meta_path, strlen(meta_path), buf, len, 0, flags, cas);
// memcached_get() does not return a CAS value, so a compare-and-swap cannot be used here
// (the previous code passed an uninitialised CAS token). Replace the existing item instead.
rc = memcached_replace(store->storage_ctx, meta_path, strlen(meta_path), buf, len, 0, flags);
Comment thread src/store_rados.c
char * buf_raw;

if (m == NULL) {
snprintf(log_msg, 1024, "Failed to allocate memory for metatile header\n");
Comment thread src/store_ro_http_proxy.c
ctx->cache.y = y;
ctx->cache.z = z;
strcpy(ctx->cache.xmlname, xmlconfig);
snprintf(ctx->cache.xmlname, sizeof(ctx->cache.xmlname), "%s", xmlconfig);
@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 32.81250% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.23%. Comparing base (c4e3e62) to head (a933f8c).
⚠️ Report is 54 commits behind head on master.

Files with missing lines Patch % Lines
src/store_ro_composite.c 50.00% 18 Missing ⚠️
src/store_ro_http_proxy.c 0.00% 17 Missing ⚠️
src/store_rados.c 14.28% 6 Missing ⚠️
src/store_memcached.c 50.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #504      +/-   ##
==========================================
+ Coverage   81.54%   82.23%   +0.69%     
==========================================
  Files          24       24              
  Lines        5645     5411     -234     
==========================================
- Hits         4603     4450     -153     
+ Misses       1042      961      -81     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…store

Follow-up to openstreetmap#524, which made close_storage() leave freeing the backend
itself to the caller:

- ro_composite_close_storage() closed its primary and secondary backends
  but never freed them; it now frees both, as render_list and
  render_expired do for the outer backend. The init error paths that close
  the primary backend now free it too.
- init_storage_ro_composite() allocated its context with malloc(), so
  cache.tile was uninitialised and closing a composite store that had
  never read a tile called free() on garbage (segfault). Use calloc().
- The parsed primary/secondary connection strings were never freed on
  success or when the secondary backend failed to initialise; the
  backends keep their own copies of what they need.
- gen_tile_test: the composite test only covered builds without Cairo,
  and decided that from the C++ HAVE_CAIRO, which can differ from what
  store_ro_composite.c sees (Autotools). It now checks at run time, and
  when composite support is built in, opens and closes a store and checks
  malformed connection strings and failing primary/secondary backends.
  Under ASan with leak detection this previously segfaulted / reported
  the leaked backends, and now passes cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YForD7qSL9LT6FhMuAoKPV
@stevekennedyuk
stevekennedyuk force-pushed the pr/storage-backend-fixes branch from a1df08e to a933f8c Compare October 22, 2026 09:59
@hummeltech
hummeltech merged commit b36a84a into openstreetmap:master Oct 22, 2026
38 of 39 checks passed
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.

3 participants