CNA-BUG-049: Accelerometer, Gyroscope, Compass and Motion declare Dispose(bool) public, so an external Dispose(false) marks the sensor disposed without cleanup
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
SensorBase declares Dispose(bool) protected, but the four sensor classes redeclare it public; Dispose(false) sets the disposed flag without Stop, unregistration or releasing the platform lease, and the destructor then skips cleanup.
- Identifier
CNA-BUG-049- Category
- Bug
- Subsystem
- Input
- Status
- Narrowed (partially fixed; describes only what survives)
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Microsoft::Devices::Sensors::Accelerometer::Dispose(bool), Gyroscope::Dispose(bool), Compass::Dispose(bool) and Motion::Dispose(bool); the IDisposable pattern of SensorBase<T>
Expected behaviour
In the .NET pattern CNA mirrors, Dispose(bool) is protected and only Dispose() and the destructor path call it. SensorBase.hpp declares it protected, and CNA's remediation plan item REMED-DEVICES-002 says all four classes should do the same, with a compile-time accessibility test.
Actual behaviour at TARGET
Accelerometer.hpp, Gyroscope.hpp, Compass.hpp and Motion.hpp each declare void Dispose(bool disposing) override; in a public section. Each implementation sends !disposing straight to SensorBase<T>::Dispose(false), which only sets disposed_ and wakes waiters: no Stop(), no instance-count decrement, no platform subsystem release, no nulling of the owner control block. The destructors call Dispose(true) only when not already disposed, so after an external Dispose(false) nothing ever cleans up. For a started Accelerometer or Gyroscope, the registration Start() made with the shared PlatformSensorSubsystem keeps naming the object; once it is destroyed, the next platform sensor event is dispatched through that registration into freed memory (Accelerometer.cpp). The per-class instance counter stays raised, so repeated misuse reaches the limit of 10 simultaneous instances. The avatar half of the original report is fixed: AvatarRenderer.hpp declares Dispose(bool) protected.
Source locations
modules/devices/include/Microsoft/Devices/Sensors/Accelerometer.hpp— Dispose(bool) override declared publicmodules/devices/include/Microsoft/Devices/Sensors/Gyroscope.hpp— Dispose(bool) override declared publicmodules/devices/include/Microsoft/Devices/Sensors/Compass.hpp— Dispose(bool) override declared publicmodules/devices/include/Microsoft/Devices/Sensors/Motion.hpp— Dispose(bool) override declared publicmodules/devices/include/Microsoft/Devices/Sensors/SensorBase.hpp— protected virtual Dispose(bool); ~SensorBasemodules/devices/src/Sensors/Accelerometer.cpp— Accelerometer::Dispose(bool) not-disposing branch; ~Accelerometer; Start registers with the subsystemmodules/devices/include/Microsoft/Devices/Sensors/Detail/PlatformSensorSubsystem.hpp— RegisterStartedInstanceLocked and UnregisterStartedInstanceLocked; DispatchToInstancesmodules/gamer-services/include/Microsoft/Xna/Framework/GamerServices/AvatarRenderer.hpp— AvatarRenderer::Dispose(bool) is protected (the fixed half)remediation/MASTER_REMEDIATION_PLAN.md— REMED-DEVICES-002 records the defect and the required access test
Evidence
Checked by reading the four headers, SensorBase.hpp, the four implementations and the subsystem at 009d40f5; not executed. The use-after-free follows from reading RegisterStartedInstanceLocked/UnregisterStartedInstanceLocked (only Stop and Dispose(true) unregister); it was not provoked under a sanitizer. CNA's own remediation plan records the defect as REMED-DEVICES-002, still NOT STARTED in REMEDIATION_PROGRESS.md. No test calls Dispose(false) on a sensor. The sensors deep dive already tells callers to use the parameterless Dispose().
Independently observed as a separate finding (merged): The overrides are public although SensorBase declares Dispose(bool) protected; calling Dispose(false) from outside sets the disposed flag without stopping the sensor, releasing its platform registration or decrementing the instance count.
Focused reproduction
// Illustrative; not compiled or run for this entry (SDL3 platform with an accelerometer).
{
Microsoft::Devices::Sensors::Accelerometer accel;
accel.Start(); // registered with the shared accelerometer subsystem
accel.Dispose(false); // public in CNA: marks disposed, no Stop(), no unregistration
} // ~Accelerometer sees IsDisposed and skips Dispose(true)
// The next accelerometer event from the platform is dispatched to the destroyed object.
Current tests
AccelerometerTests, GyroscopeTests, CompassTests, MotionTests and SensorBaseTests (under modules/devices/tests/Microsoft/Devices/Sensors) cover Dispose(), concurrent disposal and disposal during dispatch through the public parameterless Dispose() and the destructor. None checks the accessibility of Dispose(bool) or the Dispose(false) path.
Regression test
The compile-time check REMED-DEVICES-002 asks for, for all four classes, for example template<class T> concept PublicDisposeBool = requires(T& t) { t.Dispose(false); }; with static_assert(!PublicDisposeBool<Accelerometer>), plus a runtime test that Dispose() on a started sensor leaves no subsystem registration behind.
Blast radius
Code that calls Dispose(bool) on one of the four sensors from outside, which XNA and Windows Phone code cannot do because the method is protected there, so only code written against CNA's headers is exposed. Dispose(), destruction without an explicit call and VibrateController are unaffected.
Workaround
Call only the parameterless Dispose(), or let the destructor run.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Sensors guide
- Internals
- Devices internals: sensor disposal
- Deep dives
- Sensors and vibration: lifetime
- Known issues
- Bug index