@@ -5,6 +5,7 @@ use walrus::*;
55
66pub struct Config {
77 pub remove_cycles_add : bool ,
8+ pub filter_cycles_add : bool ,
89 pub limit_stable_memory_page : Option < u32 > ,
910 pub limit_heap_memory_page : Option < u32 > ,
1011 pub playground_canister_id : Option < candid:: Principal > ,
@@ -48,6 +49,25 @@ pub fn limit_resource(m: &mut Module, config: &Config) {
4849 make_cycles_burn128 ( m, & mut replacer, wasm64) ;
4950 }
5051
52+ if config. filter_cycles_add {
53+ // Create a private global set in every invokation of `ic0.call_new` to
54+ // - 0 if subsequent calls to `ic0.call_cycles_add[128]` are allowed;
55+ // - 1 if subsequent calls to `ic0.call_cycles_add[128]` should be filtered (i.e., turned into no-op).
56+ let global_id = m. globals . add_local (
57+ ValType :: I32 ,
58+ true , // mutable
59+ false , // shared (not supported yet)
60+ ConstExpr :: Value ( Value :: I32 ( 0 ) ) ,
61+ ) ;
62+ // Instrument `ic0.call_cycles_add[128]` to respect the value of the private global.
63+ make_filter_cycles_add ( m, & mut replacer, wasm64, global_id) ;
64+ make_filter_cycles_add128 ( m, & mut replacer, global_id) ;
65+ // Calls to `ic0.cycles_burn128` are always filtered (i.e., turned into no-op).
66+ make_cycles_burn128 ( m, & mut replacer, wasm64) ;
67+ // Instrument `ic0.call_new` to set the private global.
68+ make_filter_call_new ( m, & mut replacer, wasm64, global_id) ;
69+ }
70+
5171 if let Some ( limit) = config. limit_stable_memory_page {
5272 make_stable_grow ( m, & mut replacer, wasm64, limit as i32 ) ;
5373 make_stable64_grow ( m, & mut replacer, limit as i64 ) ;
@@ -126,6 +146,38 @@ fn make_cycles_add(m: &mut Module, replacer: &mut Replacer, wasm64: bool) {
126146 }
127147}
128148
149+ fn make_filter_cycles_add (
150+ m : & mut Module ,
151+ replacer : & mut Replacer ,
152+ wasm64 : bool ,
153+ global_id : GlobalId ,
154+ ) {
155+ if let Some ( old_cycles_add) = get_ic_func_id ( m, "call_cycles_add" ) {
156+ if wasm64 {
157+ panic ! ( "Wasm64 module should not call `call_cycles_add`" ) ;
158+ }
159+ let mut builder = FunctionBuilder :: new ( & mut m. types , & [ ValType :: I64 ] , & [ ] ) ;
160+ let amount = m. locals . add ( ValType :: I64 ) ;
161+ let mut func = builder. func_body ( ) ;
162+ // Compare to zero
163+ func. global_get ( global_id) ;
164+ func. i32_const ( 0 ) ;
165+ func. binop ( BinaryOp :: I32Ne ) ;
166+ // If block
167+ func. if_else (
168+ None ,
169+ |then| {
170+ then. local_get ( amount) . drop ( ) ; // no-op
171+ } ,
172+ |otherwise| {
173+ otherwise. local_get ( amount) . call ( old_cycles_add) ; // call `ic0.call_cycles_add`
174+ } ,
175+ ) ;
176+ let new_cycles_add = builder. finish ( vec ! [ amount] , & mut m. funcs ) ;
177+ replacer. add ( old_cycles_add, new_cycles_add) ;
178+ }
179+ }
180+
129181fn make_cycles_add128 ( m : & mut Module , replacer : & mut Replacer ) {
130182 if let Some ( old_cycles_add128) = get_ic_func_id ( m, "call_cycles_add128" ) {
131183 let mut builder = FunctionBuilder :: new ( & mut m. types , & [ ValType :: I64 , ValType :: I64 ] , & [ ] ) ;
@@ -142,8 +194,36 @@ fn make_cycles_add128(m: &mut Module, replacer: &mut Replacer) {
142194 }
143195}
144196
197+ fn make_filter_cycles_add128 ( m : & mut Module , replacer : & mut Replacer , global_id : GlobalId ) {
198+ if let Some ( old_cycles_add128) = get_ic_func_id ( m, "call_cycles_add128" ) {
199+ let mut builder = FunctionBuilder :: new ( & mut m. types , & [ ValType :: I64 , ValType :: I64 ] , & [ ] ) ;
200+ let high = m. locals . add ( ValType :: I64 ) ;
201+ let low = m. locals . add ( ValType :: I64 ) ;
202+ let mut func = builder. func_body ( ) ;
203+ // Compare to zero
204+ func. global_get ( global_id) ;
205+ func. i32_const ( 0 ) ;
206+ func. binop ( BinaryOp :: I32Ne ) ;
207+ // If block
208+ func. if_else (
209+ None ,
210+ |then| {
211+ then. local_get ( high) . local_get ( low) . drop ( ) . drop ( ) ; // no-op
212+ } ,
213+ |otherwise| {
214+ otherwise
215+ . local_get ( high)
216+ . local_get ( low)
217+ . call ( old_cycles_add128) ; // call `ic0.call_cycles_add128`
218+ } ,
219+ ) ;
220+ let new_cycles_add128 = builder. finish ( vec ! [ high, low] , & mut m. funcs ) ;
221+ replacer. add ( old_cycles_add128, new_cycles_add128) ;
222+ }
223+ }
224+
145225fn make_cycles_burn128 ( m : & mut Module , replacer : & mut Replacer , wasm64 : bool ) {
146- if let Some ( older_cycles_burn128) = get_ic_func_id ( m, "call_cycles_burn128 " ) {
226+ if let Some ( older_cycles_burn128) = get_ic_func_id ( m, "cycles_burn128 " ) {
147227 let dst_type = match wasm64 {
148228 true => ValType :: I64 ,
149229 false => ValType :: I32 ,
@@ -592,6 +672,138 @@ fn make_redirect_call_new(
592672 }
593673}
594674
675+ fn make_filter_call_new (
676+ m : & mut Module ,
677+ replacer : & mut Replacer ,
678+ wasm64 : bool ,
679+ global_id : GlobalId ,
680+ ) {
681+ if let Some ( old_call_new) = get_ic_func_id ( m, "call_new" ) {
682+ let pointer_type = match wasm64 {
683+ true => ValType :: I64 ,
684+ false => ValType :: I32 ,
685+ } ;
686+ // Specify the same args as `call_new` so that WASM will correctly check mismatching args
687+ let callee_src = m. locals . add ( pointer_type) ;
688+ let callee_size = m. locals . add ( pointer_type) ;
689+ let name_src = m. locals . add ( pointer_type) ;
690+ let name_size = m. locals . add ( pointer_type) ;
691+ let arg5 = m. locals . add ( pointer_type) ;
692+ let arg6 = m. locals . add ( pointer_type) ;
693+ let arg7 = m. locals . add ( pointer_type) ;
694+ let arg8 = m. locals . add ( pointer_type) ;
695+
696+ let memory = m
697+ . get_memory_id ( )
698+ . expect ( "Canister Wasm module should have only one memory" ) ;
699+
700+ // Scratch variables
701+ let not_allowed_canister = m. locals . add ( ValType :: I32 ) ;
702+ let allow_cycles = m. locals . add ( ValType :: I32 ) ;
703+
704+ // Cycles transfer is only allowed if
705+ // - the callee is the management canister or `7hfb6-caaaa-aaaar-qadga-cai` (EVM RPC Canister);
706+ // - *and* the method name is *neither* `create_canister` *nor* `deposit_cycles`.
707+ let allowed_canisters = [
708+ Principal :: from_slice ( & [ ] ) ,
709+ Principal :: from_text ( "7hfb6-caaaa-aaaar-qadga-cai" ) . unwrap ( ) ,
710+ ] ;
711+ let forbidden_function_names = [ "create_canister" , "deposit_cycles" ] ;
712+
713+ let mut builder = FunctionBuilder :: new (
714+ & mut m. types ,
715+ & [
716+ pointer_type,
717+ pointer_type,
718+ pointer_type,
719+ pointer_type,
720+ pointer_type,
721+ pointer_type,
722+ pointer_type,
723+ pointer_type,
724+ ] ,
725+ & [ ] ,
726+ ) ;
727+
728+ builder
729+ . func_body ( )
730+ . block ( None , |checks| {
731+ let checks_id = checks. id ( ) ;
732+ // Check if callee is an allowed canister
733+ checks
734+ . block ( None , |id_check| {
735+ // no match (i.e., callee not in `allowed_canisters`) => `not_allowed_canister` set to 1
736+ check_list (
737+ memory,
738+ id_check,
739+ not_allowed_canister,
740+ callee_size,
741+ callee_src,
742+ None ,
743+ & allowed_canisters
744+ . iter ( )
745+ . map ( |p| p. as_slice ( ) )
746+ . collect :: < Vec < _ > > ( ) ,
747+ wasm64,
748+ ) ;
749+ } )
750+ . local_get ( not_allowed_canister)
751+ . br_if ( checks_id) ; // we already know that callee is not allowed => no need to check further
752+
753+ // Callee is an allowed canister => check if method name is not forbidden
754+ // no match (i.e., method name not in `forbidden_function_names`) => `allow_cycles` set to 1
755+ check_list (
756+ memory,
757+ checks,
758+ allow_cycles,
759+ name_size,
760+ name_src,
761+ None ,
762+ & forbidden_function_names
763+ . iter ( )
764+ . map ( |s| s. as_bytes ( ) )
765+ . collect :: < Vec < _ > > ( ) ,
766+ wasm64,
767+ ) ;
768+ } )
769+ . local_get ( allow_cycles)
770+ . if_else (
771+ None ,
772+ |block| {
773+ // set global to 0 => allow `ic0.call_cycles_add[128]`
774+ block. i32_const ( 0 ) . global_set ( global_id) ;
775+ } ,
776+ |block| {
777+ // set global to 1 => filter `ic0.call_cycles_add[128]`
778+ block. i32_const ( 1 ) . global_set ( global_id) ;
779+ } ,
780+ )
781+ . local_get ( callee_src)
782+ . local_get ( callee_size)
783+ . local_get ( name_src)
784+ . local_get ( name_size)
785+ . local_get ( arg5)
786+ . local_get ( arg6)
787+ . local_get ( arg7)
788+ . local_get ( arg8)
789+ . call ( old_call_new) ;
790+ let new_call_new = builder. finish (
791+ vec ! [
792+ callee_src,
793+ callee_size,
794+ name_src,
795+ name_size,
796+ arg5,
797+ arg6,
798+ arg7,
799+ arg8,
800+ ] ,
801+ & mut m. funcs ,
802+ ) ;
803+ replacer. add ( old_call_new, new_call_new) ;
804+ }
805+ }
806+
595807/// Get the FuncionId of a system API in ic0 import.
596808///
597809/// If stable_size or stable64_size is not imported, add them to the module.
0 commit comments