Skip to content

Fix for heap-buffer-overflow in Eigen Map/TensorMap cached pointers#226

Open
The9Cat wants to merge 7 commits into
develfrom
coefCaching
Open

Fix for heap-buffer-overflow in Eigen Map/TensorMap cached pointers#226
The9Cat wants to merge 7 commits into
develfrom
coefCaching

Conversation

@The9Cat

@The9Cat The9Cat commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

This PR implements a fix for heap-buffer-overflow in Eigen Map/TensorMap cached pointers. This error has not appeared in general usage, as far as I know, but it is a bug nonetheless. These changes affect a lot of coefficient handling code so this PR needs thorough testing. There are no user-facing changes. This could be (should be?) rebased on main since it is a bug but we would need comprehensive testing.

Description

This PR fixes a critical heap-buffer-overflow bug in CoefStruct.H caused by dangling pointers in cached Eigen::Map and Eigen::TensorMap objects. The bug was detected by AddressSanitizer when running with 128 MPI processes and manifested as memory corruption during object destruction during cleanup operations.

Cause

The original implementation cached Eigen::Map and Eigen::TensorMap
objects in shared_ptr members:

void allocate()
{
  store.resize(dim); // Memory reallocation!
  coefs = std::make_shared<coefType>(store.data(), ...);
}

The problem:

  1. store is an Eigen::VectorXcd that owns the actual memory
  2. coefs is a shared_ptr<Map> pointing to store.data()
  3. When store.resize() is called, the vector reallocates its buffer
  4. The old coefs shared_ptr still points to freed memory (dangling pointer)
  5. During destruction, the Map tries to clean up already-freed memory
    → heap-buffer-overflow

Explanation

Eigen::Map and Eigen::TensorMap are non-owning views. They should
NEVER be wrapped in shared_ptr because shared_ptr assumes ownership
and will attempt to delete memory it doesn't own. My mistake...

A solution

Implemented a hybrid caching approach with explicit lifecycle management:

  1. Moved coefs members to private section with mutable keyword for
    lazy initialization
  2. Added two accessor methods per class:
  • getCoefMap() const: Lazy-creates and caches Map on first read
  • getCoefMapMutable(): Always recreates Map before assignment
  1. Added coefs.reset() calls in allocate() methods BEFORE store.resize()
  2. Updated assign() methods to use getCoefMapMutable()

Key benefits:

  • Lazy initialization: Map created only when first accessed
  • Automatic invalidation: Map cleared before store reallocation
  • No extra copies: Maps are lightweight pointer/dimension wrappers
  • Memory safe: No dangling pointers, proper lifecycle management

Changes

Modified: src/CoefStruct.H
Classes updated (all 8 derived classes using Maps/TensorMaps):

  • SphStruct: Eigen::Map<MatrixXcd>
  • CylStruct: Eigen::Map<MatrixXcd>
  • SlabStruct: Eigen::TensorMap<Tensor<complex<double>, 3>>
  • CubeStruct: Eigen::TensorMap<Tensor<complex<double>, 3>>
  • TblStruct: Eigen::Map<VectorXcd>
  • TrajStruct: Eigen::Map<MatrixXcd>
  • SphFldStruct: Eigen::TensorMap<Tensor<complex<double>, 3>>
  • CylFldStruct: Eigen::TensorMap<Tensor<complex<double>, 3>>

For each class:

  • Moved coefs to private section
  • Added getCoefMap() and getCoefMapMutable() methods
  • Added coefs.reset() before all store.resize() calls
  • Updated assign() methods to use mutable accessor

Updated clients to to use (e.g.) c->getCoefs() to get a constant
reference to the data rather than c->coefsto get a shared -pointer
to the data. Similarly, clients must use c->setCoefs(data) rather
than *c-coefs = data to assign data.

FieldBasis was an outlier in the pattern by setting both the Eigen
interface from its own data store directly from the CoefStruct
client. I updated this to use a unique_ptr rather than a
shared_ptr and use std::copy to set the internal data instance
from a passed CoefStruct.

Performance considerations

Memory overhead: Negligible

  • Each class stores one additional mutable shared_ptr (8 bytes)
  • This replaces the previous member, no net increase

Computation overhead: None

  • Map creation is extremely cheap (pointer arithmetic only)
  • Lazy initialization means Maps are created once per allocate() cycle
  • No extra data copies (Maps are non-owning views)

Read performance: Optimized

  • First access creates and caches Map
  • Subsequent reads use cached Map without recreation
  • Heavy loop access patterns: O(1) after first access

Testing

Tests so far

  • AddressSanitizer (128 MPI processes): No heap-buffer-overflow
  • Original functionality preserved (no semantic changes to API)
  • Disk-halo run produces expected results (e.g. power is consistent reference)

Recommended additional testing:

  • Run full test suite with AddressSanitizer enabled (maybe?)
  • Performance regression testing (compare cache hit rates)
  • Verify coefficient assignment operations produce identical results
  • Test with 128+ MPI processes to stress concurrent access

Checklist

  • Code change addresses root cause
  • All derived classes updated consistently
  • API remains backward compatible
  • No additional memory overhead
  • Performance preserved (lazy caching)
  • Documentation/comments updated
  • Check all test suites
  • Verified with AddressSanitizer

Breaking changes

None. The change is internal to CoefStruct implementation. All public
APIs remain unchanged. No changes in algorithmic logic and the most
of the changes should be "self-checked" by the compiler.

Related issues

  • Fixes heap-buffer-overflow detected by AddressSanitizer during
    128-process MPI run
  • Related to coefficient storage and cleanup in SlabSL::dump_coefs_h5()

@The9Cat The9Cat added the bug Something isn't working label Jul 23, 2026
@The9Cat
The9Cat requested a review from Copilot July 23, 2026 18:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to eliminate a heap-buffer-overflow risk stemming from cached Eigen Map/TensorMap objects becoming stale when the underlying store buffers are resized, and updates downstream call sites to use accessor APIs (getCoefs()/setCoefs()/initCoefMap()) instead of directly manipulating cached map pointers.

Changes:

  • Refactors coefficient structures to invalidate/recreate cached Eigen views around storage reallocation and exposes accessor methods for map/tensor access and assignment.
  • Updates multiple basis/IO/utility call sites to use the new accessor/assignment APIs instead of *coefs / coefs->....
  • Converts FieldBasis cached tensor maps from shared_ptr to unique_ptr and adjusts coefficient loading/copying logic.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/SphericalBasis.cc Switches coefficient access from *cur->coefs to cur->getCoefs() in HDF5 dump path.
src/SlabSL.cc Switches coefficient assignment to cur->setCoefs(...) after allocate().
src/PolarBasis.cc Switches coefficient access from *cur->coefs to cur->getCoefs() in HDF5 dump path.
src/Cylinder.cc Switches coefficient access from *cur->coefs to cur->getCoefs() in HDF5 dump path.
src/Cube.cc Switches coefficient assignment to cur->setCoefs(...) after allocate().
expui/FieldBasis.H Changes cached tensor-map ownership from shared_ptr to unique_ptr.
expui/FieldBasis.cc Updates tensor-map creation and rewrites set_coefs() to copy data instead of sharing internal pointers.
expui/CoefStruct.H Refactors multiple coefficient structs to use cached-map accessors and reset maps prior to store.resize().
expui/Coefficients.cc Updates coefficient storage/mapping initialization and assignments to use new APIs.
expui/CoefContainer.cc Updates coefficient reads/writes to use getCoefs() instead of dereferencing coefs.
expui/BiorthBasis.cc Updates coefficient creation and access to use initCoefMap() / getCoefs() instead of coefs directly.
Comments suppressed due to low confidence (2)

expui/CoefStruct.H:250

  • Same issue as SphStruct: getCoefMap() dereferences coefs when null (coefs->resize(...)), and resizing an Eigen::Map is not a safe way to bind it to store.data(). Construct the map lazily with the correct pointer/dimensions instead.
      if (!coefs) {
	int rows = mmax+1;
	int cols = nmax;
	coefs->resize(rows, cols);
      }

expui/FieldBasis.cc:315

  • Same issue in the dof!=2 path: the size check is inverted and the std::copy direction is reversed, so set_coefs() overwrites the input coefficient structure instead of copying into FieldBasis::store[0].
      // Check that the size of the coefficient data matches the expected size
      if (p->getCoefs().size() == store[0].size())
	throw std::runtime_error
	  ("FieldBasis::set_coefs: SphFldStruct data size mismatch with FieldBasis store");


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread expui/CoefStruct.H
Comment thread expui/FieldBasis.cc Outdated
Comment thread expui/CoefStruct.H
Comment thread expui/CoefContainer.cc Outdated
Comment thread expui/CoefContainer.cc Outdated
The9Cat and others added 2 commits July 23, 2026 14:42
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@The9Cat

The9Cat commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

@copilot Can you rewrite TableData and (probably) TblStruct so that the tables are multicolumn generally while also reverting correctly to single column if requested? Obviously this requires a minor interface change and a change to the h5 read and write.

@The9Cat

The9Cat commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

@copilot Never mind the last request. I misunderstood my own code. In this code, every struct is a row of the table indexed (if you like) by the first entry. So, yes, it is okay as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants