Under vulnerabilities that can occur with instruction handlers that handle two mutable accounts, and how to mitigate them.
constraint
to add an explicit constraint to an
account checking that it is not the same as another account.
data
field for user_a
and
user_b
in a single instruction. The value that the instruction sets for
user_a
is different from user_b
. Without verifying that user_a
and
user_b
are different, the program would update the data
field on the
user_a
account, then update the data
field a second time with a different
value under the assumption that user_b
is a separate account.
You can see this example in the code below.Tthere is no check to verify that
user_a
and user_b
are not the same account. Passing in the same account for
user_a
and user_b
will result in the data
field for the account being set
to b
even though the intent is to set both values a
and b
on separate
accounts. Depending on what data
represents, this could be a minor unintended
side-effect, or it could mean a severe security risk. allowing user_a
and
user_b
to be the same account could result in
user_a
isn’t the same as the public key of
user_b
, returning an error if they are the same.
user_a
and user_b
are not the same account.
constraint
#[account(..)]
attribute macro and the constraint
keyword to
add a manual constraint to an account. The constraint
keyword will check
whether the expression that follows evaluates to true or false, returning an
error if the expression evaluates to false.
The example below moves the check from the instruction logic to the account
validation struct by adding a constraint
to the #[account(..)]
attribute.
initialize
instruction to initialize a PlayerState
accountrock_paper_scissors_shoot_insecure
instruction that requires two
PlayerState
accounts, but does not check that the accounts passed into the
instruction are differentrock_paper_scissors_shoot_secure
instruction that is the same as the
rock_paper_scissors_shoot_insecure
instruction but adds a constraint that
ensures the two player accounts are differentstarter
branch
of this repository.
The starter code includes a program with two instructions and the boilerplate
setup for the test file.
The initialize
instruction initializes a new PlayerState
account that stores
the public key of a player and a choice
field that is set to None
.
The rock_paper_scissors_shoot_insecure
instruction requires two PlayerState
accounts and requires a choice from the RockPaperScissors
enum for each
player, but does not check that the accounts passed into the instruction are
different. This means a single account can be used for both PlayerState
accounts in the instruction.
rock_paper_scissors_shoot_insecure
instructioninitialize
instruction twice to
create two player accounts.
Add a test to invoke the rock_paper_scissors_shoot_insecure
instruction by
passing in the playerOne.publicKey
for as both playerOne
and playerTwo
.
anchor test
to see that the transactions completes successfully, even
though the same account is used as two accounts in the instruction. Since the
playerOne
account is used as both players in the instruction, note the
choice
stored on the playerOne
account is also overridden and set
incorrectly as scissors
.
playerOne
’s choice should be rock or scissors, so
the program behavior is strange.
rock_paper_scissors_shoot_secure
instructionlib.rs
and add a rock_paper_scissors_shoot_secure
instruction that uses the #[account(...)]
macro to add an additional
constraint
to check that player_one
and player_two
are different accounts.
rock_paper_scissors_shoot_secure
instructionrock_paper_scissors_shoot_secure
instruction, we’ll invoke the
instruction twice. First, we’ll invoke the instruction using two different
player accounts to check that the instruction works as intended. Then, we’ll
invoke the instruction using the playerOne.publicKey
as both player accounts,
which we expect to fail.
anchor test
to see that the instruction works as intended and using the
playerOne
account twice returns the expected error.
solution
branch of
the repository.