diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 16c1ea9d1ce97..5a2d8c5b48440 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -300,6 +300,14 @@ cargo-check-try-runtime: - time cargo check --features try-runtime - sccache -s +cargo-check-wasmer-sandbox: + stage: test + <<: *docker-env + <<: *test-refs + script: + - time cargo check --features wasmer-sandbox + - sccache -s + test-deterministic-wasm: stage: test <<: *docker-env diff --git a/Cargo.lock b/Cargo.lock index e1f5b2f815822..19d2062b0afdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -917,7 +917,7 @@ dependencies = [ "ansi_term 0.11.0", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -988,13 +988,41 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +[[package]] +name = "cranelift-bforest" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9221545c0507dc08a62b2d8b5ffe8e17ac580b0a74d1813b496b8d70b070fbd0" +dependencies = [ + "cranelift-entity 0.68.0", +] + [[package]] name = "cranelift-bforest" version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ca3560686e7c9c7ed7e0fe77469f2410ba5d7781b1acaa9adc8d8deea28e3e" dependencies = [ - "cranelift-entity", + "cranelift-entity 0.74.0", +] + +[[package]] +name = "cranelift-codegen" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9936ea608b6cd176f107037f6adbb4deac933466fc7231154f96598b2d3ab1" +dependencies = [ + "byteorder", + "cranelift-bforest 0.68.0", + "cranelift-codegen-meta 0.68.0", + "cranelift-codegen-shared 0.68.0", + "cranelift-entity 0.68.0", + "gimli 0.22.0", + "log 0.4.14", + "regalloc", + "smallvec 1.6.1", + "target-lexicon 0.11.2", + "thiserror", ] [[package]] @@ -1003,16 +1031,26 @@ version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf9bf1ffffb6ce3d2e5ebc83549bd2436426c99b31cc550d521364cbe35d276" dependencies = [ - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-entity", + "cranelift-bforest 0.74.0", + "cranelift-codegen-meta 0.74.0", + "cranelift-codegen-shared 0.74.0", + "cranelift-entity 0.74.0", "gimli 0.24.0", "log 0.4.14", "regalloc", "serde", "smallvec 1.6.1", - "target-lexicon", + "target-lexicon 0.12.0", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef2b2768568306540f4c8db3acce9105534d34c4a1e440529c1e702d7f8c8d7" +dependencies = [ + "cranelift-codegen-shared 0.68.0", + "cranelift-entity 0.68.0", ] [[package]] @@ -1021,10 +1059,16 @@ version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cc21936a5a6d07e23849ffe83e5c1f6f50305c074f4b2970ca50c13bf55b821" dependencies = [ - "cranelift-codegen-shared", - "cranelift-entity", + "cranelift-codegen-shared 0.74.0", + "cranelift-entity 0.74.0", ] +[[package]] +name = "cranelift-codegen-shared" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6759012d6d19c4caec95793f052613e9d4113e925e7f14154defbac0f1d4c938" + [[package]] name = "cranelift-codegen-shared" version = "0.74.0" @@ -1034,6 +1078,15 @@ dependencies = [ "serde", ] +[[package]] +name = "cranelift-entity" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86badbce14e15f52a45b666b38abe47b204969dd7f8fb7488cb55dd46b361fa6" +dependencies = [ + "serde", +] + [[package]] name = "cranelift-entity" version = "0.74.0" @@ -1043,16 +1096,28 @@ dependencies = [ "serde", ] +[[package]] +name = "cranelift-frontend" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b608bb7656c554d0a4cf8f50c7a10b857e80306f6ff829ad6d468a7e2323c8d8" +dependencies = [ + "cranelift-codegen 0.68.0", + "log 0.4.14", + "smallvec 1.6.1", + "target-lexicon 0.11.2", +] + [[package]] name = "cranelift-frontend" version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b783b351f966fce33e3c03498cb116d16d97a8f9978164a60920bd0d3a99c" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.74.0", "log 0.4.14", "smallvec 1.6.1", - "target-lexicon", + "target-lexicon 0.12.0", ] [[package]] @@ -1061,8 +1126,8 @@ version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a77c88d3dd48021ff1e37e978a00098524abd3513444ae252c08d37b310b3d2a" dependencies = [ - "cranelift-codegen", - "target-lexicon", + "cranelift-codegen 0.74.0", + "target-lexicon 0.12.0", ] [[package]] @@ -1071,15 +1136,15 @@ version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb6d408e2da77cdbbd65466298d44c86ae71c1785d2ab0d8657753cdb4d9d89" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.74.0", + "cranelift-entity 0.74.0", + "cranelift-frontend 0.74.0", "itertools 0.10.0", "log 0.4.14", "serde", "smallvec 1.6.1", "thiserror", - "wasmparser", + "wasmparser 0.78.2", ] [[package]] @@ -1333,6 +1398,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "darling" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -1496,6 +1596,32 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +[[package]] +name = "dynasm" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc2d9a5e44da60059bd38db2d05cbb478619541b8c79890547861ec1e3194f0" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "proc-macro-error 1.0.4", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42276e3f205fe63887cca255aa9a65a63fb72764c30b9a6252a7c7e46994f689" +dependencies = [ + "byteorder", + "dynasm", + "memmap2", +] + [[package]] name = "ed25519" version = "1.0.3" @@ -1557,6 +1683,27 @@ dependencies = [ "syn", ] +[[package]] +name = "enumset" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -2248,6 +2395,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.23.0" @@ -2560,6 +2718,12 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.1.5" @@ -3069,6 +3233,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + [[package]] name = "libloading" version = "0.7.0" @@ -4647,6 +4821,16 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" +dependencies = [ + "crc32fast", + "indexmap", +] + [[package]] name = "object" version = "0.23.0" @@ -7631,6 +7815,8 @@ dependencies = [ "sp-serializer", "sp-wasm-interface", "thiserror", + "wasmer", + "wasmer-compiler-singlepass", "wasmi", ] @@ -7642,6 +7828,7 @@ dependencies = [ "parity-scale-codec", "sc-allocator", "sc-executor-common", + "scoped-tls", "sp-core", "sp-runtime-interface", "sp-wasm-interface", @@ -8410,6 +8597,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +dependencies = [ + "serde", +] + [[package]] name = "serde_cbor" version = "0.11.1" @@ -9493,6 +9689,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" version = "0.3.21" @@ -9848,6 +10050,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" + [[package]] name = "target-lexicon" version = "0.12.0" @@ -10974,6 +11182,199 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70cfae554988d904d64ca17ab0e7cd652ee5c8a0807094819c1ea93eb9d6866" +dependencies = [ + "cfg-if 0.1.10", + "indexmap", + "more-asserts", + "target-lexicon 0.11.2", + "thiserror", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-derive", + "wasmer-engine", + "wasmer-engine-jit", + "wasmer-engine-native", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi 0.3.9", +] + +[[package]] +name = "wasmer-compiler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7732a9cab472bd921d5a0c422f45b3d03f62fa2c40a89e0770cef6d47e383e" +dependencies = [ + "enumset", + "serde", + "serde_bytes", + "smallvec 1.6.1", + "target-lexicon 0.11.2", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser 0.65.0", +] + +[[package]] +name = "wasmer-compiler-cranelift" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb9395f094e1d81534f4c5e330ed4cdb424e8df870d29ad585620284f5fddb" +dependencies = [ + "cranelift-codegen 0.68.0", + "cranelift-frontend 0.68.0", + "gimli 0.22.0", + "more-asserts", + "rayon", + "serde", + "smallvec 1.6.1", + "tracing", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-compiler-singlepass" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426ae6ef0f606ca815510f3e2ef6f520e217514bfb7a664defe180b9a9e75d07" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "lazy_static", + "more-asserts", + "rayon", + "serde", + "smallvec 1.6.1", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-derive" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b86dcd2c3efdb8390728a2b56f762db07789aaa5aa872a9dc776ba3a7912ed" +dependencies = [ + "proc-macro-error 1.0.4", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmer-engine" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe4667d6bd888f26ae8062a63a9379fa697415b4b4e380f33832e8418fd71b5" +dependencies = [ + "backtrace", + "bincode", + "lazy_static", + "memmap2", + "more-asserts", + "rustc-demangle", + "serde", + "serde_bytes", + "target-lexicon 0.11.2", + "thiserror", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-engine-jit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26770be802888011b4a3072f2a282fc2faa68aa48c71b3db6252a3937a85f3da" +dependencies = [ + "bincode", + "cfg-if 0.1.10", + "region", + "serde", + "serde_bytes", + "wasmer-compiler", + "wasmer-engine", + "wasmer-types", + "wasmer-vm", + "winapi 0.3.9", +] + +[[package]] +name = "wasmer-engine-native" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb4083a6c69f2cd4b000b82a80717f37c6cc2e536aee3a8ffe9af3edc276a8b" +dependencies = [ + "bincode", + "cfg-if 0.1.10", + "leb128", + "libloading 0.6.7", + "serde", + "tempfile", + "tracing", + "wasmer-compiler", + "wasmer-engine", + "wasmer-object", + "wasmer-types", + "wasmer-vm", + "which", +] + +[[package]] +name = "wasmer-object" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf8e0c12b82ff81ebecd30d7e118be5fec871d6de885a90eeb105df0a769a7b" +dependencies = [ + "object 0.22.0", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-types" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f4ac28c2951cd792c18332f03da523ed06b170f5cf6bb5b1bdd7e36c2a8218" +dependencies = [ + "cranelift-entity 0.68.0", + "serde", + "thiserror", +] + +[[package]] +name = "wasmer-vm" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7635ba0b6d2fd325f588d69a950ad9fa04dddbf6ad08b6b2a183146319bf6ae" +dependencies = [ + "backtrace", + "cc", + "cfg-if 0.1.10", + "indexmap", + "libc", + "memoffset 0.6.1", + "more-asserts", + "region", + "serde", + "thiserror", + "wasmer-types", + "winapi 0.3.9", +] + [[package]] name = "wasmi" version = "0.9.0" @@ -10999,6 +11400,12 @@ dependencies = [ "parity-wasm 0.42.2", ] +[[package]] +name = "wasmparser" +version = "0.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc2fe6350834b4e528ba0901e7aa405d78b89dc1fa3145359eb4de0e323fcf" + [[package]] name = "wasmparser" version = "0.78.2" @@ -11026,8 +11433,8 @@ dependencies = [ "rustc-demangle", "serde", "smallvec 1.6.1", - "target-lexicon", - "wasmparser", + "target-lexicon 0.12.0", + "wasmparser 0.78.2", "wasmtime-cache", "wasmtime-environ", "wasmtime-jit", @@ -11063,12 +11470,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c525b39f062eada7db3c1298287b96dcb6e472b9f6b22501300b28d9fa7582f6" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.74.0", + "cranelift-entity 0.74.0", + "cranelift-frontend 0.74.0", "cranelift-wasm", - "target-lexicon", - "wasmparser", + "target-lexicon 0.12.0", + "wasmparser 0.78.2", "wasmtime-environ", ] @@ -11082,9 +11489,9 @@ dependencies = [ "gimli 0.24.0", "more-asserts", "object 0.24.0", - "target-lexicon", + "target-lexicon 0.12.0", "thiserror", - "wasmparser", + "wasmparser 0.78.2", "wasmtime-environ", ] @@ -11095,8 +11502,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f64d0c2d881c31b0d65c1f2695e022d71eb60b9fbdd336aacca28208b58eac90" dependencies = [ "cfg-if 1.0.0", - "cranelift-codegen", - "cranelift-entity", + "cranelift-codegen 0.74.0", + "cranelift-entity 0.74.0", "cranelift-wasm", "gimli 0.24.0", "indexmap", @@ -11104,7 +11511,7 @@ dependencies = [ "more-asserts", "serde", "thiserror", - "wasmparser", + "wasmparser 0.78.2", ] [[package]] @@ -11116,9 +11523,9 @@ dependencies = [ "addr2line 0.15.1", "anyhow", "cfg-if 1.0.0", - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.74.0", + "cranelift-entity 0.74.0", + "cranelift-frontend 0.74.0", "cranelift-native", "cranelift-wasm", "gimli 0.24.0", @@ -11128,9 +11535,9 @@ dependencies = [ "rayon", "region", "serde", - "target-lexicon", + "target-lexicon 0.12.0", "thiserror", - "wasmparser", + "wasmparser 0.78.2", "wasmtime-cranelift", "wasmtime-debug", "wasmtime-environ", @@ -11149,7 +11556,7 @@ dependencies = [ "anyhow", "more-asserts", "object 0.24.0", - "target-lexicon", + "target-lexicon 0.12.0", "wasmtime-debug", "wasmtime-environ", ] @@ -11165,7 +11572,7 @@ dependencies = [ "lazy_static", "libc", "serde", - "target-lexicon", + "target-lexicon 0.12.0", "wasmtime-environ", "wasmtime-runtime", ] diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index d99f1da89e1f3..b7e2595b8e169 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -55,3 +55,4 @@ std = [] wasm-extern-trace = [] wasmtime = ["sc-executor-wasmtime"] wasmi-errno = ["wasmi/errno"] +wasmer-sandbox = ["sc-executor-common/wasmer-sandbox"] diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 4457780f8cd83..402df438f6452 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -25,5 +25,12 @@ sp-maybe-compressed-blob = { version = "4.0.0-dev", path = "../../../primitives/ sp-serializer = { version = "3.0.0", path = "../../../primitives/serializer" } thiserror = "1.0.21" +wasmer = { version = "1.0", optional = true } +wasmer-compiler-singlepass = { version = "1.0", optional = true } + [features] default = [] +wasmer-sandbox = [ + "wasmer", + "wasmer-compiler-singlepass", +] diff --git a/client/executor/common/src/lib.rs b/client/executor/common/src/lib.rs index ef73ecd90e285..99b927e062038 100644 --- a/client/executor/common/src/lib.rs +++ b/client/executor/common/src/lib.rs @@ -24,4 +24,5 @@ pub mod error; pub mod runtime_blob; pub mod sandbox; +pub mod util; pub mod wasm_runtime; diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs index 63f9cc4f258e8..7a92e8e2bd292 100644 --- a/client/executor/common/src/sandbox.rs +++ b/client/executor/common/src/sandbox.rs @@ -18,19 +18,25 @@ //! This module implements sandboxing support in the runtime. //! -//! Sandboxing is baked by wasmi at the moment. In future, however, we would like to add/switch to -//! a compiled execution engine. +//! Sandboxing is backed by wasmi and wasmer, depending on the configuration. -use crate::error::{Error, Result}; +use crate::{ + error::{Error, Result}, + util, +}; use codec::{Decode, Encode}; use sp_core::sandbox as sandbox_primitives; use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; use std::{collections::HashMap, rc::Rc}; use wasmi::{ - memory_units::Pages, Externals, ImportResolver, MemoryInstance, MemoryRef, Module, - ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind, + memory_units::Pages, Externals, ImportResolver, MemoryInstance, Module, ModuleInstance, + RuntimeArgs, RuntimeValue, Trap, TrapKind, }; +#[cfg(feature = "wasmer-sandbox")] +use crate::util::wasmer::MemoryWrapper as WasmerMemoryWrapper; +use crate::util::wasmi::MemoryWrapper as WasmiMemoryWrapper; + /// Index of a function inside the supervisor. /// /// This is a typically an index in the default table of the supervisor, however @@ -46,34 +52,59 @@ impl From for usize { /// Index of a function within guest index space. /// -/// This index is supposed to be used with as index for `Externals`. +/// This index is supposed to be used as index for `Externals`. #[derive(Copy, Clone, Debug, PartialEq)] struct GuestFuncIndex(usize); /// This struct holds a mapping from guest index space to supervisor. struct GuestToSupervisorFunctionMapping { + /// Position of elements in this vector are interpreted + /// as indices of guest functions and are mapped to + /// corresponding supervisor function indices. funcs: Vec, } impl GuestToSupervisorFunctionMapping { + /// Create an empty function mapping fn new() -> GuestToSupervisorFunctionMapping { GuestToSupervisorFunctionMapping { funcs: Vec::new() } } + /// Add a new supervisor function to the mapping. + /// Returns a newly assigned guest function index. fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex { let idx = self.funcs.len(); self.funcs.push(supervisor_func); GuestFuncIndex(idx) } + /// Find supervisor function index by its corresponding guest function index fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option { self.funcs.get(guest_func_idx.0).cloned() } } +/// Holds sandbox function and memory imports and performs name resolution struct Imports { + /// Maps qualified function name to its guest function index func_map: HashMap<(Vec, Vec), GuestFuncIndex>, - memories_map: HashMap<(Vec, Vec), MemoryRef>, + + /// Maps qualified field name to its memory reference + memories_map: HashMap<(Vec, Vec), Memory>, +} + +impl Imports { + fn func_by_name(&self, module_name: &str, func_name: &str) -> Option { + self.func_map + .get(&(module_name.as_bytes().to_owned(), func_name.as_bytes().to_owned())) + .cloned() + } + + fn memory_by_name(&self, module_name: &str, memory_name: &str) -> Option { + self.memories_map + .get(&(module_name.as_bytes().to_owned(), memory_name.as_bytes().to_owned())) + .cloned() + } } impl ImportResolver for Imports { @@ -83,10 +114,10 @@ impl ImportResolver for Imports { field_name: &str, signature: &::wasmi::Signature, ) -> std::result::Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let idx = *self.func_map.get(&key).ok_or_else(|| { + let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) })?; + Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) } @@ -95,18 +126,22 @@ impl ImportResolver for Imports { module_name: &str, field_name: &str, _memory_type: &::wasmi::MemoryDescriptor, - ) -> std::result::Result { - let key = (module_name.as_bytes().to_vec(), field_name.as_bytes().to_vec()); - let mem = self - .memories_map - .get(&key) - .ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - )) - })? - .clone(); + ) -> std::result::Result { + let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + + let wrapper = mem.as_wasmi().ok_or_else(|| { + wasmi::Error::Instantiation(format!( + "Unsupported non-wasmi export {}:{}", + module_name, field_name + )) + })?; + + // Here we use inner memory reference only to resolve + // the imports without accessing the memory contents. + let mem = unsafe { wrapper.clone_inner() }; + Ok(mem) } @@ -134,6 +169,7 @@ impl ImportResolver for Imports { /// Note that this functions are only called in the `supervisor` context. pub trait SandboxCapabilities: FunctionContext { /// Represents a function reference into the supervisor environment. + /// Provides an abstraction over execution environment. type SupervisorFuncRef; /// Invoke a function in the supervisor environment. @@ -141,9 +177,9 @@ pub trait SandboxCapabilities: FunctionContext { /// This first invokes the dispatch_thunk function, passing in the function index of the /// desired function to call and serialized arguments. The thunk calls the desired function /// with the deserialized arguments, then serializes the result into memory and returns - /// reference. The pointer to and length of the result in linear memory is encoded into an i64, - /// with the upper 32 bits representing the pointer and the lower 32 bits representing the - /// length. + /// reference. The pointer to and length of the result in linear memory is encoded into an + /// `i64`, with the upper 32 bits representing the pointer and the lower 32 bits representing + /// the length. /// /// # Errors /// @@ -164,11 +200,17 @@ pub trait SandboxCapabilities: FunctionContext { /// /// [`Externals`]: ../wasmi/trait.Externals.html pub struct GuestExternals<'a, FE: SandboxCapabilities + 'a> { + /// Supervisor function environment supervisor_externals: &'a mut FE, + + /// Instance of sandboxed module to be dispatched sandbox_instance: &'a SandboxInstance, + + /// External state passed to guest environment, see the `instantiate` function state: u32, } +/// Construct trap error from specified message fn trap(msg: &'static str) -> Trap { TrapKind::Host(Box::new(Error::Other(msg.into()))).into() } @@ -199,14 +241,15 @@ impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> { // Make `index` typesafe again. let index = GuestFuncIndex(index); + // Convert function index from guest to supervisor space let func_idx = self.sandbox_instance .guest_to_supervisor_mapping .func_by_guest_index(index) .expect( "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" + `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; + `func_by_guest_index` called with `index` can't return `None`; + qed" ); // Serialize arguments into a byte vector. @@ -220,7 +263,7 @@ impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> { let state = self.state; - // Move serialized arguments inside the memory and invoke dispatch thunk and + // Move serialized arguments inside the memory, invoke dispatch thunk and // then free allocated memory. let invoke_args_len = invoke_args_data.len() as WordSize; let invoke_args_ptr = self @@ -299,6 +342,16 @@ where f(&mut guest_externals) } +/// Module instance in terms of selected backend +enum BackendInstance { + /// Wasmi module instance + Wasmi(wasmi::ModuleRef), + + /// Wasmer module instance + #[cfg(feature = "wasmer-sandbox")] + Wasmer(wasmer::Instance), +} + /// Sandboxed instance of a wasm module. /// /// It's primary purpose is to [`invoke`] exported functions on it. @@ -314,7 +367,7 @@ where /// /// [`invoke`]: #method.invoke pub struct SandboxInstance { - instance: ModuleRef, + backend_instance: BackendInstance, dispatch_thunk: FR, guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, } @@ -327,15 +380,78 @@ impl SandboxInstance { /// /// The `state` parameter can be used to provide custom data for /// these syscall implementations. - pub fn invoke>( + pub fn invoke<'a, FE, SCH, DTH>( &self, + + // function to call that is exported from the module export_name: &str, + + // arguments passed to the function args: &[RuntimeValue], - supervisor_externals: &mut FE, + + // arbitraty context data of the call state: u32, - ) -> std::result::Result, wasmi::Error> { - with_guest_externals(supervisor_externals, self, state, |guest_externals| { - self.instance.invoke_export(export_name, args, guest_externals) + ) -> std::result::Result, wasmi::Error> + where + FE: SandboxCapabilities + 'a, + SCH: SandboxCapabilitiesHolder, + DTH: DispatchThunkHolder, + { + SCH::with_sandbox_capabilities(|supervisor_externals| { + with_guest_externals(supervisor_externals, self, state, |guest_externals| { + match &self.backend_instance { + BackendInstance::Wasmi(wasmi_instance) => { + let wasmi_result = + wasmi_instance.invoke_export(export_name, args, guest_externals)?; + + Ok(wasmi_result) + }, + + #[cfg(feature = "wasmer-sandbox")] + BackendInstance::Wasmer(wasmer_instance) => { + let function = wasmer_instance + .exports + .get_function(export_name) + .map_err(|error| wasmi::Error::Function(error.to_string()))?; + + let args: Vec = args + .iter() + .map(|v| match *v { + RuntimeValue::I32(val) => wasmer::Val::I32(val), + RuntimeValue::I64(val) => wasmer::Val::I64(val), + RuntimeValue::F32(val) => wasmer::Val::F32(val.into()), + RuntimeValue::F64(val) => wasmer::Val::F64(val.into()), + }) + .collect(); + + let wasmer_result = + DTH::initialize_thunk(&self.dispatch_thunk, || function.call(&args)) + .map_err(|error| wasmi::Error::Function(error.to_string()))?; + + if wasmer_result.len() > 1 { + return Err(wasmi::Error::Function( + "multiple return types are not supported yet".to_owned(), + )) + } + + let wasmer_result = if let Some(wasmer_value) = wasmer_result.first() { + let wasmer_value = match *wasmer_value { + wasmer::Val::I32(val) => RuntimeValue::I32(val), + wasmer::Val::I64(val) => RuntimeValue::I64(val), + wasmer::Val::F32(val) => RuntimeValue::F32(val.into()), + wasmer::Val::F64(val) => RuntimeValue::F64(val.into()), + _ => unreachable!(), + }; + + Some(wasmer_value) + } else { + None + }; + + Ok(wasmer_result) + }, + } + }) }) } @@ -343,9 +459,29 @@ impl SandboxInstance { /// /// Returns `Some(_)` if the global could be found. pub fn get_global_val(&self, name: &str) -> Option { - let global = self.instance.export_by_name(name)?.as_global()?.get(); + match &self.backend_instance { + BackendInstance::Wasmi(wasmi_instance) => { + let wasmi_global = wasmi_instance.export_by_name(name)?.as_global()?.get(); + + Some(wasmi_global.into()) + }, + + #[cfg(feature = "wasmer-sandbox")] + BackendInstance::Wasmer(wasmer_instance) => { + use sp_wasm_interface::Value; + + let global = wasmer_instance.exports.get_global(name).ok()?; + let wasmtime_value = match global.get() { + wasmer::Val::I32(val) => Value::I32(val), + wasmer::Val::I64(val) => Value::I64(val), + wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), + wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), + _ => None?, + }; - Some(global.into()) + Some(wasmtime_value) + }, + } } } @@ -366,7 +502,7 @@ pub enum InstantiationError { fn decode_environment_definition( mut raw_env_def: &[u8], - memories: &[Option], + memories: &[Option], ) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut raw_env_def) .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; @@ -401,7 +537,10 @@ fn decode_environment_definition( /// An environment in which the guest module is instantiated. pub struct GuestEnvironment { + /// Function and memory imports of the guest module imports: Imports, + + /// Supervisor functinons mapped to guest index space guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, } @@ -436,47 +575,142 @@ impl UnregisteredInstance { } } -/// Instantiate a guest module and return it's index in the store. -/// -/// The guest module's code is specified in `wasm`. Environment that will be available to -/// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]). -/// `dispatch_thunk` is used as function that handle calls from guests. -/// -/// # Errors -/// -/// Returns `Err` if any of the following conditions happens: -/// -/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`]. -/// - Module in `wasm` is invalid or couldn't be instantiated. -/// -/// [`EnvironmentDefinition`]: ../sandbox/struct.EnvironmentDefinition.html -pub fn instantiate<'a, FE: SandboxCapabilities>( - supervisor_externals: &mut FE, - dispatch_thunk: FE::SupervisorFuncRef, - wasm: &[u8], - host_env: GuestEnvironment, - state: u32, -) -> std::result::Result, InstantiationError> { - let module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let instance = ModuleInstance::new(&module, &host_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; +/// Helper type to provide sandbox capabilities to the inner context +pub trait SandboxCapabilitiesHolder { + /// Supervisor function reference + type SupervisorFuncRef; + + /// Capabilities trait + type SC: SandboxCapabilities; + + /// Wrapper that provides sandbox capabilities in a limited context + fn with_sandbox_capabilities R>(f: F) -> R; +} + +/// Helper type to provide dispatch thunk to the inner context +pub trait DispatchThunkHolder { + /// Dispatch thunk for this particular context + type DispatchThunk; - let sandbox_instance = Rc::new(SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for anything - // but for extracting memory and tables. But in this particular case, we are extracting - // for the purpose of running `start` function which should be ok. - instance: instance.not_started_instance().clone(), - dispatch_thunk, - guest_to_supervisor_mapping: host_env.guest_to_supervisor_mapping, - }); + /// Provide `DispatchThunk` for the runtime method call and execute the given function `f`. + /// + /// During the execution of the provided function `dispatch_thunk` will be callable. + fn initialize_thunk(s: &Self::DispatchThunk, f: F) -> R + where + F: FnOnce() -> R; + + /// Wrapper that provides dispatch thunk in a limited context + fn with_dispatch_thunk R>(f: F) -> R; +} + +/// Sandbox backend to use +pub enum SandboxBackend { + /// Wasm interpreter + Wasmi, - with_guest_externals(supervisor_externals, &sandbox_instance, state, |guest_externals| { - instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - })?; + /// Wasmer environment + #[cfg(feature = "wasmer-sandbox")] + Wasmer, - Ok(UnregisteredInstance { sandbox_instance }) + /// Use wasmer backend if available. Fall back to wasmi otherwise. + TryWasmer, +} + +/// Memory reference in terms of a selected backend +#[derive(Clone, Debug)] +pub enum Memory { + /// Wasmi memory reference + Wasmi(WasmiMemoryWrapper), + + /// Wasmer memory refernce + #[cfg(feature = "wasmer-sandbox")] + Wasmer(WasmerMemoryWrapper), +} + +impl Memory { + /// View as wasmi memory + pub fn as_wasmi(&self) -> Option { + match self { + Memory::Wasmi(memory) => Some(memory.clone()), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(_) => None, + } + } + + /// View as wasmer memory + #[cfg(feature = "wasmer-sandbox")] + pub fn as_wasmer(&self) -> Option { + match self { + Memory::Wasmer(memory) => Some(memory.clone()), + Memory::Wasmi(_) => None, + } + } +} + +impl util::MemoryTransfer for Memory { + fn read(&self, source_addr: Pointer, size: usize) -> Result> { + match self { + Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read(source_addr, size), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read(source_addr, size), + } + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { + match self { + Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), + } + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { + match self { + Memory::Wasmi(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), + } + } +} + +/// Wasmer specific context +#[cfg(feature = "wasmer-sandbox")] +struct WasmerBackend { + store: wasmer::Store, +} + +/// Information specific to a particular execution backend +enum BackendContext { + /// Wasmi specific context + Wasmi, + + /// Wasmer specific context + #[cfg(feature = "wasmer-sandbox")] + Wasmer(WasmerBackend), +} + +impl BackendContext { + pub fn new(backend: SandboxBackend) -> BackendContext { + match backend { + SandboxBackend::Wasmi => BackendContext::Wasmi, + + #[cfg(not(feature = "wasmer-sandbox"))] + SandboxBackend::TryWasmer => BackendContext::Wasmi, + + #[cfg(feature = "wasmer-sandbox")] + SandboxBackend::Wasmer | SandboxBackend::TryWasmer => { + let compiler = wasmer_compiler_singlepass::Singlepass::default(); + + BackendContext::Wasmer(WasmerBackend { + store: wasmer::Store::new(&wasmer::JIT::new(compiler).engine()), + }) + }, + } + } } /// This struct keeps track of all sandboxed components. @@ -485,13 +719,18 @@ pub fn instantiate<'a, FE: SandboxCapabilities>( pub struct Store { // Memories and instances are `Some` until torn down. instances: Vec>>>, - memories: Vec>, + memories: Vec>, + backend_context: BackendContext, } impl Store { /// Create a new empty sandbox store. - pub fn new() -> Self { - Store { instances: Vec::new(), memories: Vec::new() } + pub fn new(backend: SandboxBackend) -> Self { + Store { + instances: Vec::new(), + memories: Vec::new(), + backend_context: BackendContext::new(backend), + } } /// Create a new memory instance and return it's index. @@ -501,15 +740,33 @@ impl Store { /// Returns `Err` if the memory couldn't be created. /// Typically happens if `initial` is more than `maximum`. pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { + let memories = &mut self.memories; + let backend_context = &self.backend_context; + let maximum = match maximum { sandbox_primitives::MEM_UNLIMITED => None, - specified_limit => Some(Pages(specified_limit as usize)), + specified_limit => Some(specified_limit), }; - let mem = MemoryInstance::alloc(Pages(initial as usize), maximum)?; + let memory = match &backend_context { + BackendContext::Wasmi => Memory::Wasmi(WasmiMemoryWrapper::new(MemoryInstance::alloc( + Pages(initial as usize), + maximum.map(|m| Pages(m as usize)), + )?)), + + #[cfg(feature = "wasmer-sandbox")] + BackendContext::Wasmer(context) => { + let ty = wasmer::MemoryType::new(initial, maximum, false); + Memory::Wasmer(WasmerMemoryWrapper::new( + wasmer::Memory::new(&context.store, ty) + .map_err(|_| Error::InvalidMemoryReference)?, + )) + }, + }; + + let mem_idx = memories.len(); + memories.push(Some(memory.clone())); - let mem_idx = self.memories.len(); - self.memories.push(Some(mem)); Ok(mem_idx as u32) } @@ -533,7 +790,7 @@ impl Store { /// /// Returns `Err` If `memory_idx` isn't a valid index of an memory or /// if memory has been torn down. - pub fn memory(&self, memory_idx: u32) -> Result { + pub fn memory(&self, memory_idx: u32) -> Result { self.memories .get(memory_idx as usize) .cloned() @@ -575,9 +832,307 @@ impl Store { } } + /// Instantiate a guest module and return it's index in the store. + /// + /// The guest module's code is specified in `wasm`. Environment that will be available to + /// guest module is specified in `guest_env`. A dispatch thunk is used as function that + /// handle calls from guests. `state` is an opaque pointer to caller's arbitrary context + /// normally created by `sp_sandbox::Instance` primitive. + /// + /// Note: Due to borrowing constraints dispatch thunk is now propagated using DTH + /// + /// Returns uninitialized sandboxed module instance or an instantiation error. + pub fn instantiate<'a, FE, SCH, DTH>( + &mut self, + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + ) -> std::result::Result, InstantiationError> + where + FR: Clone + 'static, + FE: SandboxCapabilities + 'a, + SCH: SandboxCapabilitiesHolder, + DTH: DispatchThunkHolder, + { + let backend_context = &self.backend_context; + + let sandbox_instance = match backend_context { + BackendContext::Wasmi => + Self::instantiate_wasmi::(wasm, guest_env, state)?, + + #[cfg(feature = "wasmer-sandbox")] + BackendContext::Wasmer(context) => + Self::instantiate_wasmer::(context, wasm, guest_env, state)?, + }; + + Ok(UnregisteredInstance { sandbox_instance }) + } +} + +// Private routines +impl Store { fn register_sandbox_instance(&mut self, sandbox_instance: Rc>) -> u32 { let instance_idx = self.instances.len(); self.instances.push(Some(sandbox_instance)); instance_idx as u32 } + + fn instantiate_wasmi<'a, FE, SCH, DTH>( + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + ) -> std::result::Result>, InstantiationError> + where + FR: Clone + 'static, + FE: SandboxCapabilities + 'a, + SCH: SandboxCapabilitiesHolder, + DTH: DispatchThunkHolder, + { + let wasmi_module = + Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; + let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) + .map_err(|_| InstantiationError::Instantiation)?; + + let sandbox_instance = DTH::with_dispatch_thunk(|dispatch_thunk| { + Rc::new(SandboxInstance { + // In general, it's not a very good idea to use `.not_started_instance()` for + // anything but for extracting memory and tables. But in this particular case, we + // are extracting for the purpose of running `start` function which should be ok. + backend_instance: BackendInstance::Wasmi( + wasmi_instance.not_started_instance().clone(), + ), + dispatch_thunk: dispatch_thunk.clone(), + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, + }) + }); + + SCH::with_sandbox_capabilities(|supervisor_externals| { + with_guest_externals( + supervisor_externals, + &sandbox_instance, + state, + |guest_externals| { + wasmi_instance + .run_start(guest_externals) + .map_err(|_| InstantiationError::StartTrapped) + + // Note: no need to run start on wasmtime instance, since it's done + // automatically + }, + ) + })?; + + Ok(sandbox_instance) + } + + #[cfg(feature = "wasmer-sandbox")] + fn instantiate_wasmer<'a, FE, SCH, DTH>( + context: &WasmerBackend, + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + ) -> std::result::Result>, InstantiationError> + where + FR: Clone + 'static, + FE: SandboxCapabilities + 'a, + SCH: SandboxCapabilitiesHolder, + DTH: DispatchThunkHolder, + { + let module = wasmer::Module::new(&context.store, wasm) + .map_err(|_| InstantiationError::ModuleDecoding)?; + + type Exports = HashMap; + let mut exports_map = Exports::new(); + + for import in module.imports().into_iter() { + match import.ty() { + // Nothing to do here + wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), + + wasmer::ExternType::Memory(_) => { + let exports = exports_map + .entry(import.module().to_string()) + .or_insert(wasmer::Exports::new()); + + let memory = guest_env + .imports + .memory_by_name(import.module(), import.name()) + .ok_or(InstantiationError::ModuleDecoding)?; + + let mut wasmer_memory_ref = memory.as_wasmer().expect( + "memory is created by wasmer; \ + exported by the same module and backend; \ + thus the operation can't fail; \ + qed", + ); + + // This is safe since we're only instantiating the module and populating + // the export table, so no memory access can happen at this time. + // All subsequent memory accesses should happen through the wrapper, + // that enforces the memory access protocol. + let wasmer_memory = unsafe { wasmer_memory_ref.clone_inner() }; + + exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); + }, + + wasmer::ExternType::Function(func_ty) => { + let guest_func_index = + guest_env.imports.func_by_name(import.module(), import.name()); + + let guest_func_index = if let Some(index) = guest_func_index { + index + } else { + // Missing import (should we abort here?) + continue + }; + + let supervisor_func_index = guest_env + .guest_to_supervisor_mapping + .func_by_guest_index(guest_func_index) + .ok_or(InstantiationError::ModuleDecoding)?; + + let function = Self::wasmer_dispatch_function::( + supervisor_func_index, + &context.store, + func_ty, + state, + ); + + let exports = exports_map + .entry(import.module().to_string()) + .or_insert(wasmer::Exports::new()); + + exports.insert(import.name(), wasmer::Extern::Function(function)); + }, + } + } + + let mut import_object = wasmer::ImportObject::new(); + for (module_name, exports) in exports_map.into_iter() { + import_object.register(module_name, exports); + } + + let instance = + wasmer::Instance::new(&module, &import_object).map_err(|error| match error { + wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, + wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, + wasmer::InstantiationError::HostEnvInitialization(_) => + InstantiationError::EnvironmentDefinitionCorrupted, + })?; + + Ok(Rc::new(SandboxInstance { + backend_instance: BackendInstance::Wasmer(instance), + dispatch_thunk: DTH::with_dispatch_thunk(|dispatch_thunk| dispatch_thunk.clone()), + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, + })) + } + + #[cfg(feature = "wasmer-sandbox")] + fn wasmer_dispatch_function<'a, FE, SCH, DTH>( + supervisor_func_index: SupervisorFuncIndex, + store: &wasmer::Store, + func_ty: &wasmer::FunctionType, + state: u32, + ) -> wasmer::Function + where + FR: Clone + 'static, + FE: SandboxCapabilities + 'a, + SCH: SandboxCapabilitiesHolder, + DTH: DispatchThunkHolder, + { + wasmer::Function::new(store, func_ty, move |params| { + SCH::with_sandbox_capabilities(|supervisor_externals| { + use sp_wasm_interface::Value; + + // Serialize arguments into a byte vector. + let invoke_args_data = params + .iter() + .map(|val| match val { + wasmer::Val::I32(val) => Value::I32(*val), + wasmer::Val::I64(val) => Value::I64(*val), + wasmer::Val::F32(val) => Value::F32(f32::to_bits(*val)), + wasmer::Val::F64(val) => Value::F64(f64::to_bits(*val)), + _ => unimplemented!(), + }) + .collect::>() + .encode(); + + // Move serialized arguments inside the memory, invoke dispatch thunk and + // then free allocated memory. + let invoke_args_len = invoke_args_data.len() as WordSize; + let invoke_args_ptr = + supervisor_externals.allocate_memory(invoke_args_len).map_err(|_| { + wasmer::RuntimeError::new( + "Can't allocate memory in supervisor for the arguments", + ) + })?; + + let deallocate = |fe: &mut FE, ptr, fail_msg| { + fe.deallocate_memory(ptr).map_err(|_| wasmer::RuntimeError::new(fail_msg)) + }; + + if supervisor_externals.write_memory(invoke_args_ptr, &invoke_args_data).is_err() { + deallocate( + supervisor_externals, + invoke_args_ptr, + "Failed dealloction after failed write of invoke arguments", + )?; + + return Err(wasmer::RuntimeError::new("Can't write invoke args into memory")) + } + + // Perform the actuall call + let serialized_result = DTH::with_dispatch_thunk(|dispatch_thunk| { + supervisor_externals.invoke( + &dispatch_thunk, + invoke_args_ptr, + invoke_args_len, + state, + supervisor_func_index, + ) + }) + .map_err(|e| wasmer::RuntimeError::new(e.to_string()))?; + + // dispatch_thunk returns pointer to serialized arguments. + // Unpack pointer and len of the serialized result data. + let (serialized_result_val_ptr, serialized_result_val_len) = { + // Cast to u64 to use zero-extension. + let v = serialized_result as u64; + let ptr = (v as u64 >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (Pointer::new(ptr), len) + }; + + let serialized_result_val = supervisor_externals + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| { + wasmer::RuntimeError::new( + "Can't read the serialized result from dispatch thunk", + ) + }); + + let deserialized_result = deallocate( + supervisor_externals, + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + ) + .and_then(|_| serialized_result_val) + .and_then(|serialized_result_val| { + deserialize_result(&serialized_result_val) + .map_err(|e| wasmer::RuntimeError::new(e.to_string())) + })?; + + if let Some(value) = deserialized_result { + Ok(vec![match value { + RuntimeValue::I32(val) => wasmer::Val::I32(val), + RuntimeValue::I64(val) => wasmer::Val::I64(val), + RuntimeValue::F32(val) => wasmer::Val::F32(val.into()), + RuntimeValue::F64(val) => wasmer::Val::F64(val.into()), + }]) + } else { + Ok(vec![]) + } + }) + }) + } } diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs new file mode 100644 index 0000000000000..995424bfa8399 --- /dev/null +++ b/client/executor/common/src/util.rs @@ -0,0 +1,241 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Utilities used by all backends + +use crate::error::{Error, Result}; +use sp_wasm_interface::Pointer; +use std::ops::Range; + +/// Construct a range from an offset to a data length after the offset. +/// Returns None if the end of the range would exceed some maximum offset. +pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { + let end = offset.checked_add(len)?; + if end <= max { + Some(offset..end) + } else { + None + } +} + +/// Provides safe memory access interface using an external buffer +pub trait MemoryTransfer { + /// Read data from a slice of memory into a newly allocated buffer. + /// + /// Returns an error if the read would go out of the memory bounds. + fn read(&self, source_addr: Pointer, size: usize) -> Result>; + + /// Read data from a slice of memory into a destination buffer. + /// + /// Returns an error if the read would go out of the memory bounds. + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()>; + + /// Write data to a slice of memory. + /// + /// Returns an error if the write would go out of the memory bounds. + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()>; +} + +/// Safe wrapper over wasmi memory reference +pub mod wasmi { + use super::*; + + /// Wasmi provides direct access to its memory using slices. + /// + /// This wrapper limits the scope where the slice can be taken to + #[derive(Debug, Clone)] + pub struct MemoryWrapper(::wasmi::MemoryRef); + + impl MemoryWrapper { + /// Take ownership of the memory region and return a wrapper object + pub fn new(memory: ::wasmi::MemoryRef) -> Self { + Self(memory) + } + + /// Clone the underlying memory object + /// + /// # Safety + /// + /// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled + /// access. By returning the memory object "as is" we bypass all of the checks. + /// + /// Intended to use only during module initialization. + pub unsafe fn clone_inner(&self) -> ::wasmi::MemoryRef { + self.0.clone() + } + } + + impl super::MemoryTransfer for MemoryWrapper { + fn read(&self, source_addr: Pointer, size: usize) -> Result> { + self.0.with_direct_access(|source| { + let range = checked_range(source_addr.into(), size, source.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + Ok(Vec::from(&source[range])) + }) + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { + self.0.with_direct_access(|source| { + let range = checked_range(source_addr.into(), destination.len(), source.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + destination.copy_from_slice(&source[range]); + Ok(()) + }) + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { + self.0.with_direct_access_mut(|destination| { + let range = checked_range(dest_addr.into(), source.len(), destination.len()) + .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; + + &mut destination[range].copy_from_slice(source); + Ok(()) + }) + } + } +} + +// Routines specific to Wasmer runtime. Since sandbox can be invoked from both +/// wasmi and wasmtime runtime executors, we need to have a way to deal with sanbox +/// backends right from the start. +#[cfg(feature = "wasmer-sandbox")] +pub mod wasmer { + use super::checked_range; + use crate::error::{Error, Result}; + use sp_wasm_interface::Pointer; + use std::{cell::RefCell, convert::TryInto, rc::Rc}; + + /// In order to enforce memory access protocol to the backend memory + /// we wrap it with `RefCell` and encapsulate all memory operations. + #[derive(Debug, Clone)] + pub struct MemoryWrapper { + buffer: Rc>, + } + + impl MemoryWrapper { + /// Take ownership of the memory region and return a wrapper object + pub fn new(memory: wasmer::Memory) -> Self { + Self { buffer: Rc::new(RefCell::new(memory)) } + } + + /// Returns linear memory of the wasm instance as a slice. + /// + /// # Safety + /// + /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data + /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since + /// growing, we cannot guarantee the lifetime of the returned slice reference. + unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { + let ptr = memory.data_ptr() as *const _; + let len: usize = + memory.data_size().try_into().expect("data size should fit into usize"); + + if len == 0 { + &[] + } else { + core::slice::from_raw_parts(ptr, len) + } + } + + /// Returns linear memory of the wasm instance as a slice. + /// + /// # Safety + /// + /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is + /// returned it must be ensured that only one mutable and no shared references to memory + /// exists at the same time. + unsafe fn memory_as_slice_mut(memory: &wasmer::Memory) -> &mut [u8] { + let ptr = memory.data_ptr(); + let len: usize = + memory.data_size().try_into().expect("data size should fit into usize"); + + if len == 0 { + &mut [] + } else { + core::slice::from_raw_parts_mut(ptr, len) + } + } + + /// Clone the underlying memory object + /// + /// # Safety + /// + /// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled + /// access. By returning the memory object "as is" we bypass all of the checks. + /// + /// Intended to use only during module initialization. + /// + /// # Panics + /// + /// Will panic if `MemoryRef` is currently in use. + pub unsafe fn clone_inner(&mut self) -> wasmer::Memory { + // We take exclusive lock to ensure that we're the only one here + self.buffer.borrow_mut().clone() + } + } + + impl super::MemoryTransfer for MemoryWrapper { + fn read(&self, source_addr: Pointer, size: usize) -> Result> { + let memory = self.buffer.borrow(); + + let data_size = memory.data_size().try_into().expect("data size does not fit"); + + let range = checked_range(source_addr.into(), size, data_size) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + let mut buffer = vec![0; range.len()]; + self.read_into(source_addr, &mut buffer)?; + + Ok(buffer) + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { + unsafe { + let memory = self.buffer.borrow(); + + // This should be safe since we don't grow up memory while caching this reference + // and we give up the reference before returning from this function. + let source = Self::memory_as_slice(&memory); + + let range = checked_range(source_addr.into(), destination.len(), source.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + destination.copy_from_slice(&source[range]); + Ok(()) + } + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { + unsafe { + let memory = self.buffer.borrow_mut(); + + // This should be safe since we don't grow up memory while caching this reference + // and we give up the reference before returning from this function. + let destination = Self::memory_as_slice_mut(&memory); + + let range = checked_range(dest_addr.into(), source.len(), destination.len()) + .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; + + &mut destination[range].copy_from_slice(source); + Ok(()) + } + } + } +} diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index c1e5b3d26723d..324b2bdd0baeb 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -22,3 +22,4 @@ sc-allocator = { version = "4.0.0-dev", path = "../../allocator" } sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-interface" } sp-runtime-interface = { version = "4.0.0-dev", path = "../../../primitives/runtime-interface" } sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" } +scoped-tls = "1.0" diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index cc6a05fccf81c..3c5836c774813 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -24,6 +24,7 @@ use sc_executor_common::{ error::{Error, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, sandbox, + util::MemoryTransfer, wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, }; use sp_core::sandbox as sandbox_primitives; @@ -31,7 +32,7 @@ use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{ Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, }; -use std::{cell::RefCell, str, sync::Arc}; +use std::{cell::RefCell, rc::Rc, str, sync::Arc}; use wasmi::{ memory_units::Pages, FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, @@ -39,38 +40,45 @@ use wasmi::{ TableRef, }; -struct FunctionExecutor<'a> { - sandbox_store: sandbox::Store, - heap: sc_allocator::FreeingBumpHeapAllocator, +#[derive(Clone)] +struct FunctionExecutor { + inner: Rc, +} + +struct Inner { + sandbox_store: RefCell>, + heap: RefCell, memory: MemoryRef, table: Option, - host_functions: &'a [&'static dyn Function], + host_functions: Arc>, allow_missing_func_imports: bool, - missing_functions: &'a [String], + missing_functions: Arc>, } -impl<'a> FunctionExecutor<'a> { +impl FunctionExecutor { fn new( m: MemoryRef, heap_base: u32, t: Option, - host_functions: &'a [&'static dyn Function], + host_functions: Arc>, allow_missing_func_imports: bool, - missing_functions: &'a [String], + missing_functions: Arc>, ) -> Result { Ok(FunctionExecutor { - sandbox_store: sandbox::Store::new(), - heap: sc_allocator::FreeingBumpHeapAllocator::new(heap_base), - memory: m, - table: t, - host_functions, - allow_missing_func_imports, - missing_functions, + inner: Rc::new(Inner { + sandbox_store: RefCell::new(sandbox::Store::new(sandbox::SandboxBackend::Wasmi)), + heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), + memory: m, + table: t, + host_functions, + allow_missing_func_imports, + missing_functions, + }), }) } } -impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> { +impl sandbox::SandboxCapabilities for FunctionExecutor { type SupervisorFuncRef = wasmi::FuncRef; fn invoke( @@ -99,24 +107,26 @@ impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> { } } -impl<'a> FunctionContext for FunctionExecutor<'a> { +impl FunctionContext for FunctionExecutor { fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> WResult<()> { - self.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) + self.inner.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { - self.memory.set(address.into(), data).map_err(|e| e.to_string()) + self.inner.memory.set(address.into(), data).map_err(|e| e.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> WResult> { - let heap = &mut self.heap; - self.memory + let heap = &mut self.inner.heap.borrow_mut(); + self.inner + .memory .with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string())) } fn deallocate_memory(&mut self, ptr: Pointer) -> WResult<()> { - let heap = &mut self.heap; - self.memory + let heap = &mut self.inner.heap.borrow_mut(); + self.inner + .memory .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) } @@ -125,7 +135,7 @@ impl<'a> FunctionContext for FunctionExecutor<'a> { } } -impl<'a> Sandbox for FunctionExecutor<'a> { +impl Sandbox for FunctionExecutor { fn memory_get( &mut self, memory_id: MemoryId, @@ -133,18 +143,21 @@ impl<'a> Sandbox for FunctionExecutor<'a> { buf_ptr: Pointer, buf_len: WordSize, ) -> WResult { - let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?; + let sandboxed_memory = + self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - match MemoryInstance::transfer( - &sandboxed_memory, - offset as usize, - &self.memory, - buf_ptr.into(), - buf_len as usize, - ) { - Ok(()) => Ok(sandbox_primitives::ERR_OK), - Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + let len = buf_len as usize; + + let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if let Err(_) = self.inner.memory.set(buf_ptr.into(), &buffer) { + return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) } + + Ok(sandbox_primitives::ERR_OK) } fn memory_set( @@ -154,26 +167,37 @@ impl<'a> Sandbox for FunctionExecutor<'a> { val_ptr: Pointer, val_len: WordSize, ) -> WResult { - let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?; + let sandboxed_memory = + self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - match MemoryInstance::transfer( - &self.memory, - val_ptr.into(), - &sandboxed_memory, - offset as usize, - val_len as usize, - ) { - Ok(()) => Ok(sandbox_primitives::ERR_OK), - Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + let len = val_len as usize; + + let buffer = match self.inner.memory.get(val_ptr.into(), len) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { + return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) } + + Ok(sandbox_primitives::ERR_OK) } fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { - self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string()) + self.inner + .sandbox_store + .borrow_mut() + .memory_teardown(memory_id) + .map_err(|e| e.to_string()) } fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult { - self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string()) + self.inner + .sandbox_store + .borrow_mut() + .new_memory(initial, maximum) + .map_err(|e| e.to_string()) } fn invoke( @@ -194,8 +218,15 @@ impl<'a> Sandbox for FunctionExecutor<'a> { .map(Into::into) .collect::>(); - let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?; - let result = instance.invoke(export_name, &args, self, state); + let instance = self + .inner + .sandbox_store + .borrow() + .instance(instance_id) + .map_err(|e| e.to_string())?; + + let result = EXECUTOR + .set(self, || instance.invoke::<_, CapsHolder, ThunkHolder>(export_name, &args, state)); match result { Ok(None) => Ok(sandbox_primitives::ERR_OK), @@ -214,7 +245,11 @@ impl<'a> Sandbox for FunctionExecutor<'a> { } fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { - self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string()) + self.inner + .sandbox_store + .borrow_mut() + .instance_teardown(instance_id) + .map_err(|e| e.to_string()) } fn instance_new( @@ -227,6 +262,7 @@ impl<'a> Sandbox for FunctionExecutor<'a> { // Extract a dispatch thunk from instance's table by the specified index. let dispatch_thunk = { let table = self + .inner .table .as_ref() .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; @@ -236,19 +272,26 @@ impl<'a> Sandbox for FunctionExecutor<'a> { .ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")? }; - let guest_env = match sandbox::GuestEnvironment::decode(&self.sandbox_store, raw_env_def) { + let guest_env = match sandbox::GuestEnvironment::decode( + &*self.inner.sandbox_store.borrow(), + raw_env_def, + ) { Ok(guest_env) => guest_env, Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), }; - let instance_idx_or_err_code = - match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state) - .map(|i| i.register(&mut self.sandbox_store)) - { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, - }; + let store = &mut *self.inner.sandbox_store.borrow_mut(); + let result = EXECUTOR.set(self, || { + DISPATCH_THUNK.set(&dispatch_thunk, || { + store.instantiate::<_, CapsHolder, ThunkHolder>(wasm, guest_env, state) + }) + }); + + let instance_idx_or_err_code: u32 = match result.map(|i| i.register(store)) { + Ok(instance_idx) => instance_idx, + Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, + Err(_) => sandbox_primitives::ERR_MODULE, + }; Ok(instance_idx_or_err_code as u32) } @@ -258,13 +301,57 @@ impl<'a> Sandbox for FunctionExecutor<'a> { instance_idx: u32, name: &str, ) -> WResult> { - self.sandbox_store + self.inner + .sandbox_store + .borrow() .instance(instance_idx) .map(|i| i.get_global_val(name)) .map_err(|e| e.to_string()) } } +/// Wasmi specific implementation of `SandboxCapabilitiesHolder` that provides +/// sandbox with a scoped thread local access to a function executor. +/// This is a way to calm down the borrow checker since host function closures +/// require exclusive access to it. +struct CapsHolder; + +scoped_tls::scoped_thread_local!(static EXECUTOR: FunctionExecutor); + +impl sandbox::SandboxCapabilitiesHolder for CapsHolder { + type SupervisorFuncRef = wasmi::FuncRef; + type SC = FunctionExecutor; + + fn with_sandbox_capabilities R>(f: F) -> R { + assert!(EXECUTOR.is_set(), "wasmi executor is not set"); + EXECUTOR.with(|executor| f(&mut executor.clone())) + } +} + +/// Wasmi specific implementation of `DispatchThunkHolder` that provides +/// sandbox with a scoped thread local access to a dispatch thunk. +/// This is a way to calm down the borrow checker since host function closures +/// require exclusive access to it. +struct ThunkHolder; + +scoped_tls::scoped_thread_local!(static DISPATCH_THUNK: wasmi::FuncRef); + +impl sandbox::DispatchThunkHolder for ThunkHolder { + type DispatchThunk = wasmi::FuncRef; + + fn with_dispatch_thunk R>(f: F) -> R { + assert!(DISPATCH_THUNK.is_set(), "dispatch thunk is not set"); + DISPATCH_THUNK.with(|thunk| f(&mut thunk.clone())) + } + + fn initialize_thunk(s: &Self::DispatchThunk, f: F) -> R + where + F: FnOnce() -> R, + { + DISPATCH_THUNK.set(s, f) + } +} + /// Will be used on initialization of a module to resolve function and memory imports. struct Resolver<'a> { /// All the hot functions that we export for the WASM blob. @@ -375,7 +462,7 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { } } -impl<'a> wasmi::Externals for FunctionExecutor<'a> { +impl wasmi::Externals for FunctionExecutor { fn invoke_index( &mut self, index: usize, @@ -383,19 +470,19 @@ impl<'a> wasmi::Externals for FunctionExecutor<'a> { ) -> Result, wasmi::Trap> { let mut args = args.as_ref().iter().copied().map(Into::into); - if let Some(function) = self.host_functions.get(index) { + if let Some(function) = self.inner.host_functions.clone().get(index) { function .execute(self, &mut args) .map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg)) .map_err(wasmi::Trap::from) .map(|v| v.map(Into::into)) - } else if self.allow_missing_func_imports && - index >= self.host_functions.len() && - index < self.host_functions.len() + self.missing_functions.len() + } else if self.inner.allow_missing_func_imports && + index >= self.inner.host_functions.len() && + index < self.inner.host_functions.len() + self.inner.missing_functions.len() { Err(Error::from(format!( "Function `{}` is only a stub. Calling a stub is not allowed.", - self.missing_functions[index - self.host_functions.len()], + self.inner.missing_functions[index - self.inner.host_functions.len()], )) .into()) } else { @@ -435,9 +522,9 @@ fn call_in_wasm_module( memory: &MemoryRef, method: InvokeMethod, data: &[u8], - host_functions: &[&'static dyn Function], + host_functions: Arc>, allow_missing_func_imports: bool, - missing_functions: &Vec, + missing_functions: Arc>, ) -> Result, Error> { // Initialize FunctionExecutor. let table: Option = module_instance @@ -628,7 +715,7 @@ impl WasmModule for WasmiRuntime { data_segments_snapshot: self.data_segments_snapshot.clone(), host_functions: self.host_functions.clone(), allow_missing_func_imports: self.allow_missing_func_imports, - missing_functions, + missing_functions: Arc::new(missing_functions), })) } } @@ -684,7 +771,7 @@ pub struct WasmiInstance { /// These stubs will error when the wasm blob trie to call them. allow_missing_func_imports: bool, /// List of missing functions detected during function resolution - missing_functions: Vec, + missing_functions: Arc>, } // This is safe because `WasmiInstance` does not leak any references to `self.memory` and @@ -717,9 +804,9 @@ impl WasmInstance for WasmiInstance { &self.memory, method, data, - self.host_functions.as_ref(), + self.host_functions.clone(), self.allow_missing_func_imports, - self.missing_functions.as_ref(), + self.missing_functions.clone(), ) } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index ee0e82928db24..12e5ab0023efb 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -19,13 +19,14 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use crate::{instance_wrapper::InstanceWrapper, util}; +use crate::instance_wrapper::InstanceWrapper; use codec::{Decode, Encode}; use log::trace; use sc_allocator::FreeingBumpHeapAllocator; use sc_executor_common::{ error::Result, - sandbox::{self, SandboxCapabilities, SupervisorFuncIndex}, + sandbox::{self, SandboxCapabilities, SandboxCapabilitiesHolder, SupervisorFuncIndex}, + util::MemoryTransfer, }; use sp_core::sandbox as sandbox_primitives; use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; @@ -42,7 +43,12 @@ pub struct SupervisorFuncRef(Func); /// The state required to construct a HostContext context. The context only lasts for one host /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make /// many different host calls that must share state. +#[derive(Clone)] pub struct HostState { + inner: Rc, +} + +struct Inner { // We need some interior mutability here since the host state is shared between all host // function handlers and the wasmtime backend's `impl WasmRuntime`. // @@ -61,31 +67,18 @@ impl HostState { /// Constructs a new `HostState`. pub fn new(allocator: FreeingBumpHeapAllocator, instance: Rc) -> Self { HostState { - sandbox_store: RefCell::new(sandbox::Store::new()), - allocator: RefCell::new(allocator), - instance, + inner: Rc::new(Inner { + sandbox_store: RefCell::new(sandbox::Store::new( + sandbox::SandboxBackend::TryWasmer, + )), + allocator: RefCell::new(allocator), + instance, + }), } } - - /// Materialize `HostContext` that can be used to invoke a substrate host `dyn Function`. - pub fn materialize<'a>(&'a self) -> HostContext<'a> { - HostContext(self) - } } -/// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime -/// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from -/// a longer-living `HostState`. -pub struct HostContext<'a>(&'a HostState); - -impl<'a> std::ops::Deref for HostContext<'a> { - type Target = HostState; - fn deref(&self) -> &HostState { - self.0 - } -} - -impl<'a> SandboxCapabilities for HostContext<'a> { +impl SandboxCapabilities for HostState { type SupervisorFuncRef = SupervisorFuncRef; fn invoke( @@ -125,28 +118,30 @@ impl<'a> SandboxCapabilities for HostContext<'a> { } } -impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { +impl sp_wasm_interface::FunctionContext for HostState { fn read_memory_into( &self, address: Pointer, dest: &mut [u8], ) -> sp_wasm_interface::Result<()> { - self.instance.read_memory_into(address, dest).map_err(|e| e.to_string()) + self.inner.instance.read_memory_into(address, dest).map_err(|e| e.to_string()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { - self.instance.write_memory_from(address, data).map_err(|e| e.to_string()) + self.inner.instance.write_memory_from(address, data).map_err(|e| e.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { - self.instance - .allocate(&mut *self.allocator.borrow_mut(), size) + self.inner + .instance + .allocate(&mut *self.inner.allocator.borrow_mut(), size) .map_err(|e| e.to_string()) } fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { - self.instance - .deallocate(&mut *self.allocator.borrow_mut(), ptr) + self.inner + .instance + .deallocate(&mut *self.inner.allocator.borrow_mut(), ptr) .map_err(|e| e.to_string()) } @@ -155,7 +150,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { } } -impl<'a> Sandbox for HostContext<'a> { +impl Sandbox for HostState { fn memory_get( &mut self, memory_id: MemoryId, @@ -164,27 +159,20 @@ impl<'a> Sandbox for HostContext<'a> { buf_len: WordSize, ) -> sp_wasm_interface::Result { let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - sandboxed_memory.with_direct_access(|sandboxed_memory| { - let len = buf_len as usize; - let src_range = match util::checked_range(offset as usize, len, sandboxed_memory.len()) - { - Some(range) => range, - None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - }; - let supervisor_mem_size = self.instance.memory_size() as usize; - let dst_range = match util::checked_range(buf_ptr.into(), len, supervisor_mem_size) { - Some(range) => range, - None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - }; - self.instance - .write_memory_from( - Pointer::new(dst_range.start as u32), - &sandboxed_memory[src_range], - ) - .expect("ranges are checked above; write can't fail; qed"); - Ok(sandbox_primitives::ERR_OK) - }) + self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + + let len = buf_len as usize; + + let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if let Err(_) = self.inner.instance.write_memory_from(buf_ptr, &buffer) { + return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + } + + Ok(sandbox_primitives::ERR_OK) } fn memory_set( @@ -195,38 +183,33 @@ impl<'a> Sandbox for HostContext<'a> { val_len: WordSize, ) -> sp_wasm_interface::Result { let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - sandboxed_memory.with_direct_access_mut(|sandboxed_memory| { - let len = val_len as usize; - let supervisor_mem_size = self.instance.memory_size() as usize; - let src_range = match util::checked_range(val_ptr.into(), len, supervisor_mem_size) { - Some(range) => range, - None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - }; - let dst_range = match util::checked_range(offset as usize, len, sandboxed_memory.len()) - { - Some(range) => range, - None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - }; - self.instance - .read_memory_into( - Pointer::new(src_range.start as u32), - &mut sandboxed_memory[dst_range], - ) - .expect("ranges are checked above; read can't fail; qed"); - Ok(sandbox_primitives::ERR_OK) - }) + self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + + let len = val_len as usize; + + let buffer = match self.inner.instance.read_memory(val_ptr, len) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { + return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + } + + Ok(sandbox_primitives::ERR_OK) } fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { - self.sandbox_store + self.inner + .sandbox_store .borrow_mut() .memory_teardown(memory_id) .map_err(|e| e.to_string()) } fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { - self.sandbox_store + self.inner + .sandbox_store .borrow_mut() .new_memory(initial, maximum) .map_err(|e| e.to_string()) @@ -250,9 +233,14 @@ impl<'a> Sandbox for HostContext<'a> { .map(Into::into) .collect::>(); - let instance = - self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; - let result = instance.invoke(export_name, &args, self, state); + let instance = self + .inner + .sandbox_store + .borrow() + .instance(instance_id) + .map_err(|e| e.to_string())?; + + let result = instance.invoke::<_, CapsHolder, ThunkHolder>(export_name, &args, state); match result { Ok(None) => Ok(sandbox_primitives::ERR_OK), @@ -262,7 +250,7 @@ impl<'a> Sandbox for HostContext<'a> { if val.len() > return_val_len as usize { Err("Return value buffer is too small")?; } - ::write_memory(self, return_val, val) + ::write_memory(self, return_val, val) .map_err(|_| "can't write return value")?; Ok(sandbox_primitives::ERR_OK) }) @@ -272,7 +260,8 @@ impl<'a> Sandbox for HostContext<'a> { } fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { - self.sandbox_store + self.inner + .sandbox_store .borrow_mut() .instance_teardown(instance_id) .map_err(|e| e.to_string()) @@ -288,6 +277,7 @@ impl<'a> Sandbox for HostContext<'a> { // Extract a dispatch thunk from the instance's table by the specified index. let dispatch_thunk = { let table_item = self + .inner .instance .table() .as_ref() @@ -303,20 +293,26 @@ impl<'a> Sandbox for HostContext<'a> { SupervisorFuncRef(func_ref) }; - let guest_env = - match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), - }; - - let instance_idx_or_err_code = - match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state) - .map(|i| i.register(&mut *self.sandbox_store.borrow_mut())) - { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, - }; + let guest_env = match sandbox::GuestEnvironment::decode( + &*self.inner.sandbox_store.borrow(), + raw_env_def, + ) { + Ok(guest_env) => guest_env, + Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), + }; + + let store = &mut *self.inner.sandbox_store.borrow_mut(); + let result = DISPATCH_THUNK.set(&dispatch_thunk, || { + store + .instantiate::<_, CapsHolder, ThunkHolder>(wasm, guest_env, state) + .map(|i| i.register(store)) + }); + + let instance_idx_or_err_code = match result { + Ok(instance_idx) => instance_idx, + Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, + Err(_) => sandbox_primitives::ERR_MODULE, + }; Ok(instance_idx_or_err_code as u32) } @@ -326,10 +322,50 @@ impl<'a> Sandbox for HostContext<'a> { instance_idx: u32, name: &str, ) -> sp_wasm_interface::Result> { - self.sandbox_store + self.inner + .sandbox_store .borrow() .instance(instance_idx) .map(|i| i.get_global_val(name)) .map_err(|e| e.to_string()) } } + +/// Wasmtime specific implementation of `SandboxCapabilitiesHolder` that provides +/// sandbox with a scoped thread local access to a function executor. +/// This is a way to calm down the borrow checker since host function closures +/// require exclusive access to it. +struct CapsHolder; + +impl SandboxCapabilitiesHolder for CapsHolder { + type SupervisorFuncRef = SupervisorFuncRef; + type SC = HostState; + + fn with_sandbox_capabilities R>(f: F) -> R { + crate::state_holder::with_context(|ctx| f(&mut ctx.expect("wasmtime executor is not set"))) + } +} + +/// Wasmtime specific implementation of `DispatchThunkHolder` that provides +/// sandbox with a scoped thread local access to a dispatch thunk. +/// This is a way to calm down the borrow checker since host function closures +/// require exclusive access to it. +struct ThunkHolder; + +scoped_tls::scoped_thread_local!(static DISPATCH_THUNK: SupervisorFuncRef); + +impl sandbox::DispatchThunkHolder for ThunkHolder { + type DispatchThunk = SupervisorFuncRef; + + fn with_dispatch_thunk R>(f: F) -> R { + assert!(DISPATCH_THUNK.is_set(), "dispatch thunk is not set"); + DISPATCH_THUNK.with(|thunk| f(&mut thunk.clone())) + } + + fn initialize_thunk(s: &Self::DispatchThunk, f: F) -> R + where + F: FnOnce() -> R, + { + DISPATCH_THUNK.set(s, f) + } +} diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index 0e5094db51195..b27fb944bc030 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -255,7 +255,9 @@ impl MissingHostFuncHandler { fn wasmtime_func_sig(func: &dyn Function) -> wasmtime::FuncType { let signature = func.signature(); let params = signature.args.iter().cloned().map(into_wasmtime_val_type); + let results = signature.return_value.iter().cloned().map(into_wasmtime_val_type); + wasmtime::FuncType::new(params, results) } diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 23a912204521e..f66d62f673d90 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -19,11 +19,15 @@ //! Defines data and logic needed for interaction with an WebAssembly instance of a substrate //! runtime module. -use crate::{imports::Imports, util}; +use crate::{ + imports::Imports, + util::{from_wasmtime_val, into_wasmtime_val}, +}; use sc_executor_common::{ error::{Error, Result}, runtime_blob, + util::checked_range, wasm_runtime::InvokeMethod, }; use sp_wasm_interface::{Pointer, Value, WordSize}; @@ -96,12 +100,16 @@ impl EntryPoint { /// routines. pub struct InstanceWrapper { instance: Instance, + // The memory instance of the `instance`. // // It is important to make sure that we don't make any copies of this to make it easier to // proof See `memory_as_slice` and `memory_as_slice_mut`. memory: Memory, + + /// Indirect functions table of the module table: Option, + // Make this struct explicitly !Send & !Sync. _not_send_nor_sync: marker::PhantomData<*const ()>, } @@ -147,7 +155,7 @@ impl InstanceWrapper { None => { let memory = get_linear_memory(&instance)?; if !memory.grow(heap_pages).is_ok() { - return Err("failed top increase the linear memory size".into()) + return Err("failed to increase the linear memory size".into()) } memory }, @@ -223,11 +231,6 @@ impl InstanceWrapper { self.table.as_ref() } - /// Returns the byte size of the linear memory instance attached to this instance. - pub fn memory_size(&self) -> u32 { - self.memory.data_size() as u32 - } - /// Reads `__heap_base: i32` global variable and returns it. /// /// If it doesn't exist, not a global or of not i32 type returns an error. @@ -291,32 +294,45 @@ fn get_table(instance: &Instance) -> Option
{ /// Functions related to memory. impl InstanceWrapper { - /// Read data from a slice of memory into a destination buffer. + /// Read data from a slice of memory into a newly allocated buffer. + /// + /// Returns an error if the read would go out of the memory bounds. + pub fn read_memory(&self, source_addr: Pointer, size: usize) -> Result> { + let range = checked_range(source_addr.into(), size, self.memory.data_size()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + let mut buffer = vec![0; range.len()]; + self.read_memory_into(source_addr, &mut buffer)?; + + Ok(buffer) + } + + /// Read data from the instance memory into a slice. /// /// Returns an error if the read would go out of the memory bounds. - pub fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> Result<()> { + pub fn read_memory_into(&self, source_addr: Pointer, dest: &mut [u8]) -> Result<()> { unsafe { // This should be safe since we don't grow up memory while caching this reference and // we give up the reference before returning from this function. let memory = self.memory_as_slice(); - let range = util::checked_range(address.into(), dest.len(), memory.len()) + let range = checked_range(source_addr.into(), dest.len(), memory.len()) .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; dest.copy_from_slice(&memory[range]); Ok(()) } } - /// Write data to a slice of memory. + /// Write data to the instance memory from a slice. /// /// Returns an error if the write would go out of the memory bounds. - pub fn write_memory_from(&self, address: Pointer, data: &[u8]) -> Result<()> { + pub fn write_memory_from(&self, dest_addr: Pointer, data: &[u8]) -> Result<()> { unsafe { // This should be safe since we don't grow up memory while caching this reference and // we give up the reference before returning from this function. let memory = self.memory_as_slice_mut(); - let range = util::checked_range(address.into(), data.len(), memory.len()) + let range = checked_range(dest_addr.into(), data.len(), memory.len()) .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; memory[range].copy_from_slice(data); Ok(()) @@ -442,11 +458,11 @@ impl runtime_blob::InstanceGlobals for InstanceWrapper { } fn get_global_value(&self, global: &Self::Global) -> Value { - util::from_wasmtime_val(global.get()) + from_wasmtime_val(global.get()) } fn set_global_value(&self, global: &Self::Global, value: Value) { - global.set(util::into_wasmtime_val(value)).expect( + global.set(into_wasmtime_val(value)).expect( "the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed", ); } diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index f80d8c8e5bd58..f6878ec5ee6e1 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -148,7 +148,7 @@ pub struct WasmtimeInstance { } // This is safe because `WasmtimeInstance` does not leak reference to `self.imports` -// and all imports don't reference any anything, other than host functions and memory +// and all imports don't reference anything, other than host functions and memory unsafe impl Send for WasmtimeInstance {} impl WasmInstance for WasmtimeInstance { diff --git a/client/executor/wasmtime/src/state_holder.rs b/client/executor/wasmtime/src/state_holder.rs index 0e2684cd25130..45bddc841bde6 100644 --- a/client/executor/wasmtime/src/state_holder.rs +++ b/client/executor/wasmtime/src/state_holder.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::host::{HostContext, HostState}; +use crate::host::HostState; scoped_tls::scoped_thread_local!(static HOST_STATE: HostState); @@ -36,10 +36,10 @@ where /// context will be `None`. pub fn with_context(f: F) -> R where - F: FnOnce(Option) -> R, + F: FnOnce(Option) -> R, { if !HOST_STATE.is_set() { return f(None) } - HOST_STATE.with(|state| f(Some(state.materialize()))) + HOST_STATE.with(|state| f(Some(state.clone()))) } diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 3109a76a9af88..2c135fe7a343b 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -16,21 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::ops::Range; - use sp_wasm_interface::Value; -/// Construct a range from an offset to a data length after the offset. -/// Returns None if the end of the range would exceed some maximum offset. -pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { - let end = offset.checked_add(len)?; - if end <= max { - Some(offset..end) - } else { - None - } -} - /// Converts a [`wasmtime::Val`] into a substrate runtime interface [`Value`]. /// /// Panics if the given value doesn't have a corresponding variant in `Value`.