CNA-BUG-168: LocalGamerServicesStore maps distinct gamertags to one achievements file when they differ only in characters outside A-Z, a-z, 0-9, '.', '_' and '-'

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

✓

Evidence basis: source-verified at the pinned commit; executed for this entry (the Evidence section names exactly what was run); 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.

SanitizeStoreFileNameComponent replaces every other byte with an underscore, so gamertags such as "Ann B" and "Ann_B", or two same-length non-ASCII names, share one achievements file and see each other's achievements.

Identifier
CNA-BUG-168
Category
Bug
Subsystem
Networking & gamer services
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Low (a triage suggestion, not a project priority)
Evidence basis
Reproduced: executed for this entry (the Evidence section names exactly what was run)
Tests touching this area
Yes: see Current tests
Affected contract
SignedInGamer::AwardAchievement and GetAchievements persistence (CNA::Internal::GamerServices::SaveEarnedAchievementEXT, LoadEarnedAchievementsEXT, SanitizeStoreFileNameComponent)

Expected behaviour

Achievements belong to one gamer: GetAchievements returns what that gamertag earned and nothing another gamer earned.

Actual behaviour at TARGET

The achievements file is achievements/<SanitizeStoreFileNameComponent(gamertag)>.json (LocalGamerServicesStore.cpp). The sanitiser keeps ASCII letters, digits, -, _ and ., replaces every other byte with _ (each byte of a multi-byte UTF-8 character separately) and maps an empty name to _. The mapping is many-to-one and the file does not record the original gamertag, so "Ann B", "Ann_B" and "Ann!B" read and write one list, as do two non-ASCII names whose characters have the same byte lengths. Leaderboards are not affected in the same way: each board file stores every entry's raw gamertag.

Source locations

Evidence

Checked by reading at 009d40f5; the audit's own executed probe follows below. The four gamertags the dispatcher creates sanitise to distinct names, so only application-created gamers can collide. The gamer-services deep dive states that the mapping "does not make identities unique" but not the consequence.

Independent re-verification: Reproduced by the audit at 009d40f5. A probe built from TARGET's LocalGamerServicesStore.cpp, PropertyDictionary.cpp and the storage sources (g++ -O0; the Sharp Runtime types came from a sibling checkout that TARGET does not pin) sanitised “Ann B”, “Ann_B” and “Ann!B” to the same name Ann_B, and the two non-ASCII names “éx” and “šx” to the same __x. After SaveEarnedAchievementEXT("Ann B", "first_steps", ...) one file, achievements/Ann_B.json, existed, and LoadEarnedAchievementsEXT for “Ann_B” and for “Ann!B” each returned that entry, while “Bob” returned none. The four gamers the dispatcher creates sanitise to distinct names, so only application-created gamers collide.

Focused reproduction

// Illustrative; the audit's executed probe is described under Evidence.
auto ann  = SignedInGamer::CreateInternal("Ann B");
auto ann2 = SignedInGamer::CreateInternal("Ann_B");
ann.AwardAchievement("first_steps");     // writes achievements/Ann_B.json
auto list = ann2.GetAchievements();      // contains first_steps

Current tests

SignedInGamerTest.AchievementsAreIsolatedPerGamertag in GamerServicesGamerTests.cpp uses gamertags that sanitise to different names; no test uses two that sanitise to the same one.

Regression test

Two gamertags that differ only in a space, punctuation or a non-ASCII character, each awarded a different key, each reading back only its own; a collision-free encoding (percent-encoding or a hash suffix, with the gamertag stored in the file) satisfies it.

Blast radius

Games that create local profiles with free-form names: two players' achievements merge. Leaderboards and the four stub gamers are unaffected.

Workaround

Restrict profile names to ASCII letters, digits, -, _ and ., or keep names distinct after that mapping.

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

Known issues
Bug index