From a62d7647611e45e01f54035eb6ac176f51100a81 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Wed, 5 Jul 2023 20:40:31 +0200 Subject: [PATCH] whitelist certain wasms (#175) * whitelist certain wasms * use backend to hash * new whitelist approach * cleanup * add to whitelist * fix * fix * address code review * appease CI? * next try --- service/pool/Main.mo | 14 ++++++++++---- service/pool/tests/actor_class/test.sh | 8 ++++---- service/pool/tests/canisterPool.test.sh | 2 +- service/wasm-utils/Cargo.lock | 2 ++ service/wasm-utils/Cargo.toml | 2 ++ service/wasm-utils/lib.rs | 15 +++++++++++++++ service/wasm-utils/wasm-utils.did | 10 ++++++++-- src/build.ts | 3 ++- src/declarations/backend/backend.did | 2 +- src/declarations/backend/backend.did.d.ts | 5 ++++- src/declarations/backend/backend.did.js | 2 +- src/declarations/wasm-utils/wasm-utils.did | 10 ++++++++-- src/declarations/wasm-utils/wasm-utils.did.d.ts | 1 + src/declarations/wasm-utils/wasm-utils.did.js | 11 ++++++++++- 14 files changed, 69 insertions(+), 18 deletions(-) diff --git a/service/pool/Main.mo b/service/pool/Main.mo index 3cad1468..b5eca8cb 100644 --- a/service/pool/Main.mo +++ b/service/pool/Main.mo @@ -125,7 +125,7 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { await getExpiredCanisterInfo(); }; - public func installCode(info : Types.CanisterInfo, args : Types.InstallArgs, profiling : Bool) : async Types.CanisterInfo { + public shared ({ caller }) func installCode(info : Types.CanisterInfo, args : Types.InstallArgs, profiling : Bool, is_whitelisted : Bool) : async Types.CanisterInfo { if (info.timestamp == 0) { stats := Logs.updateStats(stats, #mismatch); throw Error.reject "Cannot install removed canister"; @@ -140,7 +140,13 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { limit_stable_memory_page = ?(16384 : Nat32); // Limit to 1G of stable memory backend_canister_id = ?Principal.fromActor(this); }; - let wasm = await Wasm.transform(args.wasm_module, config); + let wasm = if (caller == controller and is_whitelisted) { + args.wasm_module; + } else if (is_whitelisted) { + await Wasm.is_whitelisted(args.wasm_module); + } else { + await Wasm.transform(args.wasm_module, config); + }; let newArgs = { arg = args.arg; wasm_module = wasm; @@ -313,7 +319,7 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { switch (sanitizeInputs(caller, canister_id)) { case (#ok info) { let args = { arg; wasm_module; mode; canister_id }; - ignore await installCode(info, args, pool.profiling caller); // inherit the profiling of the parent + ignore await installCode(info, args, pool.profiling caller, false); // inherit the profiling of the parent }; case (#err makeMsg) throw Error.reject(makeMsg "install_code"); }; @@ -378,7 +384,7 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { msg : { #GCCanisters : Any; #balance : Any; - #callForward: Any; + #callForward : Any; #dump : Any; #getCanisterId : Any; #getSubtree : Any; diff --git a/service/pool/tests/actor_class/test.sh b/service/pool/tests/actor_class/test.sh index 70cd0f8a..c6f1a59b 100644 --- a/service/pool/tests/actor_class/test.sh +++ b/service/pool/tests/actor_class/test.sh @@ -9,7 +9,7 @@ let S = install(wasm, null, opt 100_000_000_000_000); let nonce = record { timestamp = 1 : int; nonce = 1 : nat }; let c1 = call S.getCanisterId(nonce); let args = record { arg = blob ""; wasm_module = parent; mode = variant { install }; canister_id = c1.id }; -call S.installCode(c1, args, false); +call S.installCode(c1, args, false, false); let c1 = c1.id; @@ -50,7 +50,7 @@ let S = install(wasm, init, opt 100_000_000_000_000); let nonce = record { timestamp = 1 : int; nonce = 1 : nat }; let c1 = call S.getCanisterId(nonce); let args = record { arg = blob ""; wasm_module = parent; mode = variant { install }; canister_id = c1.id }; -call S.installCode(c1, args, false); +call S.installCode(c1, args, false, false); let c1 = c1.id; fail call c1.makeChild(0); @@ -63,11 +63,11 @@ let S = install(wasm, null, opt 100_000_000_000_000); let nonce = record { timestamp = 1 : int; nonce = 1 : nat }; let c1 = call S.getCanisterId(nonce); let args = record { arg = blob ""; wasm_module = parent; mode = variant { install }; canister_id = c1.id }; -call S.installCode(c1, args, false); +call S.installCode(c1, args, false, false); let c2 = call S.getCanisterId(nonce); let args = record { arg = blob ""; wasm_module = deleter; mode = variant { install }; canister_id = c2.id }; -call S.installCode(c2, args, false); +call S.installCode(c2, args, false, false); let c1 = c1.id; let c2 = c2.id; diff --git a/service/pool/tests/canisterPool.test.sh b/service/pool/tests/canisterPool.test.sh index fc0cba60..2dbe7481 100644 --- a/service/pool/tests/canisterPool.test.sh +++ b/service/pool/tests/canisterPool.test.sh @@ -14,7 +14,7 @@ let init = opt record { let S = install(wasm, init, null); let nonce = record { timestamp = 1 : int; nonce = 1 : nat }; let CID = call S.getCanisterId(nonce); -call S.installCode(CID, record { arg = blob ""; wasm_module = empty_wasm; mode = variant { install }; canister_id = CID.id }, false); +call S.installCode(CID, record { arg = blob ""; wasm_module = empty_wasm; mode = variant { install }; canister_id = CID.id }, false, false); metadata(CID.id, "module_hash"); // Immediately expire diff --git a/service/wasm-utils/Cargo.lock b/service/wasm-utils/Cargo.lock index 25953cb2..fd3dece7 100644 --- a/service/wasm-utils/Cargo.lock +++ b/service/wasm-utils/Cargo.lock @@ -585,11 +585,13 @@ name = "wasm-utils" version = "0.1.0" dependencies = [ "candid", + "hex", "ic-cdk", "ic-cdk-macros", "ic-wasm", "serde", "serde_bytes", + "sha2", "walrus", ] diff --git a/service/wasm-utils/Cargo.toml b/service/wasm-utils/Cargo.toml index 6ea26ca3..6cea6e5d 100644 --- a/service/wasm-utils/Cargo.toml +++ b/service/wasm-utils/Cargo.toml @@ -8,12 +8,14 @@ path = "lib.rs" crate-type = ["cdylib"] [dependencies] +hex = "0.4.3" ic-cdk = "0.8.0-beta.0" ic-cdk-macros = "0.8.0-beta.0" serde = "1.0" serde_bytes = "0.11" candid = "0.9.0-beta.2" ic-wasm = { version = "0.3.7", default-features = false } +sha2 = "0.10.6" walrus = "0.19" [profile.release] diff --git a/service/wasm-utils/lib.rs b/service/wasm-utils/lib.rs index f6496da2..3ef6a6e6 100644 --- a/service/wasm-utils/lib.rs +++ b/service/wasm-utils/lib.rs @@ -2,6 +2,7 @@ use candid::{CandidType, Deserialize}; use serde_bytes::ByteBuf; use ic_wasm::*; +use sha2::Digest; #[derive(CandidType, Deserialize)] struct Config { @@ -11,6 +12,20 @@ struct Config { backend_canister_id: Option, } +const WHITELISTED_WASMS: [&str; 1] = [ + "651425d92d3796ddae581191452e0e87484eeff4ff6352fe9a59c7e1f97a2310", // dfx 0.14.1 frontend canister +]; + +#[ic_cdk_macros::query] +fn is_whitelisted(wasm: ByteBuf) -> ByteBuf { + let wasm_hash = hex::encode(sha2::Sha256::digest(&wasm)); + if WHITELISTED_WASMS.contains(&wasm_hash.as_str()) { + wasm + } else { + ic_cdk::trap("Wasm is not whitelisted") + } +} + #[ic_cdk_macros::query] fn transform(wasm: ByteBuf, config: Config) -> ByteBuf { let mut m = walrus::Module::from_buffer(&wasm).unwrap(); diff --git a/service/wasm-utils/wasm-utils.did b/service/wasm-utils/wasm-utils.did index 35463025..76c7913b 100644 --- a/service/wasm-utils/wasm-utils.did +++ b/service/wasm-utils/wasm-utils.did @@ -1,5 +1,11 @@ -type Config = record { profiling: bool; remove_cycles_add: bool; limit_stable_memory_page: opt nat32; backend_canister_id: opt principal}; +type Config = record { + profiling : bool; + remove_cycles_add : bool; + limit_stable_memory_page : opt nat32; + backend_canister_id : opt principal; +}; service : { - transform : (blob, Config) -> (blob); + transform : (blob, Config) -> (blob) query; + is_whitelisted : (blob) -> (blob) query; } diff --git a/src/build.ts b/src/build.ts index a33b3711..6d04c97b 100644 --- a/src/build.ts +++ b/src/build.ts @@ -190,7 +190,8 @@ async function install( const new_info = await backend.installCode( canisterInfo, installArgs, - profiling + profiling, + false ); canisterInfo = new_info; logger.log(`Code installed at canister id ${canisterInfo.id}`); diff --git a/src/declarations/backend/backend.did b/src/declarations/backend/backend.did index 2e5c4b5b..11ad3ca8 100644 --- a/src/declarations/backend/backend.did +++ b/src/declarations/backend/backend.did @@ -53,7 +53,7 @@ type Self = vec CanisterInfo; }) query; http_request: (HttpRequest) -> (HttpResponse) query; - installCode: (CanisterInfo, InstallArgs, bool) -> (CanisterInfo); + installCode: (CanisterInfo, InstallArgs, bool, bool) -> (CanisterInfo); install_code: (record { arg: blob; diff --git a/src/declarations/backend/backend.did.d.ts b/src/declarations/backend/backend.did.d.ts index d7f50a92..5187a964 100644 --- a/src/declarations/backend/backend.did.d.ts +++ b/src/declarations/backend/backend.did.d.ts @@ -64,7 +64,10 @@ export interface Self { Array<[Principal, Array]> >; http_request: ActorMethod<[HttpRequest], HttpResponse>; - installCode: ActorMethod<[CanisterInfo, InstallArgs, boolean], CanisterInfo>; + installCode: ActorMethod< + [CanisterInfo, InstallArgs, boolean, boolean], + CanisterInfo + >; install_code: ActorMethod< [ { diff --git a/src/declarations/backend/backend.did.js b/src/declarations/backend/backend.did.js index 2f3a5cf4..0009c2c3 100644 --- a/src/declarations/backend/backend.did.js +++ b/src/declarations/backend/backend.did.js @@ -100,7 +100,7 @@ export const idlFactory = ({ IDL }) => { ), http_request: IDL.Func([HttpRequest], [HttpResponse], ["query"]), installCode: IDL.Func( - [CanisterInfo, InstallArgs, IDL.Bool], + [CanisterInfo, InstallArgs, IDL.Bool, IDL.Bool], [CanisterInfo], [] ), diff --git a/src/declarations/wasm-utils/wasm-utils.did b/src/declarations/wasm-utils/wasm-utils.did index 35463025..76c7913b 100644 --- a/src/declarations/wasm-utils/wasm-utils.did +++ b/src/declarations/wasm-utils/wasm-utils.did @@ -1,5 +1,11 @@ -type Config = record { profiling: bool; remove_cycles_add: bool; limit_stable_memory_page: opt nat32; backend_canister_id: opt principal}; +type Config = record { + profiling : bool; + remove_cycles_add : bool; + limit_stable_memory_page : opt nat32; + backend_canister_id : opt principal; +}; service : { - transform : (blob, Config) -> (blob); + transform : (blob, Config) -> (blob) query; + is_whitelisted : (blob) -> (blob) query; } diff --git a/src/declarations/wasm-utils/wasm-utils.did.d.ts b/src/declarations/wasm-utils/wasm-utils.did.d.ts index a2c43c5d..632d8a26 100644 --- a/src/declarations/wasm-utils/wasm-utils.did.d.ts +++ b/src/declarations/wasm-utils/wasm-utils.did.d.ts @@ -8,6 +8,7 @@ export interface Config { limit_stable_memory_page: [] | [number]; } export interface _SERVICE { + is_whitelisted: ActorMethod<[Uint8Array | number[]], Uint8Array | number[]>; transform: ActorMethod< [Uint8Array | number[], Config], Uint8Array | number[] diff --git a/src/declarations/wasm-utils/wasm-utils.did.js b/src/declarations/wasm-utils/wasm-utils.did.js index b2eff185..2dd1eaed 100644 --- a/src/declarations/wasm-utils/wasm-utils.did.js +++ b/src/declarations/wasm-utils/wasm-utils.did.js @@ -6,7 +6,16 @@ export const idlFactory = ({ IDL }) => { limit_stable_memory_page: IDL.Opt(IDL.Nat32), }); return IDL.Service({ - transform: IDL.Func([IDL.Vec(IDL.Nat8), Config], [IDL.Vec(IDL.Nat8)], []), + is_whitelisted: IDL.Func( + [IDL.Vec(IDL.Nat8)], + [IDL.Vec(IDL.Nat8)], + ["query"] + ), + transform: IDL.Func( + [IDL.Vec(IDL.Nat8), Config], + [IDL.Vec(IDL.Nat8)], + ["query"] + ), }); }; export const init = ({ IDL }) => {