Add simulation detection countermeasure (backport #22880) (#23143)

* Add simulation detection countermeasure (#22880)

* Add simulation detection countermeasures

* Add program and test using TestValidator

* Remove incinerator deposit

* Remove incinerator

* Update Cargo.lock

* Add more features to simulation bank

* Update Cargo.lock per rebase

Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
(cherry picked from commit c42b80f099)

# Conflicts:
#	programs/bpf/Cargo.lock
#	programs/bpf/Cargo.toml

* Update Cargo.lock

Co-authored-by: Michael Vines <mvines@gmail.com>
Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
This commit is contained in:
mergify[bot]
2022-02-17 14:45:24 +00:00
committed by GitHub
parent f629c71849
commit 0fdbec9735
11 changed files with 2905 additions and 196 deletions

View File

@ -198,9 +198,16 @@ impl Accounts {
}
}
pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
pub fn new_from_parent(
parent: &Accounts,
slot: Slot,
parent_slot: Slot,
simulation_bank: bool,
) -> Self {
let accounts_db = parent.accounts_db.clone();
accounts_db.set_hash(slot, parent_slot);
if !simulation_bank {
accounts_db.set_hash(slot, parent_slot);
}
Self {
accounts_db,
account_locks: Mutex::new(AccountLocks::default()),

View File

@ -1020,6 +1020,18 @@ pub trait DropCallback: fmt::Debug {
fn clone_box(&self) -> Box<dyn DropCallback + Send + Sync>;
}
/// Noop callback on dropping banks is useful for simulation banks, which are
/// new banks created from a frozen bank, but should not be purged in the same
/// way.
#[derive(Debug, Clone)]
struct NoopDropCallback;
impl DropCallback for NoopDropCallback {
fn callback(&self, _b: &Bank) {}
fn clone_box(&self) -> Box<dyn DropCallback + Send + Sync> {
Box::new(self.clone())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, AbiExample, Clone, Copy)]
pub struct RewardInfo {
pub reward_type: RewardType,
@ -1238,6 +1250,7 @@ struct LoadVoteAndStakeAccountsResult {
#[derive(Debug, Default)]
pub struct NewBankOptions {
pub vote_only_bank: bool,
pub simulation_bank: bool,
}
impl Bank {
@ -1508,7 +1521,10 @@ impl Bank {
new_bank_options: NewBankOptions,
) -> Self {
let mut time = Measure::start("bank::new_from_parent");
let NewBankOptions { vote_only_bank } = new_bank_options;
let NewBankOptions {
vote_only_bank,
simulation_bank,
} = new_bank_options;
parent.freeze();
assert_ne!(slot, parent.slot());
@ -1522,6 +1538,7 @@ impl Bank {
&parent.rc.accounts,
slot,
parent.slot(),
simulation_bank,
)),
parent: RwLock::new(Some(parent.clone())),
slot,
@ -1611,6 +1628,20 @@ impl Bank {
let (feature_set, feature_set_time) =
Measure::this(|_| parent.feature_set.clone(), (), "feature_set_creation");
let drop_callback = if simulation_bank {
RwLock::new(OptionalDropCallback(Some(Box::new(NoopDropCallback))))
} else {
RwLock::new(OptionalDropCallback(
parent
.drop_callback
.read()
.unwrap()
.0
.as_ref()
.map(|drop_callback| drop_callback.clone_box()),
))
};
let mut new = Bank {
rc,
src,
@ -1664,15 +1695,7 @@ impl Bank {
transaction_log_collector_config,
transaction_log_collector: Arc::new(RwLock::new(TransactionLogCollector::default())),
feature_set,
drop_callback: RwLock::new(OptionalDropCallback(
parent
.drop_callback
.read()
.unwrap()
.0
.as_ref()
.map(|drop_callback| drop_callback.clone_box()),
)),
drop_callback,
freeze_started: AtomicBool::new(false),
cost_tracker: RwLock::new(CostTracker::default()),
sysvar_cache: RwLock::new(SysvarCache::default()),
@ -3479,12 +3502,26 @@ impl Bank {
/// Run transactions against a frozen bank without committing the results
pub fn simulate_transaction(
&self,
self: &Arc<Bank>,
transaction: SanitizedTransaction,
) -> TransactionSimulationResult {
assert!(self.is_frozen(), "simulation bank must be frozen");
self.simulate_transaction_unchecked(transaction)
// Simulation detection countermeasure 1: Create a new child bank for the simulation. This
// ensures comparing the slot values between the Clock and SlotHistory sysvars does not
// reveal that the program is running in simulation.
//
// Reference: https://opcodes.fr/en/publications/2022-01/detecting-transaction-simulation/
let bank = Bank::new_from_parent_with_options(
self,
&Pubkey::default(),
self.slot().saturating_add(1),
NewBankOptions {
simulation_bank: true,
..NewBankOptions::default()
},
);
bank.simulate_transaction_unchecked(transaction)
}
/// Run transactions against a bank without committing the results; does not check if the bank