Skip to content

Commit 16d03b2

Browse files
committed
Add actor call stack to call manager + detect reentrant upgrades in syscall
1 parent c7e3498 commit 16d03b2

File tree

8 files changed

+67
-36
lines changed

8 files changed

+67
-36
lines changed

fvm/src/call_manager/default.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use fvm_shared::event::StampedEvent;
1414
use fvm_shared::sys::BlockId;
1515
use fvm_shared::{ActorID, METHOD_SEND};
1616
use num_traits::Zero;
17-
use std::collections::HashMap;
1817

1918
use super::state_access_tracker::{ActorAccessState, StateAccessTracker};
2019
use super::{Backtrace, CallManager, Entrypoint, InvocationResult, NO_DATA_BLOCK_ID};
@@ -76,8 +75,8 @@ pub struct InnerDefaultCallManager<M: Machine> {
7675
limits: M::Limiter,
7776
/// Accumulator for events emitted in this call stack.
7877
events: EventsAccumulator,
79-
/// A map of ActorID and how often they appear on the call stack.
80-
actor_call_stack: HashMap<ActorID, i32>,
78+
/// The actor call stack (ActorID and entrypoint name tuple).
79+
actor_call_stack: Vec<(ActorID, &'static str)>,
8180
}
8281

8382
#[doc(hidden)]
@@ -162,7 +161,7 @@ where
162161
limits,
163162
events: Default::default(),
164163
state_access_tracker,
165-
actor_call_stack: HashMap::new(),
164+
actor_call_stack: vec![],
166165
})))
167166
}
168167

@@ -331,12 +330,16 @@ where
331330
self.nonce
332331
}
333332

334-
fn get_actor_call_stack(&self) -> &HashMap<ActorID, i32> {
333+
fn get_actor_call_stack(&self) -> &Vec<(ActorID, &'static str)> {
335334
&self.actor_call_stack
336335
}
337336

338-
fn get_actor_call_stack_mut(&mut self) -> &mut HashMap<ActorID, i32> {
339-
&mut self.actor_call_stack
337+
fn actor_call_stack_push(&mut self, actor_id: ActorID, entrypoint: &Entrypoint) -> () {
338+
self.actor_call_stack
339+
.push((actor_id, entrypoint.func_name()))
340+
}
341+
fn actor_call_stack_pop(&mut self) -> Option<(ActorID, &'static str)> {
342+
self.actor_call_stack.pop()
340343
}
341344

342345
fn next_actor_address(&self) -> Address {
@@ -639,7 +642,11 @@ where
639642
},
640643
};
641644

642-
self.send_resolved::<K>(from, to, entrypoint, params, value, read_only)
645+
self.actor_call_stack_push(to, &entrypoint);
646+
let res = self.send_resolved::<K>(from, to, entrypoint, params, value, read_only);
647+
self.actor_call_stack_pop();
648+
649+
res
643650
}
644651

645652
/// Send with resolved addresses.

fvm/src/call_manager/mod.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use fvm_shared::econ::TokenAmount;
77
use fvm_shared::error::ExitCode;
88
use fvm_shared::upgrade::UpgradeInfo;
99
use fvm_shared::{ActorID, MethodNum};
10-
use std::collections::HashMap;
1110

1211
use crate::engine::Engine;
1312
use crate::gas::{Gas, GasCharge, GasTimer, GasTracker, PriceList};
@@ -120,8 +119,17 @@ pub trait CallManager: 'static {
120119
delegated_address: Option<Address>,
121120
) -> Result<()>;
122121

123-
fn get_actor_call_stack(&self) -> &HashMap<ActorID, i32>;
124-
fn get_actor_call_stack_mut(&mut self) -> &mut HashMap<ActorID, i32>;
122+
/// Based on current actor stack, checks if the actor with the given ID can be upgraded.
123+
//fn can_actor_upgrade(&self, actor_id: ActorID) -> bool;
124+
125+
// returns the actor call stack
126+
fn get_actor_call_stack(&self) -> &Vec<(ActorID, &'static str)>;
127+
128+
/// add an actor to the calling stack.
129+
fn actor_call_stack_push(&mut self, actor_id: ActorID, entrypoint: &Entrypoint) -> ();
130+
131+
/// pop an actor from the calling stack.
132+
fn actor_call_stack_pop(&mut self) -> Option<(ActorID, &'static str)>;
125133

126134
/// Resolve an address into an actor ID, charging gas as appropriate.
127135
fn resolve_address(&self, address: &Address) -> Result<Option<ActorID>>;
@@ -210,6 +218,9 @@ pub enum Entrypoint {
210218
Upgrade(UpgradeInfo),
211219
}
212220

221+
pub static INVOKE_FUNC_NAME: &str = "invoke";
222+
pub static UPGRADE_FUNC_NAME: &str = "upgrade";
223+
213224
impl std::fmt::Display for Entrypoint {
214225
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215226
match self {
@@ -229,8 +240,8 @@ impl Entrypoint {
229240

230241
fn func_name(&self) -> &'static str {
231242
match self {
232-
Entrypoint::Invoke(_) => "invoke",
233-
Entrypoint::Upgrade(_) => "upgrade",
243+
Entrypoint::Invoke(_) => INVOKE_FUNC_NAME,
244+
Entrypoint::Upgrade(_) => UPGRADE_FUNC_NAME,
234245
}
235246
}
236247

fvm/src/kernel/default.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ use super::blocks::{Block, BlockRegistry};
3030
use super::error::Result;
3131
use super::hash::SupportedHashes;
3232
use super::*;
33-
use crate::call_manager::{CallManager, Entrypoint, InvocationResult, NO_DATA_BLOCK_ID};
33+
use crate::call_manager::{
34+
CallManager, Entrypoint, InvocationResult, INVOKE_FUNC_NAME, NO_DATA_BLOCK_ID,
35+
UPGRADE_FUNC_NAME,
36+
};
3437
use crate::externs::{Chain, Consensus, Rand};
3538
use crate::gas::GasTimer;
3639
use crate::init_actor::INIT_ACTOR_ID;
@@ -137,12 +140,6 @@ where
137140
return Err(syscall_error!(LimitExceeded; "cannot store return block").into());
138141
}
139142

140-
self.call_manager
141-
.get_actor_call_stack_mut()
142-
.entry(self.actor_id)
143-
.and_modify(|count| *count += 1)
144-
.or_insert(1);
145-
146143
// Send.
147144
let result = self.call_manager.with_transaction(|cm| {
148145
cm.call_actor::<K>(
@@ -879,12 +876,26 @@ where
879876
.create_actor(code_id, actor_id, delegated_address)
880877
}
881878

882-
fn is_actor_on_call_stack(&self) -> bool {
883-
self.call_manager
884-
.get_actor_call_stack()
885-
.get(&self.actor_id)
886-
.map(|count| *count > 0)
887-
.unwrap_or(false)
879+
fn can_actor_upgrade(&self) -> bool {
880+
let mut iter = self.call_manager.get_actor_call_stack().iter();
881+
882+
// find the first position of this actor on the call stack
883+
let position = iter.position(|&tuple| tuple == (self.actor_id, INVOKE_FUNC_NAME));
884+
if position.is_none() {
885+
return true;
886+
}
887+
888+
// make sure that no other actor appears on the call stack after 'position' (unless its
889+
// a recursive upgrade call which is allowed)
890+
while let Some(tuple) = iter.next() {
891+
if tuple.0 != self.actor_id {
892+
return false;
893+
} else if tuple.1 != UPGRADE_FUNC_NAME {
894+
return false;
895+
}
896+
}
897+
898+
true
888899
}
889900

890901
fn upgrade_actor<K: Kernel>(

fvm/src/kernel/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ pub trait ActorOps {
215215
params_id: BlockId,
216216
) -> Result<SendResult>;
217217

218-
fn is_actor_on_call_stack(&self) -> bool;
218+
fn can_actor_upgrade(&self) -> bool;
219219

220220
/// Installs actor code pointed by cid
221221
#[cfg(feature = "m2-native")]

fvm/src/syscalls/actor.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,10 @@ pub fn upgrade_actor<K: Kernel>(
121121
Err(err) => return err.into(),
122122
};
123123

124-
// actor cannot be upgraded if its already on the call stack
125-
if context.kernel.is_actor_on_call_stack() {
124+
if !context.kernel.can_actor_upgrade() {
126125
return ControlFlow::Error(syscall_error!(
127126
Forbidden;
128-
"cannot upgrade actor if it is already on the call stack"
127+
"calling upgrade on actor already on call stack is forbidden"
129128
));
130129
};
131130

fvm/tests/dummy.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0, MIT
33
use std::borrow::Borrow;
44
use std::cell::RefCell;
5-
use std::collections::HashMap;
65
use std::rc::Rc;
76

87
use anyhow::Context;
@@ -359,11 +358,15 @@ impl CallManager for DummyCallManager {
359358
todo!()
360359
}
361360

362-
fn get_actor_call_stack(&self) -> &HashMap<ActorID, i32> {
361+
fn get_actor_call_stack(&self) -> &Vec<(ActorID, &'static str)> {
363362
todo!()
364363
}
365364

366-
fn get_actor_call_stack_mut(&mut self) -> &mut HashMap<ActorID, i32> {
365+
fn actor_call_stack_push(&mut self, _actor_id: ActorID, _entrypoint: &Entrypoint) {
366+
todo!()
367+
}
368+
369+
fn actor_call_stack_pop(&mut self) -> Option<(ActorID, &'static str)> {
367370
todo!()
368371
}
369372

testing/conformance/src/vm.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,8 @@ where
303303
self.0.upgrade_actor::<Self>(new_code_cid, params_id)
304304
}
305305

306-
fn is_actor_on_call_stack(&self) -> bool {
307-
self.0.is_actor_on_call_stack()
306+
fn can_actor_upgrade(&self) -> bool {
307+
self.0.can_actor_upgrade()
308308
}
309309
}
310310

testing/integration/tests/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ fn upgrade_actor_test() {
11891189
.unwrap();
11901190

11911191
let val: i64 = res.msg_receipt.return_data.deserialize().unwrap();
1192-
assert_eq!(val, 444);
1192+
assert_eq!(val, 444, "{:?}", res.failure_info);
11931193

11941194
assert!(
11951195
res.msg_receipt.exit_code.is_success(),

0 commit comments

Comments
 (0)