Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sim: Override slot hashes account on simulation bank #24543

Merged
merged 5 commits into from Apr 22, 2022

Conversation

joncinque
Copy link
Contributor

Problem

As pointed out in #22880, it's possible for a program to detect if it's in a simulation.

Summary of Changes

Creating new banks has unintended knock-on effects all over the system, and a single source of truth for sysvars that falls back to the accounts db is error-prone, so #24033 and #24034 may cause more problems than they solve.

This is a much simpler approach, and simply overrides the slot history sysvar for simulation banks.

Fixes #23260

Copy link
Member

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach looks much cleaner!

.as_ref()
.map(|account| from_account::<SlotHistory, _>(account).unwrap())
.unwrap_or_default();
slot_history.add(self.slot().saturating_sub(1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this fix will quite work because a frozen bank has already called slot_history.add(self.slot()) which will flip the bit for self.slot() % MAX_ENTRIES to 1. When you call slot_history.add(self.slot() - 1), the flipped bit is still set and could be detected by manual inspection (even if the SlotHistory api doesn't expose it)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, we'd have to know what the bit was set to beforehand to reset it to its previous value. However, this is only exploitable if you know a particular slot was skipped exactly MAX_ENTRIES away from your simulation bank, which means you'd need to supply some other data about slot skips to detect it. Although there's still an exploit present, it would probably be close to random. Do you have a suggestion for resetting this properly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, yeah it's tricky that the bank doesn't know what the previous value was. If we always leave the bit flipped as a 1, then a program will know it's not being simulated if it sees that bit set to 0. Thinking now if there's a good way to do this...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing I can come up with is randomizing the bit...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Randomizing the bit is probably sufficient. Another approach could be fetching the state of the sysvar for the previous block with self.load_slow_with_fixed_root(self.proper_ancestors(), ..)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's even better, great suggestion.

runtime/src/bank.rs Outdated Show resolved Hide resolved
runtime/src/bank.rs Outdated Show resolved Hide resolved
runtime/src/bank.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@behzadnouri behzadnouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 this seems better approach than previous one.

let data = slot_account.data.borrow();
let slot: u64 = u64::from_le_bytes(data[data.len() - 8..].try_into().unwrap());

let clock = Clock::get().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm only Clock and SlotHistory expose the slot right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to test for SlotHistory::get() as well, to check the other way to obtain the sysvar?

Also for the case that Clock is included in the accounts rather than obtained through Clock::get?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm only Clock and SlotHistory expose the slot right?

Explicitly, yes. You could maybe suss it out through the recent blockhashes or slot hashes, but they don't give an explicit slot.

Do we want to test for SlotHistory::get() as well, to check the other way to obtain the sysvar?

Thankfully, it's not possible to do SlotHistory::get()! Which makes this implementation even tidier. Otherwise we'd need to also override it in the sysvar cache.

Also for the case that Clock is included in the accounts rather than obtained through Clock::get?

Good call, I'll add that case as well to be sure. Clock is only updated at the start of the slot, and not at the end, so we should be good, but but it's better to add a test for the future.

programs/bpf/rust/simulation/src/lib.rs Outdated Show resolved Hide resolved
if let Some(account_override) =
account_overrides.and_then(|overrides| overrides.get(key))
{
account_override.clone()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we don't need to do any of these stuff below
https://github.com/solana-labs/solana/blob/02bfb85c1/runtime/src/accounts.rs#L274-L326
for overridden account here because right now it is only SlotHistroy.

But the AccountOverrides = HashMap<Pubkey, AccountSharedData> type implies that these overrides are open to be extended to any account and not just SlotHistory, in which case the assumption above might not be true any more.

Would it make sense to do the override later here instead:
https://github.com/solana-labs/solana/blob/02bfb85c1/runtime/src/accounts.rs#L275-L276
so the rest of the code is symmetric between overridden accounts and account-db accounts?

Alternatively. would it make sense to tighten AccountOverrides a little bit so only explicit set of accounts can be overridden. For now seems like we just need SlotHistory. So maybe instead define:

struct AccountOverrides {
   slot_history: Option<AccountSharedData>,
}

Copy link
Contributor Author

@joncinque joncinque Apr 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the override later here instead:
https://github.com/solana-labs/solana/blob/02bfb85c1/runtime/src/accounts.rs#L275-L276
so the rest of the code is symmetric between overridden accounts and account-db accounts?

Are you suggesting to plumb the overrides down into AccountsDb::load_with_fixed_root? To be honest, I'd prefer not to, since that complicates the AccountsDb interface unnecessarily. Accounts is meant to be a wrapper interface over AccountsDb, so I think it's better for the overrides to be processed there. If you want to keep things symmetric, however, I can refactor the loading logic to either use accounts_db or the overrides. What do you think?

Edit: I think I misunderstood, will refactor to do the rest of the code if loaded from the cache too.

But the AccountOverrides = HashMap<Pubkey, AccountSharedData> type implies that these overrides are open to be extended to any account and not just SlotHistory, in which case the assumption above might not be true any more.

Alternatively. would it make sense to tighten AccountOverrides a little bit so only explicit set of accounts can be overridden.

That was the idea, but if you think it's better to be explicit, I can certainly change up AccountOverrides to explicitly add the slot history. In that case, maybe I'm missing something, but it seems like it's in addition to the loading logic discussed earlier, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tightened up the logic even further to only add the overrides if the slot history account is present in the transaction to simulate, and added AccountOverrides as a full type

core/src/banking_stage.rs Outdated Show resolved Hide resolved
runtime/src/bank.rs Outdated Show resolved Hide resolved
@joncinque
Copy link
Contributor Author

Thanks for all the feedback everyone! Your points should all be addressed now

Copy link
Contributor

@behzadnouri behzadnouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@jstarry
Copy link
Member

jstarry commented Apr 22, 2022

I'd like to see this get backported to both v1.9 and v1.10, any objections? cc @t-nelson

@codecov
Copy link

codecov bot commented Apr 22, 2022

Codecov Report

Merging #24543 (5b29174) into master (3155270) will increase coverage by 12.0%.
The diff coverage is 81.0%.

@@             Coverage Diff             @@
##           master   #24543       +/-   ##
===========================================
+ Coverage    70.0%    82.0%    +12.0%     
===========================================
  Files          36      595      +559     
  Lines        2255   165000   +162745     
  Branches      322        0      -322     
===========================================
+ Hits         1580   135430   +133850     
- Misses        560    29570    +29010     
+ Partials      115        0      -115     

@joncinque
Copy link
Contributor Author

Per offline discussion, we'll backport to 1.10 so RPC nodes can pick it up, which are the target of the fix.

@joncinque joncinque merged commit 0d51596 into solana-labs:master Apr 22, 2022
mergify bot pushed a commit that referenced this pull request Apr 22, 2022
* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt

(cherry picked from commit 0d51596)

# Conflicts:
#	programs/bpf/Cargo.lock
#	runtime/src/bank.rs
@joncinque joncinque deleted the acct-override branch April 22, 2022 10:34
mergify bot added a commit that referenced this pull request Apr 22, 2022
#24589)

* sim: Override slot hashes account on simulation bank (#24543)

* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt

(cherry picked from commit 0d51596)

# Conflicts:
#	programs/bpf/Cargo.lock
#	runtime/src/bank.rs

* Fix merge conflicts

Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
jeffwashington pushed a commit to jeffwashington/solana that referenced this pull request Apr 22, 2022
* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt
@t-nelson
Copy link
Contributor

Per offline discussion, we'll backport to 1.10 so RPC nodes can pick it up, which are the target of the fix.

perfect. v1.9 isn't long for this world anyway

@joncinque joncinque added the v1.9 label Apr 25, 2022
mergify bot pushed a commit that referenced this pull request Apr 25, 2022
* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt

(cherry picked from commit 0d51596)

# Conflicts:
#	programs/bpf/Cargo.lock
#	runtime/src/accounts.rs
#	runtime/src/bank.rs
#	runtime/src/lib.rs
mergify bot added a commit that referenced this pull request Apr 26, 2022
#24666)

* sim: Override slot hashes account on simulation bank (#24543)

* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt

(cherry picked from commit 0d51596)

# Conflicts:
#	programs/bpf/Cargo.lock
#	runtime/src/accounts.rs
#	runtime/src/bank.rs
#	runtime/src/lib.rs

* Fix merge conflicts

* Sync lru version for build error

* cargo fmt

* Clippy

Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
jeffwashington pushed a commit to jeffwashington/solana that referenced this pull request Jun 29, 2022
* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt
jeffwashington pushed a commit to jeffwashington/solana that referenced this pull request Jun 30, 2022
* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Simulation-detection counter measures can cause validator to panic
4 participants