CNA-BUG-083: PhoneApplicationService::getCurrentProperty()'s static instance detaches from an already-destroyed Game during static destruction

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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.

The process-wide PhoneApplicationService is a function-local static holding a borrowed Game*; attached to a Game that is destroyed first, its destructor calls DetachEXT on the dead game's events.

Identifier
CNA-BUG-083
Category
Bug
Subsystem
Core & runtime
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (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::Phone::Shell::PhoneApplicationService::getCurrentProperty(), AttachEXT(Game&), DetachEXT() and ~PhoneApplicationService()

Expected behaviour

The header presents getCurrentProperty() as “the application's single lifecycle service” and AttachEXT as the one line a phone port writes. DetachEXT's documentation says the destructor calls it “so a game that outlives nothing in particular still leaves no dangling subscription behind”. Using the process-wide instance with an ordinary Game should end the process without undefined behaviour.

Actual behaviour at TARGET

getCurrentProperty() returns a function-local static PhoneApplicationService, destroyed during static destruction after main returns. AttachEXT stores a raw Game* and three event tokens; nothing in Game or elsewhere under modules/runtime knows about the service, so nothing clears the pointer when the game is destroyed. ~PhoneApplicationService calls DetachEXT, which calls Activated.Remove, Deactivated.Remove and Exiting.Remove through the stored pointer. When the Game lived on main's stack, the usual shape of a port, those calls erase from the handler vectors of a destroyed object: a use-after-free at exit.

Source locations

Evidence

Checked by reading PhoneApplicationService.cpp and its header at 009d40f5, and Sharp Runtime's System::EventHandler::Remove (sharp-runtime next @ 41b918c9, a revision TARGET does not pin), which erases from the member vector with no liveness check. A search of modules/runtime finds no reference to the phone module. Unchanged since the earlier recorded pin. Not executed: the failure is undefined behaviour, so its visible symptom (a crash at exit, heap corruption, or nothing) depends on the allocator.

Focused reproduction

Illustrative (not compiled for this entry): the obvious port.

#include "Microsoft/Phone/Shell/PhoneApplicationService.hpp"
#include "Microsoft/Xna/Framework/Game.hpp"

int main()
{
    Microsoft::Xna::Framework::Game game;   // destroyed when main returns
    Microsoft::Phone::Shell::PhoneApplicationService::getCurrentProperty().AttachEXT(game);
    game.Run();
    return 0;
}   // static destruction: ~PhoneApplicationService -> DetachEXT -> game_->Activated.Remove(...) on a dead Game

Current tests

All nine cases in PhoneApplicationServiceTests.cpp construct a local service. DestroyingTheServiceUnsubscribesFromTheGame pins the safe order (the service dies first), and CurrentIsOneServiceForTheWholeProcess compares addresses without attaching. No test attaches the static instance to a game that is destroyed before it.

Regression test

A test in a child process (the failure happens during static destruction) that attaches getCurrentProperty() to a scoped Game, lets the game go out of scope and exits, run under AddressSanitizer. A fix could clear the pointer from the game's side (for example on Game::Disposed) or hold a weak lifetime token instead of the raw pointer.

Blast radius

Every phone port that attaches the process-wide Current service to a Game destroyed before static destruction, which is any game object that is not itself a static. Local services destroyed before their game, and services detached explicitly, are unaffected. Only executables that link CNA::Phone are involved; the CNA umbrella does not include it.

Workaround

Call PhoneApplicationService::getCurrentProperty().DetachEXT() after Run() returns, before the Game is destroyed.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index