FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0225 — KalmanObject returns Eigen matrices allocated in the plugin and freed in fhsim: the two must agree on Eigen's allocator
ID 0225
Class BUG
Severity 2
Status blocked

Models: —

Found: 2026-11-03, feature base-asan (local ASan/UBSan build of fhsim_base 0bcb39f against fhsim 56c64d9e)

Decision needed: how the Kalman plugin interface stays safe when fhsim and a plugin are built with different Eigen allocator settings: (a) state it as part of the plugin ABI (same Eigen version and the same EIGEN_MAX_ALIGN_BYTES/SIMD flags in every package, D14 AVX2), and for sanitizer runs build every package with ASan or define EIGEN_MALLOC_ALREADY_ALIGNED=1 everywhere; or (b) change KalmanObject to fill buffers that fhsim owns (API change: fhsim_base src/kalman and fhsim's test objects must follow). Agent default: (a), no code or flag change until ruled.

Evidence

  • include/fhsim/simobject/KalmanObject.h:32-56: five pure virtuals return estimator::Matrix / estimator::Vector (dynamic Eigen types) by value. The plugin allocates the heap buffer; fhsim frees it.
  • src/engine/integrator/kalman/KalmanFilter.cpp:75, :84, :100 (KalmanFilterData) and src/engine/integrator/kalman/KalmanIntegrator.cpp:235, :259 take the results and destroy them in fhsim.
  • Eigen (Conan eigen/5.0.1) Eigen/src/Core/util/Memory.h:22-54: EIGEN_MALLOC_ALREADY_ALIGNED is 1 only on glibc 64-bit, with EIGEN_DEFAULT_ALIGN_BYTES == 16 and !defined(__SANITIZE_ADDRESS__). Otherwise Eigen uses handmade_aligned_malloc (over-allocates and returns an offset pointer) and frees with handmade_aligned_free. The macro is decided per translation unit, so two packages can disagree.
  • Reproduction: fhsim_base built with -fsanitize=address,undefined, fhsim (local_main) without. fhsim_base fast tier, SimObject.KalmanPendulum, KalmanHydroBall, KalmanHydroCable, ZeroDynamics, Jacobian.KalmanPendulum_atNonZeroStateOffset abort: AddressSanitizer: attempting free on address which was not malloc()-ed, "16 bytes inside of 48-byte region", allocated in KalmanPendulum::GetInitialEstimationErrorCovariance() (libfhsim_base.so), freed in estimator::KalmanFilterData::KalmanFilterData (fhsim). The 16-byte offset is the handmade header.
  • Falsified the alternative (ownership bug in base): the same ASan build with -DEIGEN_MALLOC_ALREADY_ALIGNED=1 added to base's flags passes the whole fast tier in one process (380 gtest cases), with no ASan, LSan or UBSan report from src/kalman. The Kalman "leaks" seen before were a consequence of the abort.

Effect

In a normal Linux build both sides use plain malloc/free, so nothing fails today. It fails as soon as the two sides choose a different allocator: a sanitizer build of one package only, a plugin built with AVX (32-byte alignment, handmade allocator) against an fhsim built without (or the reverse), or a different Eigen version across the boundary. The result is a crash or heap corruption at the first Kalman setup (KalmanFilterData), not a wrong number. On MSVC both sides normally use _aligned_malloc only when alignment > 16; the same flag mismatch applies there.

Possible fix

  • (a) Document the rule (one Eigen version and the same SIMD/alignment flags for fhsim and every plugin: D14 states AVX2), and for sanitizer runs instrument every package or define EIGEN_MALLOC_ALREADY_ALIGNED=1 in all of them. No code change.
  • (b) Interface change: void GetProcessNoiseCovariance(Eigen::Ref<estimator::Matrix>) or raw double* buffers sized by fhsim, so memory never crosses the boundary. Changes fhsim_base src/kalman (KalmanHydroBall, KalmanHydroCable, KalmanPendulum, ZeroDynamics) and tests/testobjects/DecayingStateEstimator.

Test that would prove it

The reproduction above (fhsim_base fast tier with ASan in base only) is red today; under (b) it turns green without the macro. Under (a) a CI ASan job that instruments every package is the guard.

Risk

(b) is an API break for every KalmanObject implementation (only fhsim_base and fhsim's own tests in the workspace; outside code unknown). (a) leaves a crash for anyone who mixes flags.