Problem
PokerTable.initializeOwner() is a separate unguarded function that sets the contract owner. Its only protection is require(owner == address(0)) — meaning anyone can call it on a freshly initialized clone before BigO does.
Currently this is safe because the lobby creates the PloTable clone and BigO clone in a single transaction, and BigO.initialize() calls initializeOwner(address(this)) atomically. But:
- The safety depends on an external invariant (the lobby's transaction atomicity) that isn't enforced by PokerTable itself
- If a new factory or integration creates a PloTable clone without immediately initializing the owner, the clone is vulnerable to ownership hijacking
- The invariant is not locally verifiable from PokerTable's code alone
Proposed Fix
Fold _owner into PokerTable.initialize() as a parameter so ownership is set atomically with initialization. Remove initializeOwner() entirely.
This requires reworking the creation order in PloBigO2BBPLobby.createTable() — currently PloTable is created first (without knowing the BigO address), then BigO is created and sets itself as PloTable's owner. Options:
- Reverse creation order: create BigO first (without a poker address), then create PloTable with BigO as owner, then link BigO to PloTable
- Two-phase in lobby: create both clones without initializing, then initialize both with cross-references in the same tx
- Lobby sets owner: have the factory not set an owner, and have the lobby call
initialize() with the BigO address after both clones exist
Severity
Low — currently safe due to single-tx atomicity in the lobby. But the pattern is fragile and would become a critical vulnerability if the creation flow is ever split across transactions or a new integration calls initializeOwner separately.
References
- Originally reported in SeismicSystems/poker#641
PokerTable.sol — initializeOwner()
BigO.sol — initialize() calls poker().initializeOwner()
PloBigO2BBPLobby.sol — createTable() orchestration
Problem
PokerTable.initializeOwner()is a separate unguarded function that sets the contract owner. Its only protection isrequire(owner == address(0))— meaning anyone can call it on a freshly initialized clone before BigO does.Currently this is safe because the lobby creates the PloTable clone and BigO clone in a single transaction, and
BigO.initialize()callsinitializeOwner(address(this))atomically. But:Proposed Fix
Fold
_ownerintoPokerTable.initialize()as a parameter so ownership is set atomically with initialization. RemoveinitializeOwner()entirely.This requires reworking the creation order in
PloBigO2BBPLobby.createTable()— currently PloTable is created first (without knowing the BigO address), then BigO is created and sets itself as PloTable's owner. Options:initialize()with the BigO address after both clones existSeverity
Low — currently safe due to single-tx atomicity in the lobby. But the pattern is fragile and would become a critical vulnerability if the creation flow is ever split across transactions or a new integration calls
initializeOwnerseparately.References
PokerTable.sol—initializeOwner()BigO.sol—initialize()callspoker().initializeOwner()PloBigO2BBPLobby.sol—createTable()orchestration