Repository: bytecodealliance/wasmtime
Pull request #7876 (2024-02-06 01:11:43)

Preview 2 filesystem: track open_mode instead of reporting the permissions
First update the fd_filestat_set test to assert that the filestat_set operations on a file descriptor behaves the same whether opened readonly or readwrite. This test now reproduces the behavior reported in https://github.com/bytecodealliance/wasmtime/issues/7829. wasi-common passes this changed test, but wasmtime-wasi::preview2 does not, showing that this is a regression between the two implementations.

Then, in preview2 implementation, track open_mode for a File and Dir, and use that instead of reporting permissions in DescriptorFlags.

FilePerms/DirPerms is a way of having wasmtime-wasi, instead of the operating system, mediate permissions on a preview2 Descriptor. This was conflated with the open mode, which should determine whether DescriptorFlags contains READ or WRITE or MUTATE_DIRECTORY.

* don't mask FilePerms with the DescriptorFlags (oflags) in path_open
* File and Dir now track their open_mode (represented using FilePerms)
  and report them in DescriptorFlags


pchickey: I believe the test now shows that the implementations all behave as expected.
brettcannon: I just finished running CPython's test suite and this fixed the issue I found and didn't cause any new ones!

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b347a8b9f..f3fef8487 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -665,7 +665,7 @@ dependencies = [
  "target-lexicon",
  "thiserror",
  "toml",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wat",
 ]
 
@@ -844,7 +844,7 @@ dependencies = [
  "serde_derive",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-types",
  "wat",
 ]
@@ -2632,7 +2632,7 @@ version = "0.0.0"
 dependencies = [
  "cargo_metadata",
  "heck",
- "wit-component 0.20.1",
+ "wit-component 0.21.0",
 ]
 
 [[package]]
@@ -2945,7 +2945,7 @@ name = "verify-component-adapter"
 version = "19.0.0"
 dependencies = [
  "anyhow",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wat",
 ]
 
@@ -3052,7 +3052,7 @@ dependencies = [
  "byte-array-literals",
  "object",
  "wasi",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wit-bindgen",
 ]
 
@@ -3129,27 +3129,18 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
 
 [[package]]
 name = "wasm-encoder"
-version = "0.38.1"
+version = "0.41.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f"
-dependencies = [
- "leb128",
-]
-
-[[package]]
-name = "wasm-encoder"
-version = "0.41.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
+checksum = "972f97a5d8318f908dded23594188a90bcd09365986b1163e66d70170e5287ae"
 dependencies = [
  "leb128",
 ]
 
 [[package]]
 name = "wasm-metadata"
-version = "0.10.17"
+version = "0.10.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
+checksum = "18ebaa7bd0f9e7a5e5dd29b9a998acf21c4abed74265524dd7e85934597bfb10"
 dependencies = [
  "anyhow",
  "indexmap 2.0.0",
@@ -3157,35 +3148,36 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.2.46"
+version = "0.2.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82e89f070944fe6b516669e81589f63ddd90fe8f94f33b119908ebdf22593cbf"
+checksum = "66190ebefee2459923201c7683489eda894e703625b0c91324b1186b830410e0"
 dependencies = [
  "egg",
  "log",
  "rand",
  "thiserror",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.15.1"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea4e4f4234958299a110ccc01b4ad396cece58a02fced8771b319c56d596f6d"
+checksum = "9411792ad7c3f356ec2b31685d6553e4e8c20ed9efa192c24ab7f7560c290598"
 dependencies = [
+ "anyhow",
  "arbitrary",
  "flagset",
  "indexmap 2.0.0",
  "leb128",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
 ]
 
 [[package]]
@@ -3230,19 +3222,9 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.118.1"
+version = "0.121.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9"
-dependencies = [
- "indexmap 2.0.0",
- "semver",
-]
-
-[[package]]
-name = "wasmparser"
-version = "0.121.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
+checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab"
 dependencies = [
  "bitflags 2.4.1",
  "indexmap 2.0.0",
@@ -3260,12 +3242,12 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.2.78"
+version = "0.2.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05e32c13c59fdc64d3f6998a1d52eb1d362b6904a88b754190ccb85661ad577a"
+checksum = "60e73986a6b7fdfedb7c5bf9e7eb71135486507c8fbc4c0c42cffcb6532988b7"
 dependencies = [
  "anyhow",
- "wasmparser 0.121.0",
+ "wasmparser",
 ]
 
 [[package]]
@@ -3296,8 +3278,8 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "wasi-cap-std-sync",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
  "wasmtime-cache",
  "wasmtime-component-macro",
  "wasmtime-component-util",
@@ -3429,7 +3411,7 @@ dependencies = [
  "tokio",
  "tracing",
  "walkdir",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime",
  "wasmtime-cache",
  "wasmtime-cli-flags",
@@ -3443,10 +3425,10 @@ dependencies = [
  "wasmtime-wasi-nn",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
- "wast 70.0.2",
+ "wast 71.0.1",
  "wat",
  "windows-sys 0.52.0",
- "wit-component 0.20.1",
+ "wit-component 0.21.0",
 ]
 
 [[package]]
@@ -3475,7 +3457,7 @@ dependencies = [
  "wasmtime",
  "wasmtime-component-util",
  "wasmtime-wit-bindgen",
- "wit-parser",
+ "wit-parser 0.14.0",
 ]
 
 [[package]]
@@ -3499,7 +3481,7 @@ dependencies = [
  "object",
  "target-lexicon",
  "thiserror",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
@@ -3538,8 +3520,8 @@ dependencies = [
  "serde_derive",
  "target-lexicon",
  "thiserror",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
  "wasmprinter",
  "wasmtime-component-util",
  "wasmtime-types",
@@ -3554,7 +3536,7 @@ dependencies = [
  "component-fuzz-util",
  "env_logger",
  "libfuzzer-sys",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmprinter",
  "wasmtime-environ",
  "wat",
@@ -3611,7 +3593,7 @@ dependencies = [
  "rand",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime",
  "wasmtime-fuzzing",
 ]
@@ -3631,12 +3613,12 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "v8",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wasm-mutate",
  "wasm-smith",
  "wasm-spec-interpreter",
  "wasmi",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmprinter",
  "wasmtime",
  "wasmtime-wast",
@@ -3683,7 +3665,7 @@ dependencies = [
  "rand",
  "rustix",
  "sptr",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wasmtime-asm-macros",
  "wasmtime-environ",
  "wasmtime-fiber",
@@ -3701,7 +3683,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "thiserror",
- "wasmparser 0.121.0",
+ "wasmparser",
 ]
 
 [[package]]
@@ -3812,7 +3794,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 70.0.2",
+ "wast 71.0.1",
 ]
 
 [[package]]
@@ -3824,7 +3806,7 @@ dependencies = [
  "gimli",
  "object",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "winch-codegen",
@@ -3837,7 +3819,7 @@ dependencies = [
  "anyhow",
  "heck",
  "indexmap 2.0.0",
- "wit-parser",
+ "wit-parser 0.14.0",
 ]
 
 [[package]]
@@ -3855,24 +3837,24 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "70.0.2"
+version = "71.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3d5061300042ff5065123dae1e27d00c03f567d34a2937c8472255148a216dc"
+checksum = "647c3ac4354da32688537e8fc4d2fe6c578df51896298cb64727d98088a1fd26"
 dependencies = [
  "bumpalo",
  "leb128",
  "memchr",
  "unicode-width",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
 ]
 
 [[package]]
 name = "wat"
-version = "1.0.85"
+version = "1.0.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afd7357b6cc46d46a2509c43dcb1dd4131dafbf4e75562d87017b5a05ffad2d6"
+checksum = "b69c36f634411568a2c6d24828b674961e37ea03340fe1d605c337ed8162d901"
 dependencies = [
- "wast 70.0.2",
+ "wast 71.0.1",
 ]
 
 [[package]]
@@ -3997,7 +3979,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-environ",
 ]
 
@@ -4013,7 +3995,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4043,7 +4025,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4213,9 +4195,9 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0e367a0bdb6b9f6db939a21749dfcd398b62a23f58afa5c44e7de16289dbeba"
+checksum = "6237168d93497b26dacdab157b08ad2787d74cdce10f89735f791b2a225eba4d"
 dependencies = [
  "bitflags 2.4.1",
  "wit-bindgen-rust-macro",
@@ -4223,33 +4205,33 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen-core"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147bee3fde39f80da448dc971d3357c34f5810605e0ba59345ebe576002371c8"
+checksum = "a3d22547a645a7d3e4311b977a34f12e1d47df48125dc06e11e45984c99282ed"
 dependencies = [
  "anyhow",
- "wit-component 0.18.2",
- "wit-parser",
+ "wit-component 0.20.1",
+ "wit-parser 0.13.1",
 ]
 
 [[package]]
 name = "wit-bindgen-rust"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3033046c0f9112a67dc6c1e4281347c8d5f1e01d4cafc01321c259d45e3b8e4f"
+checksum = "c6a691b95d07cc4e7f7aa259b480f7f0208c4ded49406fd4f8bfd5a5a61c5db1"
 dependencies = [
  "anyhow",
  "heck",
  "wasm-metadata",
  "wit-bindgen-core",
- "wit-component 0.18.2",
+ "wit-component 0.20.1",
 ]
 
 [[package]]
 name = "wit-bindgen-rust-macro"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f191047320b2c8bd05a5d367a28c7fe0ad409d43153efc8881aa775a652ca112"
+checksum = "763cc6195d4ede622e5388051e381d397930016c8dc2f1199d3b48eb26781b6a"
 dependencies = [
  "anyhow",
  "proc-macro2",
@@ -4257,14 +4239,14 @@ dependencies = [
  "syn 2.0.32",
  "wit-bindgen-core",
  "wit-bindgen-rust",
- "wit-component 0.18.2",
+ "wit-component 0.20.1",
 ]
 
 [[package]]
 name = "wit-component"
-version = "0.18.2"
+version = "0.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2"
+checksum = "331de496d439010797c17637d8002712b9b69110f1669164c09dfa226ad277bb"
 dependencies = [
  "anyhow",
  "bitflags 2.4.1",
@@ -4273,17 +4255,17 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.38.1",
+ "wasm-encoder",
  "wasm-metadata",
- "wasmparser 0.118.1",
- "wit-parser",
+ "wasmparser",
+ "wit-parser 0.13.1",
 ]
 
 [[package]]
 name = "wit-component"
-version = "0.20.1"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "331de496d439010797c17637d8002712b9b69110f1669164c09dfa226ad277bb"
+checksum = "be60cd1b2ff7919305301d0c27528d4867bd793afe890ba3837743da9655d91b"
 dependencies = [
  "anyhow",
  "bitflags 2.4.1",
@@ -4292,10 +4274,10 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wasm-metadata",
- "wasmparser 0.121.0",
- "wit-parser",
+ "wasmparser",
+ "wit-parser 0.14.0",
 ]
 
 [[package]]
@@ -4315,6 +4297,24 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "wit-parser"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee4ad7310367bf272507c0c8e0c74a80b4ed586b833f7c7ca0b7588f686f11a"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.0.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
 [[package]]
 name = "witx"
 version = "0.9.1"
diff --git a/Cargo.toml b/Cargo.toml
index 18b6c0c17..2e7ebfd6b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -230,18 +230,18 @@ io-extras = "0.18.0"
 rustix = "0.38.21"
 is-terminal = "0.4.0"
 # wit-bindgen:
-wit-bindgen = { version = "0.15.0", default-features = false }
+wit-bindgen = { version = "0.17.0", default-features = false }
 
 # wasm-tools family:
-wasmparser = "0.121.0"
-wat = "1.0.85"
-wast = "70.0.2"
-wasmprinter = "0.2.78"
-wasm-encoder = "0.41.0"
-wasm-smith = "0.15.1"
-wasm-mutate = "0.2.46"
-wit-parser = "0.13.1"
-wit-component = "0.20.1"
+wasmparser = "0.121.2"
+wat = "1.0.88"
+wast = "71.0.1"
+wasmprinter = "0.2.80"
+wasm-encoder = "0.41.2"
+wasm-smith = "0.16.1"
+wasm-mutate = "0.2.48"
+wit-parser = "0.14.0"
+wit-component = "0.21.0"
 
 # Non-Bytecode Alliance maintained dependencies:
 # --------------------------
diff --git a/crates/fuzzing/src/generators/module.rs b/crates/fuzzing/src/generators/module.rs
index e27422021..b669ff25d 100644
--- a/crates/fuzzing/src/generators/module.rs
+++ b/crates/fuzzing/src/generators/module.rs
@@ -62,7 +62,7 @@ impl ModuleConfig {
         let mut module = wasm_smith::Module::new(self.config.clone(), input)?;
 
         if let Some(default_fuel) = default_fuel {
-            module.ensure_termination(default_fuel);
+            module.ensure_termination(default_fuel).unwrap();
         }
 
         Ok(module)
diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs
index 8831865d9..9381e5785 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -21,6 +21,7 @@ mod stacks;
 use self::diff_wasmtime::WasmtimeInstance;
 use self::engine::{DiffEngine, DiffInstance};
 use crate::generators::{self, DiffValue, DiffValueType};
+use crate::single_module_fuzzer::KnownValid;
 use arbitrary::Arbitrary;
 pub use stacks::check_stacks;
 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
@@ -135,7 +136,12 @@ pub enum Timeout {
 /// panic or segfault or anything else that can be detected "passively".
 ///
 /// The engine will be configured using provided config.
-pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, timeout: Timeout) {
+pub fn instantiate(
+    wasm: &[u8],
+    known_valid: KnownValid,
+    config: &generators::Config,
+    timeout: Timeout,
+) {
     let mut store = config.to_store();
 
     let module = match compile_module(store.engine(), wasm, known_valid, config) {
@@ -193,7 +199,7 @@ pub enum Command {
 /// The modules are expected to *not* have start functions as no timeouts are configured.
 pub fn instantiate_many(
     modules: &[Vec<u8>],
-    known_valid: bool,
+    known_valid: KnownValid,
     config: &generators::Config,
     commands: &[Command],
 ) {
@@ -246,13 +252,13 @@ pub fn instantiate_many(
 fn compile_module(
     engine: &Engine,
     bytes: &[u8],
-    known_valid: bool,
+    known_valid: KnownValid,
     config: &generators::Config,
 ) -> Option<Module> {
     log_wasm(bytes);
     match config.compile(engine, bytes) {
         Ok(module) => Some(module),
-        Err(_) if !known_valid => None,
+        Err(_) if known_valid == KnownValid::No => None,
         Err(e) => {
             if let generators::InstanceAllocationStrategy::Pooling(c) = &config.wasmtime.strategy {
                 // When using the pooling allocator, accept failures to compile
@@ -602,7 +608,7 @@ pub fn table_ops(
 
         let wasm = ops.to_wasm_binary();
         log_wasm(&wasm);
-        let module = match compile_module(store.engine(), &wasm, false, &fuzz_config) {
+        let module = match compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config) {
             Some(m) => m,
             None => return 0,
         };
diff --git a/crates/fuzzing/src/oracles/diff_wasmtime.rs b/crates/fuzzing/src/oracles/diff_wasmtime.rs
index 9333c02e6..f844a1901 100644
--- a/crates/fuzzing/src/oracles/diff_wasmtime.rs
+++ b/crates/fuzzing/src/oracles/diff_wasmtime.rs
@@ -4,6 +4,7 @@ use crate::generators::{self, DiffValue, DiffValueType, WasmtimeConfig};
 use crate::oracles::dummy;
 use crate::oracles::engine::DiffInstance;
 use crate::oracles::{compile_module, engine::DiffEngine, StoreLimits};
+use crate::single_module_fuzzer::KnownValid;
 use anyhow::{Context, Error, Result};
 use arbitrary::Unstructured;
 use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val};
@@ -36,7 +37,7 @@ impl DiffEngine for WasmtimeEngine {
 
     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
         let store = self.config.to_store();
-        let module = compile_module(store.engine(), wasm, true, &self.config).unwrap();
+        let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap();
         let instance = WasmtimeInstance::new(store, module)?;
         Ok(Box::new(instance))
     }
diff --git a/crates/fuzzing/src/single_module_fuzzer.rs b/crates/fuzzing/src/single_module_fuzzer.rs
index 6e2ef0738..3a0f3cb13 100644
--- a/crates/fuzzing/src/single_module_fuzzer.rs
+++ b/crates/fuzzing/src/single_module_fuzzer.rs
@@ -95,8 +95,8 @@ macro_rules! single_module_fuzzer {
 /// module.
 pub fn execute<'a, T, U>(
     input: &'a [u8],
-    run: fn(&[u8], bool, T, &mut Unstructured<'a>) -> Result<U>,
-    gen_module: fn(&mut T, &mut Unstructured<'a>) -> Result<Vec<u8>>,
+    run: fn(&[u8], KnownValid, T, &mut Unstructured<'a>) -> Result<U>,
+    gen_module: fn(&mut T, &mut Unstructured<'a>) -> Result<(Vec<u8>, KnownValid)>,
 ) -> Result<U>
 where
     T: Arbitrary<'a>,
@@ -113,12 +113,29 @@ where
     };
     let mut u = Unstructured::new(fuzz_data);
     let mut config = u.arbitrary()?;
-    let generated = gen_module(&mut config, &mut u)?;
+    let (generated, known_valid) = gen_module(&mut config, &mut u)?;
     let module = module_in_input.unwrap_or(&generated);
     if let Ok(file) = std::env::var("WRITE_FUZZ_INPUT_TO") {
         std::fs::write(file, encode_module(&module, &fuzz_data)).unwrap();
     }
-    run(module, module_in_input.is_none(), config, &mut u)
+    let known_valid = if module_in_input.is_some() {
+        KnownValid::No
+    } else {
+        known_valid
+    };
+    run(module, known_valid, config, &mut u)
+}
+
+/// Used as part of `execute` above to determine whether a module is known to
+/// be valid ahead of time.
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+pub enum KnownValid {
+    /// This module is known to be valid so it should assert compilation
+    /// succeeds for example.
+    Yes,
+    /// This module is not known to be valid and it may not compile
+    /// successfully. Note that it's also not known to compile unsuccessfully.
+    No,
 }
 
 const SECTION_NAME: &str = "wasmtime-fuzz-input";
@@ -133,7 +150,7 @@ pub fn mutate<T>(
     data: &mut [u8],
     mut size: usize,
     max_size: usize,
-    gen_module: fn(&mut T, &mut Unstructured<'_>) -> Result<Vec<u8>>,
+    gen_module: fn(&mut T, &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)>,
     mutate: fn(&mut [u8], usize, usize) -> usize,
 ) -> usize
 where
@@ -170,7 +187,7 @@ where
         .arbitrary()
         .and_then(|mut config| gen_module(&mut config, &mut u))
     {
-        Ok(module) => {
+        Ok((module, _known_valid)) => {
             let module = encode_module(&module, &data[..new_size]);
 
             if module.len() < max_size {
@@ -295,10 +312,10 @@ mod tests {
             let run2 = run_config::<(u32, u32)>;
 
             if let Ok((module, known_valid)) = execute(&buf[..seed_size], run1, gen) {
-                assert!(known_valid);
+                assert_eq!(known_valid, KnownValid::Yes);
                 let new_size = mutate(&mut buf, seed_size, max_size, gen, noop_mutate);
                 if let Ok((module2, known_valid)) = execute(&buf[..new_size], run2, gen) {
-                    assert!(!known_valid);
+                    assert_eq!(known_valid, KnownValid::No);
                     compares += 1;
                     if module != module2 {
                         panic!("modules differ");
@@ -313,21 +330,24 @@ mod tests {
 
         fn run_config<T>(
             data: &[u8],
-            known_valid: bool,
+            known_valid: KnownValid,
             _: T,
             _: &mut Unstructured<'_>,
-        ) -> Result<(Vec<u8>, bool)>
+        ) -> Result<(Vec<u8>, KnownValid)>
         where
             T: for<'a> Arbitrary<'a>,
         {
             Ok((data.to_vec(), known_valid))
         }
 
-        fn gen<T>(_: &mut T, u: &mut Unstructured<'_>) -> Result<Vec<u8>>
+        fn gen<T>(_: &mut T, u: &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)>
         where
             T: for<'a> Arbitrary<'a>,
         {
-            Ok(u.arbitrary::<wasm_smith::Module>()?.to_bytes())
+            Ok((
+                u.arbitrary::<wasm_smith::Module>()?.to_bytes(),
+                KnownValid::Yes,
+            ))
         }
 
         fn noop_mutate(_buf: &mut [u8], size: usize, _new_size: usize) -> usize {
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index 97c0119f3..f440fa4e5 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -3,7 +3,7 @@ use crate::preview2::{
         host::{monotonic_clock, wall_clock},
         HostMonotonicClock, HostWallClock,
     },
-    filesystem::Dir,
+    filesystem::{Dir, OpenMode},
     network::{SocketAddrCheck, SocketAddrUse},
     pipe, random, stdio,
     stdio::{StdinStream, StdoutStream},
@@ -144,8 +144,17 @@ impl WasiCtxBuilder {
         file_perms: FilePerms,
         path: impl AsRef<str>,
     ) -> &mut Self {
-        self.preopens
-            .push((Dir::new(dir, perms, file_perms), path.as_ref().to_owned()));
+        let mut open_mode = OpenMode::empty();
+        if perms.contains(DirPerms::READ) {
+            open_mode |= OpenMode::READ;
+        }
+        if perms.contains(DirPerms::MUTATE) {
+            open_mode |= OpenMode::WRITE;
+        }
+        self.preopens.push((
+            Dir::new(dir, perms, file_perms, open_mode),
+            path.as_ref().to_owned(),
+        ));
         self
     }
 
diff --git a/crates/wasi/src/preview2/filesystem.rs b/crates/wasi/src/preview2/filesystem.rs
index 1f1d149dd..3b54797f6 100644
--- a/crates/wasi/src/preview2/filesystem.rs
+++ b/crates/wasi/src/preview2/filesystem.rs
@@ -67,20 +67,40 @@ bitflags::bitflags! {
     }
 }
 
+bitflags::bitflags! {
+    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+    pub struct OpenMode: usize {
+        const READ = 0b1;
+        const WRITE = 0b10;
+    }
+}
+
 pub struct File {
+    /// The operating system File this struct is mediating access to.
+    ///
     /// Wrapped in an Arc because the same underlying file is used for
-    /// implementing the stream types. Also needed for [`spawn_blocking`].
+    /// implementing the stream types. A copy is also needed for
+    /// [`spawn_blocking`].
     ///
     /// [`spawn_blocking`]: Self::spawn_blocking
     pub file: Arc<cap_std::fs::File>,
+    /// Permissions to enforce on access to the file. These permissions are
+    /// specified by a user of the `crate::preview2::WasiCtxBuilder`, and are
+    /// enforced prior to any enforced by the underlying operating system.
     pub perms: FilePerms,
+    /// The mode the file was opened under: bits for reading, and writing.
+    /// Required to correctly report the DescriptorFlags, because cap-std
+    /// doesn't presently provide a cross-platform equivelant of reading the
+    /// oflags back out using fcntl.
+    pub open_mode: OpenMode,
 }
 
 impl File {
-    pub fn new(file: cap_std::fs::File, perms: FilePerms) -> Self {
+    pub fn new(file: cap_std::fs::File, perms: FilePerms, open_mode: OpenMode) -> Self {
         Self {
             file: Arc::new(file),
             perms,
+            open_mode,
         }
     }
 
@@ -106,17 +126,41 @@ bitflags::bitflags! {
 
 #[derive(Clone)]
 pub struct Dir {
+    /// The operating system file descriptor this struct is mediating access
+    /// to.
+    ///
+    /// Wrapped in an Arc because a copy is needed for [`spawn_blocking`].
+    ///
+    /// [`spawn_blocking`]: Self::spawn_blocking
     pub dir: Arc<cap_std::fs::Dir>,
+    /// Permissions to enforce on access to this directory. These permissions
+    /// are specified by a user of the `crate::preview2::WasiCtxBuilder`, and
+    /// are enforced prior to any enforced by the underlying operating system.
+    ///
+    /// These permissions are also enforced on any directories opened under
+    /// this directory.
     pub perms: DirPerms,
+    /// Permissions to enforce on any files opened under this directory.
     pub file_perms: FilePerms,
+    /// The mode the directory was opened under: bits for reading, and writing.
+    /// Required to correctly report the DescriptorFlags, because cap-std
+    /// doesn't presently provide a cross-platform equivelant of reading the
+    /// oflags back out using fcntl.
+    pub open_mode: OpenMode,
 }
 
 impl Dir {
-    pub fn new(dir: cap_std::fs::Dir, perms: DirPerms, file_perms: FilePerms) -> Self {
+    pub fn new(
+        dir: cap_std::fs::Dir,
+        perms: DirPerms,
+        file_perms: FilePerms,
+        open_mode: OpenMode,
+    ) -> Self {
         Dir {
             dir: Arc::new(dir),
             perms,
             file_perms,
+            open_mode,
         }
     }
 
diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs
index f2b67eca0..c6d93713f 100644
--- a/crates/wasi/src/preview2/host/filesystem.rs
+++ b/crates/wasi/src/preview2/host/filesystem.rs
@@ -4,8 +4,9 @@ use crate::preview2::bindings::filesystem::types::{
     self, ErrorCode, HostDescriptor, HostDirectoryEntryStream,
 };
 use crate::preview2::bindings::io::streams::{InputStream, OutputStream};
-use crate::preview2::filesystem::{Descriptor, Dir, File, ReaddirIterator};
-use crate::preview2::filesystem::{FileInputStream, FileOutputStream};
+use crate::preview2::filesystem::{
+    Descriptor, Dir, File, FileInputStream, FileOutputStream, OpenMode, ReaddirIterator,
+};
 use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, WasiView};
 use anyhow::Context;
 use wasmtime::component::Resource;
@@ -130,10 +131,10 @@ impl<T: WasiView> HostDescriptor for T {
             Descriptor::File(f) => {
                 let flags = f.spawn_blocking(|f| f.get_fd_flags()).await?;
                 let mut flags = get_from_fdflags(flags);
-                if f.perms.contains(FilePerms::READ) {
+                if f.open_mode.contains(OpenMode::READ) {
                     flags |= DescriptorFlags::READ;
                 }
-                if f.perms.contains(FilePerms::WRITE) {
+                if f.open_mode.contains(OpenMode::WRITE) {
                     flags |= DescriptorFlags::WRITE;
                 }
                 Ok(flags)
@@ -141,10 +142,10 @@ impl<T: WasiView> HostDescriptor for T {
             Descriptor::Dir(d) => {
                 let flags = d.spawn_blocking(|d| d.get_fd_flags()).await?;
                 let mut flags = get_from_fdflags(flags);
-                if d.perms.contains(DirPerms::READ) {
+                if d.open_mode.contains(OpenMode::READ) {
                     flags |= DescriptorFlags::READ;
                 }
-                if d.perms.contains(DirPerms::MUTATE) {
+                if d.open_mode.contains(OpenMode::WRITE) {
                     flags |= DescriptorFlags::MUTATE_DIRECTORY;
                 }
                 Ok(flags)
@@ -507,28 +508,40 @@ impl<T: WasiView> HostDescriptor for T {
             }
         }
 
+        // Track whether we are creating file, for permission check:
+        let mut create = false;
+        // Track open mode, for permission check and recording in created descriptor:
+        let mut open_mode = OpenMode::empty();
+        // Construct the OpenOptions to give the OS:
         let mut opts = cap_std::fs::OpenOptions::new();
         opts.maybe_dir(true);
 
-        if oflags.contains(OpenFlags::CREATE | OpenFlags::EXCLUSIVE) {
-            opts.create_new(true);
-            opts.write(true);
-        } else if oflags.contains(OpenFlags::CREATE) {
-            opts.create(true);
+        if oflags.contains(OpenFlags::CREATE) {
+            if oflags.contains(OpenFlags::EXCLUSIVE) {
+                opts.create_new(true);
+            } else {
+                opts.create(true);
+            }
+            create = true;
             opts.write(true);
+            open_mode |= OpenMode::WRITE;
         }
+
         if oflags.contains(OpenFlags::TRUNCATE) {
             opts.truncate(true);
         }
         if flags.contains(DescriptorFlags::READ) {
             opts.read(true);
+            open_mode |= OpenMode::READ;
         }
         if flags.contains(DescriptorFlags::WRITE) {
             opts.write(true);
+            open_mode |= OpenMode::WRITE;
         } else {
             // If not opened write, open read. This way the OS lets us open
             // the file, but we can use perms to reject use of the file later.
             opts.read(true);
+            open_mode |= OpenMode::READ;
         }
         if symlink_follow(path_flags) {
             opts.follow(FollowSymlinks::Yes);
@@ -538,8 +551,8 @@ impl<T: WasiView> HostDescriptor for T {
 
         // These flags are not yet supported in cap-std:
         if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
-            | flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
-            | flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
+            || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
+            || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
         {
             Err(ErrorCode::Unsupported)?;
         }
@@ -553,6 +566,15 @@ impl<T: WasiView> HostDescriptor for T {
             }
         }
 
+        // Now enforce this WasiCtx's permissions before letting the OS have
+        // its shot:
+        if !d.perms.contains(DirPerms::MUTATE) && create {
+            Err(ErrorCode::NotPermitted)?;
+        }
+        if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
+            Err(ErrorCode::NotPermitted)?;
+        }
+
         // Represents each possible outcome from the spawn_blocking operation.
         // This makes sure we don't have to give spawn_blocking any way to
         // manipulate the table.
@@ -582,15 +604,17 @@ impl<T: WasiView> HostDescriptor for T {
             .await?;
 
         match opened {
-            OpenResult::Dir(dir) => {
-                Ok(table.push(Descriptor::Dir(Dir::new(dir, d.perms, d.file_perms)))?)
-            }
-
-            OpenResult::File(file) => Ok(table.push(Descriptor::File(File::new(
-                file,
-                mask_file_perms(d.file_perms, flags),
+            OpenResult::Dir(dir) => Ok(table.push(Descriptor::Dir(Dir::new(
+                dir,
+                d.perms,
+                d.file_perms,
+                open_mode,
             )))?),
 
+            OpenResult::File(file) => {
+                Ok(table.push(Descriptor::File(File::new(file, d.file_perms, open_mode)))?)
+            }
+
             OpenResult::NotDir => Err(ErrorCode::NotDirectory.into()),
         }
     }
@@ -1040,18 +1064,6 @@ fn symlink_follow(path_flags: types::PathFlags) -> bool {
     path_flags.contains(types::PathFlags::SYMLINK_FOLLOW)
 }
 
-fn mask_file_perms(p: FilePerms, flags: types::DescriptorFlags) -> FilePerms {
-    use types::DescriptorFlags;
-    let mut out = FilePerms::empty();
-    if p.contains(FilePerms::READ) && flags.contains(DescriptorFlags::READ) {
-        out |= FilePerms::READ;
-    }
-    if p.contains(FilePerms::WRITE) && flags.contains(DescriptorFlags::WRITE) {
-        out |= FilePerms::WRITE;
-    }
-    out
-}
-
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/crates/wasmtime/src/runtime/component/func.rs b/crates/wasmtime/src/runtime/component/func.rs
index 4775f3b8d..2458a132c 100644
--- a/crates/wasmtime/src/runtime/component/func.rs
+++ b/crates/wasmtime/src/runtime/component/func.rs
@@ -692,7 +692,7 @@ impl Func {
             // of the component.
             flags.set_may_enter(true);
 
-            let (calls, host_table) = store.0.component_calls_and_host_table();
+            let (calls, host_table, _) = store.0.component_resource_state();
             ResourceTables {
                 calls,
                 host_table: Some(host_table),
diff --git a/crates/wasmtime/src/runtime/component/func/options.rs b/crates/wasmtime/src/runtime/component/func/options.rs
index 6cb038f4f..dc5c574a7 100644
--- a/crates/wasmtime/src/runtime/component/func/options.rs
+++ b/crates/wasmtime/src/runtime/component/func/options.rs
@@ -1,4 +1,5 @@
 use crate::component::matching::InstanceType;
+use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables};
 use crate::component::ResourceType;
 use crate::store::{StoreId, StoreOpaque};
 use crate::StoreContextMut;
@@ -278,7 +279,7 @@ impl<'a, T> LowerContext<'a, T> {
     ///
     /// The `ty` provided is which table to put this into.
     pub fn guest_resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 {
-        self.resource_tables().resource_lower_own(Some(ty), rep)
+        self.resource_tables().guest_resource_lower_own(rep, ty)
     }
 
     /// Lowers a `borrow` resource into the guest, converting the `rep` to a
@@ -298,19 +299,19 @@ impl<'a, T> LowerContext<'a, T> {
         if unsafe { (*self.instance).resource_owned_by_own_instance(ty) } {
             return rep;
         }
-        self.resource_tables().resource_lower_borrow(Some(ty), rep)
+        self.resource_tables().guest_resource_lower_borrow(rep, ty)
     }
 
     /// Lifts a host-owned `own` resource at the `idx` specified into the
     /// representation of that resource.
-    pub fn host_resource_lift_own(&mut self, idx: u32) -> Result<u32> {
-        self.resource_tables().resource_lift_own(None, idx)
+    pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
+        self.resource_tables().host_resource_lift_own(idx)
     }
 
     /// Lifts a host-owned `borrow` resource at the `idx` specified into the
     /// representation of that resource.
-    pub fn host_resource_lift_borrow(&mut self, idx: u32) -> Result<u32> {
-        self.resource_tables().resource_lift_borrow(None, idx)
+    pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
+        self.resource_tables().host_resource_lift_borrow(idx)
     }
 
     /// Lowers a resource into the host-owned table, returning the index it was
@@ -318,8 +319,8 @@ impl<'a, T> LowerContext<'a, T> {
     ///
     /// Note that this is a special case for `Resource<T>`. Most of the time a
     /// host value shouldn't be lowered with a lowering context.
-    pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 {
-        self.resource_tables().resource_lower_own(None, rep)
+    pub fn host_resource_lower_own(&mut self, rep: u32) -> HostResourceIndex {
+        self.resource_tables().host_resource_lower_own(rep)
     }
 
     /// Returns the underlying resource type for the `ty` table specified.
@@ -335,26 +336,27 @@ impl<'a, T> LowerContext<'a, T> {
         InstanceType::new(unsafe { &*self.instance })
     }
 
-    fn resource_tables(&mut self) -> ResourceTables<'_> {
-        let (calls, host_table) = self.store.0.component_calls_and_host_table();
-        ResourceTables {
-            host_table: Some(host_table),
-            calls,
-            // Note that the unsafety here should be valid given the contract of
-            // `LowerContext::new`.
-            tables: Some(unsafe { (*self.instance).component_resource_tables() }),
-        }
+    fn resource_tables(&mut self) -> HostResourceTables<'_> {
+        let (calls, host_table, host_resource_data) = self.store.0.component_resource_state();
+        HostResourceTables::from_parts(
+            ResourceTables {
+                host_table: Some(host_table),
+                calls,
+                // Note that the unsafety here should be valid given the contract of
+                // `LowerContext::new`.
+                tables: Some(unsafe { (*self.instance).component_resource_tables() }),
+            },
+            host_resource_data,
+        )
     }
 
-    /// Begins a call into the component instance, starting recording of
-    /// metadata related to resource borrowing.
+    /// See [`HostResourceTables::enter_call`].
     #[inline]
     pub fn enter_call(&mut self) {
         self.resource_tables().enter_call()
     }
 
-    /// Completes a call into the component instance, validating that it's ok to
-    /// complete by ensuring the are no remaining active borrows.
+    /// See [`HostResourceTables::exit_call`].
     #[inline]
     pub fn exit_call(&mut self) -> Result<()> {
         self.resource_tables().exit_call()
@@ -379,6 +381,7 @@ pub struct LiftContext<'a> {
     instance: *mut ComponentInstance,
 
     host_table: &'a mut ResourceTable,
+    host_resource_data: &'a mut HostResourceData,
 
     calls: &'a mut CallContexts,
 }
@@ -404,8 +407,8 @@ impl<'a> LiftContext<'a> {
         // so it's hacked around a bit. This unsafe pointer cast could be fixed
         // with more methods in more places, but it doesn't seem worth doing it
         // at this time.
-        let (calls, host_table) =
-            (&mut *(store as *mut StoreOpaque)).component_calls_and_host_table();
+        let (calls, host_table, host_resource_data) =
+            (&mut *(store as *mut StoreOpaque)).component_resource_state();
         let memory = options.memory.map(|_| options.memory(store));
 
         LiftContext {
@@ -415,6 +418,7 @@ impl<'a> LiftContext<'a> {
             instance,
             calls,
             host_table,
+            host_resource_data,
         }
     }
 
@@ -450,7 +454,7 @@ impl<'a> LiftContext<'a> {
         ty: TypeResourceTableIndex,
         idx: u32,
     ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<InstanceFlags>)> {
-        let idx = self.resource_tables().resource_lift_own(Some(ty), idx)?;
+        let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
         // Note that the unsafety here should be valid given the contract of
         // `LiftContext::new`.
         let (dtor, flags) = unsafe { (*self.instance).dtor_and_flags(ty) };
@@ -463,19 +467,19 @@ impl<'a> LiftContext<'a> {
         ty: TypeResourceTableIndex,
         idx: u32,
     ) -> Result<u32> {
-        self.resource_tables().resource_lift_borrow(Some(ty), idx)
+        self.resource_tables().guest_resource_lift_borrow(idx, ty)
     }
 
     /// Lowers a resource into the host-owned table, returning the index it was
     /// inserted at.
-    pub fn host_resource_lower_own(&mut self, rep: u32) -> u32 {
-        self.resource_tables().resource_lower_own(None, rep)
+    pub fn host_resource_lower_own(&mut self, rep: u32) -> HostResourceIndex {
+        self.resource_tables().host_resource_lower_own(rep)
     }
 
     /// Lowers a resource into the host-owned table, returning the index it was
     /// inserted at.
-    pub fn host_resource_lower_borrow(&mut self, rep: u32) -> u32 {
-        self.resource_tables().resource_lower_borrow(None, rep)
+    pub fn host_resource_lower_borrow(&mut self, rep: u32) -> HostResourceIndex {
+        self.resource_tables().host_resource_lower_borrow(rep)
     }
 
     /// Returns the underlying type of the resource table specified by `ty`.
@@ -491,23 +495,26 @@ impl<'a> LiftContext<'a> {
         InstanceType::new(unsafe { &*self.instance })
     }
 
-    fn resource_tables(&mut self) -> ResourceTables<'_> {
-        ResourceTables {
-            host_table: Some(self.host_table),
-            calls: self.calls,
-            // Note that the unsafety here should be valid given the contract of
-            // `LiftContext::new`.
-            tables: Some(unsafe { (*self.instance).component_resource_tables() }),
-        }
+    fn resource_tables(&mut self) -> HostResourceTables<'_> {
+        HostResourceTables::from_parts(
+            ResourceTables {
+                host_table: Some(self.host_table),
+                calls: self.calls,
+                // Note that the unsafety here should be valid given the contract of
+                // `LiftContext::new`.
+                tables: Some(unsafe { (*self.instance).component_resource_tables() }),
+            },
+            self.host_resource_data,
+        )
     }
 
-    /// Same as `LowerContext::enter_call`
+    /// See [`HostResourceTables::enter_call`].
     #[inline]
     pub fn enter_call(&mut self) {
         self.resource_tables().enter_call()
     }
 
-    /// Same as `LiftContext::enter_call`
+    /// See [`HostResourceTables::exit_call`].
     #[inline]
     pub fn exit_call(&mut self) -> Result<()> {
         self.resource_tables().exit_call()
diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs
index 187a3337d..f3e40cb8e 100644
--- a/crates/wasmtime/src/runtime/component/mod.rs
+++ b/crates/wasmtime/src/runtime/component/mod.rs
@@ -63,6 +63,8 @@ pub use self::resources::{Resource, ResourceAny};
 pub use self::types::{ResourceType, Type};
 pub use self::values::{Enum, Flags, List, OptionVal, Record, ResultVal, Tuple, Val, Variant};
 
+pub(crate) use self::resources::HostResourceData;
+
 // These items are expected to be used by an eventual
 // `#[derive(ComponentType)]`, they are not part of Wasmtime's API stability
 // guarantees
diff --git a/crates/wasmtime/src/runtime/component/resources.rs b/crates/wasmtime/src/runtime/component/resources.rs
index 7de8a65cf..b41b073dc 100644
--- a/crates/wasmtime/src/runtime/component/resources.rs
+++ b/crates/wasmtime/src/runtime/component/resources.rs
@@ -10,8 +10,10 @@ use std::any::TypeId;
 use std::fmt;
 use std::marker;
 use std::mem::MaybeUninit;
-use std::sync::atomic::{AtomicU32, Ordering::Relaxed};
-use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType};
+use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
+use wasmtime_environ::component::{
+    CanonicalAbiInfo, DefinedResourceIndex, InterfaceType, TypeResourceTableIndex,
+};
 use wasmtime_runtime::component::{ComponentInstance, InstanceFlags, ResourceTables};
 use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw};
 
@@ -201,49 +203,334 @@ pub struct Resource<T> {
     /// Dear rust please consider `T` used even though it's not actually used.
     _marker: marker::PhantomData<fn() -> T>,
 
-    /// Internal dynamic state tracking for this resource. This can be one of
-    /// four different states:
-    ///
-    /// * `BORROW` / `u32::MAX` - this indicates that this is a borrowed
-    ///   resource. The `rep` doesn't live in the host table and this `Resource`
-    ///   instance is transiently available. It's the host's responsibility to
-    ///   discard this resource when the borrow duration has finished.
-    ///
-    /// * `NOT_IN_TABLE` / `u32::MAX - 1` - this indicates that this is an owned
-    ///   resource not present in any store's table. This resource is not lent
-    ///   out. It can be passed as an `(own $t)` directly into a guest's table
-    ///   or it can be passed as a borrow to a guest which will insert it into
-    ///   a host store's table for dynamic borrow tracking.
-    ///
-    /// * `TAKEN` / `u32::MAX - 2` - while the `rep` is available the resource
-    ///   has been dynamically moved into a guest and cannot be moved in again.
-    ///   This is used for example to prevent the same resource from being
-    ///   passed twice to a guest.
-    ///
-    /// * All other values - any other value indicates that the value is an
-    ///   index into a store's table of host resources. It's guaranteed that the
-    ///   table entry represents a host resource and the resource may have
-    ///   borrow tracking associated with it.
-    ///
-    /// Note that this is an `AtomicU32` but it's not intended to actually be
-    /// used in conjunction with threads as generally a `Store<T>` lives on one
-    /// thread at a time. The `AtomicU32` here is used to ensure that this type
-    /// is `Send + Sync` when captured as a reference to make async programming
-    /// more ergonomic.
-    state: AtomicU32,
+    state: AtomicResourceState,
 }
 
-// See comments on `state` above for info about these values.
-const BORROW: u32 = u32::MAX;
-const NOT_IN_TABLE: u32 = u32::MAX - 1;
-const TAKEN: u32 = u32::MAX - 2;
+/// Internal dynamic state tracking for this resource. This can be one of
+/// four different states:
+///
+/// * `BORROW` / `u64::MAX` - this indicates that this is a borrowed
+///   resource. The `rep` doesn't live in the host table and this `Resource`
+///   instance is transiently available. It's the host's responsibility to
+///   discard this resource when the borrow duration has finished.
+///
+/// * `NOT_IN_TABLE` / `u64::MAX - 1` - this indicates that this is an owned
+///   resource not present in any store's table. This resource is not lent
+///   out. It can be passed as an `(own $t)` directly into a guest's table
+///   or it can be passed as a borrow to a guest which will insert it into
+///   a host store's table for dynamic borrow tracking.
+///
+/// * `TAKEN` / `u64::MAX - 2` - while the `rep` is available the resource
+///   has been dynamically moved into a guest and cannot be moved in again.
+///   This is used for example to prevent the same resource from being
+///   passed twice to a guest.
+///
+/// * All other values - any other value indicates that the value is an
+///   index into a store's table of host resources. It's guaranteed that the
+///   table entry represents a host resource and the resource may have
+///   borrow tracking associated with it. The low 32-bits of the value are
+///   the table index and the upper 32-bits are the generation.
+///
+/// Note that this is an `AtomicU64` but it's not intended to actually be
+/// used in conjunction with threads as generally a `Store<T>` lives on one
+/// thread at a time. The `AtomicU64` here is used to ensure that this type
+/// is `Send + Sync` when captured as a reference to make async programming
+/// more ergonomic.
+struct AtomicResourceState(AtomicU64);
+
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+enum ResourceState {
+    Borrow,
+    NotInTable,
+    Taken,
+    Index(HostResourceIndex),
+}
+
+impl AtomicResourceState {
+    const BORROW: Self = Self(AtomicU64::new(ResourceState::BORROW));
+    const NOT_IN_TABLE: Self = Self(AtomicU64::new(ResourceState::NOT_IN_TABLE));
+
+    fn get(&self) -> ResourceState {
+        ResourceState::decode(self.0.load(Relaxed))
+    }
+
+    fn swap(&self, state: ResourceState) -> ResourceState {
+        ResourceState::decode(self.0.swap(state.encode(), Relaxed))
+    }
+}
+
+impl ResourceState {
+    // See comments on `state` above for info about these values.
+    const BORROW: u64 = u64::MAX;
+    const NOT_IN_TABLE: u64 = u64::MAX - 1;
+    const TAKEN: u64 = u64::MAX - 2;
+
+    fn decode(bits: u64) -> ResourceState {
+        match bits {
+            Self::BORROW => Self::Borrow,
+            Self::NOT_IN_TABLE => Self::NotInTable,
+            Self::TAKEN => Self::Taken,
+            other => Self::Index(HostResourceIndex(other)),
+        }
+    }
+
+    fn encode(&self) -> u64 {
+        match self {
+            Self::Borrow => Self::BORROW,
+            Self::NotInTable => Self::NOT_IN_TABLE,
+            Self::Taken => Self::TAKEN,
+            Self::Index(index) => index.0,
+        }
+    }
+}
+
+/// Metadata tracking the state of resources within a `Store`.
+///
+/// This is a borrowed structure created from a `Store` piecemeal from below.
+/// The `ResourceTables` type holds most of the raw information and this
+/// structure tacks on a reference to `HostResourceData` to track generation
+/// numbers of host indices.
+pub struct HostResourceTables<'a> {
+    tables: ResourceTables<'a>,
+    host_resource_data: &'a mut HostResourceData,
+}
+
+/// Metadata for host-owned resources owned within a `Store`.
+///
+/// This metadata is used to prevent the ABA problem with indices handed out as
+/// part of `Resource` and `ResourceAny`. Those structures are `Copy` meaning
+/// that it's easy to reuse them, possibly accidentally. To prevent issues in
+/// the host Wasmtime attaches both an index (within `ResourceTables`) as well
+/// as a 32-bit generation counter onto each `HostResourceIndex` which the host
+/// actually holds in `Resource` and `ResourceAny`.
+///
+/// This structure holds a list which is a parallel list to the "list of reps"
+/// that's stored within `ResourceTables` elsewhere in the `Store`. This
+/// parallel list holds the last known generation of each element in the table.
+/// The generation is then compared on access to make sure it's the same.
+///
+/// Whenever a slot in the table is allocated the `cur_generation` field is
+/// pushed at the corresponding index of `generation_of_table_slot`. Whenever
+/// a field is accessed the current value of `generation_of_table_slot` is
+/// checked against the generation of the index. Whenever a slot is deallocated
+/// the generation is incremented. Put together this means that any access of a
+/// deallocated slot should deterministically provide an error.
+#[derive(Default)]
+pub struct HostResourceData {
+    cur_generation: u32,
+    generation_of_table_slot: Vec<u32>,
+}
+
+/// Host representation of an index into a table slot.
+///
+/// This is morally (u32, u32) but is encoded as a 64-bit integer. The low
+/// 32-bits are the table index and the upper 32-bits are the generation
+/// counter.
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+#[repr(transparent)]
+pub struct HostResourceIndex(u64);
+
+impl HostResourceIndex {
+    fn new(idx: u32, gen: u32) -> HostResourceIndex {
+        HostResourceIndex(u64::from(idx) | (u64::from(gen) << 32))
+    }
+
+    fn index(&self) -> u32 {
+        u32::try_from(self.0 & 0xffffffff).unwrap()
+    }
+
+    fn gen(&self) -> u32 {
+        u32::try_from(self.0 >> 32).unwrap()
+    }
+}
+
+impl<'a> HostResourceTables<'a> {
+    pub fn new_host(store: &'a mut StoreOpaque) -> HostResourceTables<'_> {
+        let (calls, host_table, host_resource_data) = store.component_resource_state();
+        HostResourceTables::from_parts(
+            ResourceTables {
+                host_table: Some(host_table),
+                calls,
+                tables: None,
+            },
+            host_resource_data,
+        )
+    }
+
+    pub fn from_parts(
+        tables: ResourceTables<'a>,
+        host_resource_data: &'a mut HostResourceData,
+    ) -> Self {
+        HostResourceTables {
+            tables,
+            host_resource_data,
+        }
+    }
+
+    /// Lifts an `own` resource that resides in the host's tables at the `idx`
+    /// specified into its `rep`.
+    ///
+    /// This method additionally takes an `expected` type which the resource is
+    /// expected to have. All host resources are stored into a single table so
+    /// this is used to perform a runtime check to ensure that the resource
+    /// still has the same type as when it was originally inserted.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if `idx` doesn't point to a valid owned resource, if
+    /// `idx` can't be lifted as an `own` (e.g. it has active borrows), or if
+    /// the resource at `idx` does not have the type `expected`.
+    pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
+        let idx = self.validate_host_index(idx, true)?;
+        self.tables.resource_lift_own(None, idx)
+    }
 
-fn host_resource_tables(store: &mut StoreOpaque) -> ResourceTables<'_> {
-    let (calls, host_table) = store.component_calls_and_host_table();
-    ResourceTables {
-        calls,
-        host_table: Some(host_table),
-        tables: None,
+    /// See [`HostResourceTables::host_resource_lift_own`].
+    pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
+        let idx = self.validate_host_index(idx, false)?;
+        self.tables.resource_lift_borrow(None, idx)
+    }
+
+    /// Lowers an `own` resource to be owned by the host.
+    ///
+    /// This returns a new index into the host's set of resource tables which
+    /// will point to the `rep` specified as well as recording that it has the
+    /// `ty` specified. The returned index is suitable for conversion into
+    /// either [`Resource`] or [`ResourceAny`].
+    pub fn host_resource_lower_own(&mut self, rep: u32) -> HostResourceIndex {
+        let idx = self.tables.resource_lower_own(None, rep);
+        self.new_host_index(idx)
+    }
+
+    /// See [`HostResourceTables::host_resource_lower_own`].
+    pub fn host_resource_lower_borrow(&mut self, rep: u32) -> HostResourceIndex {
+        let idx = self.tables.resource_lower_borrow(None, rep);
+        self.new_host_index(idx)
+    }
+
+    /// Validates that `idx` is still valid for the host tables, notably
+    /// ensuring that the generation listed in `idx` is the same as the
+    /// last recorded generation of the slot itself.
+    ///
+    /// The `is_removal` option indicates whether or not this table access will
+    /// end up removing the element from the host table. In such a situation the
+    /// current generation number is incremented.
+    fn validate_host_index(&mut self, idx: HostResourceIndex, is_removal: bool) -> Result<u32> {
+        let actual = usize::try_from(idx.index()).ok().and_then(|i| {
+            self.host_resource_data
+                .generation_of_table_slot
+                .get(i)
+                .copied()
+        });
+
+        // If `idx` is out-of-bounds then skip returning an error. In such a
+        // situation the operation that this is guarding will return a more
+        // precise error, such as a lift operation.
+        if let Some(actual) = actual {
+            if actual != idx.gen() {
+                bail!("host-owned resource is being used with the wrong type");
+            }
+        }
+
+        // Bump the current generation of this is a removal to ensure any
+        // future item placed in this slot can't be pointed to by the `idx`
+        // provided above.
+        if is_removal {
+            self.host_resource_data.cur_generation += 1;
+        }
+
+        Ok(idx.index())
+    }
+
+    /// Creates a new `HostResourceIndex` which will point to the raw table
+    /// slot provided by `idx`.
+    ///
+    /// This will register metadata necessary to track the current generation
+    /// in the returned `HostResourceIndex` as well.
+    fn new_host_index(&mut self, idx: u32) -> HostResourceIndex {
+        let list = &mut self.host_resource_data.generation_of_table_slot;
+        let gen = self.host_resource_data.cur_generation;
+        match list.get_mut(idx as usize) {
+            Some(slot) => *slot = gen,
+            None => {
+                assert_eq!(idx as usize, list.len());
+                list.push(gen);
+            }
+        }
+
+        HostResourceIndex::new(idx, gen)
+    }
+
+    /// Drops a host-owned resource from host tables.
+    ///
+    /// This method will attempt to interpret `idx` as pointing to either a
+    /// `borrow` or `own` resource with the `expected` type specified. This
+    /// method will then return the underlying `rep` if it points to an `own`
+    /// resource which can then be further processed for destruction.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if `idx` doesn't point to a valid resource, points to
+    /// an `own` with active borrows, or if it doesn't have the type `expected`
+    /// in the host tables.
+    pub fn host_resource_drop(&mut self, idx: HostResourceIndex) -> Result<Option<u32>> {
+        let idx = self.validate_host_index(idx, true)?;
+        self.tables.resource_drop(None, idx)
+    }
+
+    /// Lowers an `own` resource into the guest, converting the `rep` specified
+    /// into a guest-local index.
+    ///
+    /// The `ty` provided is which table to put this into.
+    pub fn guest_resource_lower_own(&mut self, rep: u32, ty: TypeResourceTableIndex) -> u32 {
+        self.tables.resource_lower_own(Some(ty), rep)
+    }
+
+    /// Lowers a `borrow` resource into the guest, converting the `rep`
+    /// specified into a guest-local index.
+    ///
+    /// The `ty` provided is which table to put this into.
+    ///
+    /// Note that this cannot be used in isolation because lowering a borrow
+    /// into a guest has a special case where `rep` is returned directly if `ty`
+    /// belongs to the component being lowered into. That property must be
+    /// handled by the caller of this function.
+    pub fn guest_resource_lower_borrow(&mut self, rep: u32, ty: TypeResourceTableIndex) -> u32 {
+        self.tables.resource_lower_borrow(Some(ty), rep)
+    }
+
+    /// Lifts an `own` resource from the `idx` specified from the table `ty`.
+    ///
+    /// This will lookup the appropriate table in the guest and return the `rep`
+    /// corresponding to `idx` if it's valid.
+    pub fn guest_resource_lift_own(&mut self, idx: u32, ty: TypeResourceTableIndex) -> Result<u32> {
+        self.tables.resource_lift_own(Some(ty), idx)
+    }
+
+    /// Lifts a `borrow` resource from the `idx` specified from the table `ty`.
+    ///
+    /// This will lookup the appropriate table in the guest and return the `rep`
+    /// corresponding to `idx` if it's valid.
+    pub fn guest_resource_lift_borrow(
+        &mut self,
+        idx: u32,
+        ty: TypeResourceTableIndex,
+    ) -> Result<u32> {
+        self.tables.resource_lift_borrow(Some(ty), idx)
+    }
+
+    /// Begins a call into the component instance, starting recording of
+    /// metadata related to resource borrowing.
+    #[inline]
+    pub fn enter_call(&mut self) {
+        self.tables.enter_call()
+    }
+
+    /// Completes a call into the component instance, validating that it's ok to
+    /// complete by ensuring the are no remaining active borrows.
+    #[inline]
+    pub fn exit_call(&mut self) -> Result<()> {
+        self.tables.exit_call()
     }
 }
 
@@ -257,7 +544,7 @@ where
     /// `(borrow $t)` or `(own $t)`.
     pub fn new_own(rep: u32) -> Resource<T> {
         Resource {
-            state: AtomicU32::new(NOT_IN_TABLE),
+            state: AtomicResourceState::NOT_IN_TABLE,
             rep,
             _marker: marker::PhantomData,
         }
@@ -272,7 +559,7 @@ where
     /// embedder.
     pub fn new_borrow(rep: u32) -> Resource<T> {
         Resource {
-            state: AtomicU32::new(BORROW),
+            state: AtomicResourceState::BORROW,
             rep,
             _marker: marker::PhantomData,
         }
@@ -290,19 +577,19 @@ where
     /// borrowed resources have an owner somewhere else on the stack so can only
     /// be accessed, not destroyed.
     pub fn owned(&self) -> bool {
-        match self.state.load(Relaxed) {
-            BORROW => false,
-            _ => true,
+        match self.state.get() {
+            ResourceState::Borrow => false,
+            ResourceState::Taken | ResourceState::NotInTable | ResourceState::Index(_) => true,
         }
     }
 
     fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
         match ty {
             InterfaceType::Own(t) => {
-                let rep = match self.state.load(Relaxed) {
+                let rep = match self.state.get() {
                     // If this is a borrow resource then this is a dynamic
                     // error on behalf of the embedder.
-                    BORROW => {
+                    ResourceState::Borrow => {
                         bail!("cannot lower a `borrow` resource into an `own`")
                     }
 
@@ -310,32 +597,32 @@ where
                     // dynamically transferring ownership to wasm. Record that
                     // it's no longer present and then pass through the
                     // representation.
-                    NOT_IN_TABLE => {
-                        let prev = self.state.swap(TAKEN, Relaxed);
-                        assert_eq!(prev, NOT_IN_TABLE);
+                    ResourceState::NotInTable => {
+                        let prev = self.state.swap(ResourceState::Taken);
+                        assert_eq!(prev, ResourceState::NotInTable);
                         self.rep
                     }
 
                     // This resource has already been moved into wasm so this is
                     // a dynamic error on behalf of the embedder.
-                    TAKEN => bail!("host resource already consumed"),
+                    ResourceState::Taken => bail!("host resource already consumed"),
 
                     // If this resource lives in a host table then try to take
                     // it out of the table, which may fail, and on success we
                     // can move the rep into the guest table.
-                    idx => cx.host_resource_lift_own(idx)?,
+                    ResourceState::Index(idx) => cx.host_resource_lift_own(idx)?,
                 };
                 Ok(cx.guest_resource_lower_own(t, rep))
             }
             InterfaceType::Borrow(t) => {
-                let rep = match self.state.load(Relaxed) {
+                let rep = match self.state.get() {
                     // If this is already a borrowed resource, nothing else to
                     // do and the rep is passed through.
-                    BORROW => self.rep,
+                    ResourceState::Borrow => self.rep,
 
                     // If this resource is already gone, that's a dynamic error
                     // for the embedder.
-                    TAKEN => bail!("host resource already consumed"),
+                    ResourceState::Taken => bail!("host resource already consumed"),
 
                     // If this resource is not currently in a table then it
                     // needs to move into a table to participate in state
@@ -344,16 +631,16 @@ where
                     // state.
                     //
                     // Afterwards this is the same as the `idx` case below.
-                    NOT_IN_TABLE => {
+                    ResourceState::NotInTable => {
                         let idx = cx.host_resource_lower_own(self.rep);
-                        let prev = self.state.swap(idx, Relaxed);
-                        assert_eq!(prev, NOT_IN_TABLE);
+                        let prev = self.state.swap(ResourceState::Index(idx));
+                        assert_eq!(prev, ResourceState::NotInTable);
                         cx.host_resource_lift_borrow(idx)?
                     }
 
                     // If this resource lives in a table then it needs to come
                     // out of the table with borrow-tracking employed.
-                    idx => cx.host_resource_lift_borrow(idx)?,
+                    ResourceState::Index(idx) => cx.host_resource_lift_borrow(idx)?,
                 };
                 Ok(cx.guest_resource_lower_borrow(t, rep))
             }
@@ -373,7 +660,7 @@ where
                 let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
                 assert!(dtor.is_some());
                 assert!(flags.is_none());
-                (AtomicU32::new(NOT_IN_TABLE), rep)
+                (AtomicResourceState::NOT_IN_TABLE, rep)
             }
 
             // The borrow here is lifted from the guest, but note the lack of
@@ -386,7 +673,7 @@ where
             InterfaceType::Borrow(t) => {
                 debug_assert!(cx.resource_type(t) == ResourceType::host::<T>());
                 let rep = cx.guest_resource_lift_borrow(t, index)?;
-                (AtomicU32::new(BORROW), rep)
+                (AtomicResourceState::BORROW, rep)
             }
             _ => bad_type_info(),
         };
@@ -398,23 +685,39 @@ where
     }
 
     /// Attempts to convert a [`ResourceAny`] into [`Resource`].
+    ///
+    /// This method will check that `resource` has type
+    /// `ResourceType::host::<T>()` and then convert it into a typed version of
+    /// the resource.
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if `resource` does not have type
+    /// `ResourceType::host::<T>()`. This function may also return an error if
+    /// `resource` is no longer valid, for example it was previously converted.
+    ///
+    /// # Panics
+    ///
+    /// This function will panic if `resource` does not belong to the `store`
+    /// specified.
     pub fn try_from_resource_any(
-        ResourceAny { idx, ty, own_state }: ResourceAny,
+        resource: ResourceAny,
         mut store: impl AsContextMut,
     ) -> Result<Self> {
         let store = store.as_context_mut();
         let store_id = store.0.id();
-        let mut tables = host_resource_tables(store.0);
-        assert_eq!(ty, ResourceType::host::<T>(), "resource type mismatch");
+        let mut tables = HostResourceTables::new_host(store.0);
+        let ResourceAny { idx, ty, own_state } = resource;
+        ensure!(ty == ResourceType::host::<T>(), "resource type mismatch");
         let (state, rep) = if let Some(OwnState { store, dtor, flags }) = own_state {
             assert_eq!(store_id, store, "wrong store used to convert resource");
             assert!(dtor.is_some(), "destructor must be set");
             assert!(flags.is_none(), "flags must not be set");
-            let rep = tables.resource_lift_own(None, idx)?;
-            (AtomicU32::new(NOT_IN_TABLE), rep)
+            let rep = tables.host_resource_lift_own(idx)?;
+            (AtomicResourceState::NOT_IN_TABLE, rep)
         } else {
-            let rep = tables.resource_lift_borrow(None, idx)?;
-            (AtomicU32::new(BORROW), rep)
+            let rep = tables.host_resource_lift_borrow(idx)?;
+            (AtomicResourceState::BORROW, rep)
         };
         Ok(Resource {
             state,
@@ -489,11 +792,11 @@ unsafe impl<T: 'static> Lift for Resource<T> {
 
 impl<T> fmt::Debug for Resource<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let state = match self.state.load(Relaxed) {
-            BORROW => "borrow",
-            NOT_IN_TABLE => "own (not in table)",
-            TAKEN => "taken",
-            _ => "own",
+        let state = match self.state.get() {
+            ResourceState::Borrow => "borrow",
+            ResourceState::NotInTable => "own (not in table)",
+            ResourceState::Taken => "taken",
+            ResourceState::Index(_) => "own",
         };
         f.debug_struct("Resource")
             .field("rep", &self.rep)
@@ -528,7 +831,7 @@ impl<T> fmt::Debug for Resource<T> {
 /// used.
 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
 pub struct ResourceAny {
-    idx: u32,
+    idx: HostResourceIndex,
     ty: ResourceType,
     own_state: Option<OwnState>,
 }
@@ -542,15 +845,26 @@ struct OwnState {
 
 impl ResourceAny {
     /// Attempts to convert an imported [`Resource`] into [`ResourceAny`].
-    /// `idx` is the [`ResourceImportIndex`] returned by [`Linker::resource`].
+    ///
+    /// * `resource` is the resource to convert.
+    /// * `store` is the store to place the returned resource into.
+    /// * `instance_pre` is the instance from where `idx` below was derived.
+    /// * `idx` is the [`ResourceImportIndex`] returned by [`Linker::resource`].
     ///
     /// [`Linker::resource`]: crate::component::LinkerInstance::resource
+    ///
+    /// # Errors
+    ///
+    /// This method will return an error if `idx` isn't valid for
+    /// `instance_pre` or if `resource` is not of the correct type for the
+    /// `idx` import.
     pub fn try_from_resource<T: 'static, U>(
-        Resource { rep, state, .. }: Resource<T>,
+        resource: Resource<T>,
         mut store: impl AsContextMut,
         instance_pre: &InstancePre<U>,
         idx: ResourceImportIndex,
     ) -> Result<Self> {
+        let Resource { rep, state, .. } = resource;
         let store = store.as_context_mut();
         let import = instance_pre
             .resource_import(idx)
@@ -563,11 +877,11 @@ impl ResourceAny {
         };
         ensure!(*ty == ResourceType::host::<T>(), "resource type mismatch");
 
-        let mut tables = host_resource_tables(store.0);
-        let (idx, own_state) = match state.load(Relaxed) {
-            BORROW => (tables.resource_lower_borrow(None, rep), None),
-            NOT_IN_TABLE => {
-                let idx = tables.resource_lower_own(None, rep);
+        let mut tables = HostResourceTables::new_host(store.0);
+        let (idx, own_state) = match state.get() {
+            ResourceState::Borrow => (tables.host_resource_lower_borrow(rep), None),
+            ResourceState::NotInTable => {
+                let idx = tables.host_resource_lower_own(rep);
                 (
                     idx,
                     Some(OwnState {
@@ -577,8 +891,8 @@ impl ResourceAny {
                     }),
                 )
             }
-            TAKEN => bail!("host resource already consumed"),
-            idx => (
+            ResourceState::Taken => bail!("host resource already consumed"),
+            ResourceState::Index(idx) => (
                 idx,
                 Some(OwnState {
                     dtor: Some(dtor_funcref.into()),
@@ -656,7 +970,7 @@ impl ResourceAny {
         //
         // This could fail if the index is invalid or if this is removing an
         // `Own` entry which is currently being borrowed.
-        let rep = host_resource_tables(store.0).resource_drop(None, self.idx)?;
+        let rep = HostResourceTables::new_host(store.0).host_resource_drop(self.idx)?;
 
         let (rep, state) = match (rep, &self.own_state) {
             (Some(rep), Some(state)) => (rep, state),
@@ -708,14 +1022,14 @@ impl ResourceAny {
         match ty {
             InterfaceType::Own(t) => {
                 if cx.resource_type(t) != self.ty {
-                    bail!("mismatched resource types")
+                    bail!("mismatched resource types");
                 }
                 let rep = cx.host_resource_lift_own(self.idx)?;
                 Ok(cx.guest_resource_lower_own(t, rep))
             }
             InterfaceType::Borrow(t) => {
                 if cx.resource_type(t) != self.ty {
-                    bail!("mismatched resource types")
+                    bail!("mismatched resource types");
                 }
                 let rep = cx.host_resource_lift_borrow(self.idx)?;
                 Ok(cx.guest_resource_lower_borrow(t, rep))
diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs
index 5de37b33c..0854c1315 100644
--- a/crates/wasmtime/src/runtime/store.rs
+++ b/crates/wasmtime/src/runtime/store.rs
@@ -369,6 +369,8 @@ pub struct StoreOpaque {
     component_host_table: wasmtime_runtime::component::ResourceTable,
     #[cfg(feature = "component-model")]
     component_calls: wasmtime_runtime::component::CallContexts,
+    #[cfg(feature = "component-model")]
+    host_resource_data: crate::component::HostResourceData,
 }
 
 #[cfg(feature = "async")]
@@ -520,6 +522,8 @@ impl<T> Store<T> {
                 component_host_table: Default::default(),
                 #[cfg(feature = "component-model")]
                 component_calls: Default::default(),
+                #[cfg(feature = "component-model")]
+                host_resource_data: Default::default(),
             },
             limiter: None,
             call_hook: None,
@@ -1627,13 +1631,18 @@ at https://bytecodealliance.org/security.
 
     #[inline]
     #[cfg(feature = "component-model")]
-    pub(crate) fn component_calls_and_host_table(
+    pub(crate) fn component_resource_state(
         &mut self,
     ) -> (
         &mut wasmtime_runtime::component::CallContexts,
         &mut wasmtime_runtime::component::ResourceTable,
+        &mut crate::component::HostResourceData,
     ) {
-        (&mut self.component_calls, &mut self.component_host_table)
+        (
+            &mut self.component_calls,
+            &mut self.component_host_table,
+            &mut self.host_resource_data,
+        )
     }
 
     #[cfg(feature = "component-model")]
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index 99608a879..2eea42d97 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -85,12 +85,6 @@ path = "fuzz_targets/stacks.rs"
 test = false
 doc = false
 
-[[bin]]
-name = "compile-maybe-invalid"
-path = "fuzz_targets/compile-maybe-invalid.rs"
-test = false
-doc = false
-
 [[bin]]
 name = "cranelift-fuzzgen"
 path = "fuzz_targets/cranelift-fuzzgen.rs"
diff --git a/fuzz/fuzz_targets/compile-maybe-invalid.rs b/fuzz/fuzz_targets/compile-maybe-invalid.rs
deleted file mode 100644
index 2cdb282f8..000000000
--- a/fuzz/fuzz_targets/compile-maybe-invalid.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-#![no_main]
-
-use libfuzzer_sys::fuzz_target;
-use wasmtime::{Engine, Module};
-use wasmtime_fuzzing::wasm_smith::MaybeInvalidModule;
-
-fuzz_target!(|module: MaybeInvalidModule| {
-    let engine = Engine::default();
-    let wasm = module.to_bytes();
-    wasmtime_fuzzing::oracles::log_wasm(&wasm);
-    drop(Module::new(&engine, &wasm));
-});
diff --git a/fuzz/fuzz_targets/instantiate-many.rs b/fuzz/fuzz_targets/instantiate-many.rs
index 48f1248a1..0ee9fa2eb 100644
--- a/fuzz/fuzz_targets/instantiate-many.rs
+++ b/fuzz/fuzz_targets/instantiate-many.rs
@@ -5,6 +5,7 @@
 
 use libfuzzer_sys::arbitrary::{Result, Unstructured};
 use libfuzzer_sys::fuzz_target;
+use wasmtime_fuzzing::single_module_fuzzer::KnownValid;
 use wasmtime_fuzzing::{generators, oracles};
 
 const MAX_MODULES: usize = 5;
@@ -56,7 +57,7 @@ fn execute_one(data: &[u8]) -> Result<()> {
             .collect::<Result<Vec<_>>>()?,
     );
 
-    oracles::instantiate_many(&modules, true, &config, &commands);
+    oracles::instantiate_many(&modules, KnownValid::Yes, &config, &commands);
 
     Ok(())
 }
diff --git a/fuzz/fuzz_targets/instantiate.rs b/fuzz/fuzz_targets/instantiate.rs
index 8e552bea0..cc5ae10f5 100644
--- a/fuzz/fuzz_targets/instantiate.rs
+++ b/fuzz/fuzz_targets/instantiate.rs
@@ -3,6 +3,7 @@
 use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
 use wasmtime_fuzzing::generators::Config;
 use wasmtime_fuzzing::oracles::{instantiate, Timeout};
+use wasmtime_fuzzing::single_module_fuzzer::KnownValid;
 
 wasmtime_fuzzing::single_module_fuzzer!(execute gen_module);
 
@@ -31,7 +32,7 @@ impl<'a> Arbitrary<'a> for InstantiateInput {
 
 fn execute(
     module: &[u8],
-    known_valid: bool,
+    known_valid: KnownValid,
     mut input: InstantiateInput,
     u: &mut Unstructured<'_>,
 ) -> Result<()> {
@@ -39,21 +40,40 @@ fn execute(
         // If the input module isn't a "known valid" module then it can't be
         // relied on self-regulating itself, so force a timeout via epochs/fuel
         // in the configuration.
-        Timeout::None if !known_valid => input.config.generate_timeout(u)?,
+        Timeout::None if known_valid == KnownValid::No => input.config.generate_timeout(u)?,
         other => other,
     };
     instantiate(module, known_valid, &input.config, timeout);
     Ok(())
 }
 
-fn gen_module(input: &mut InstantiateInput, u: &mut Unstructured<'_>) -> Result<Vec<u8>> {
-    let module = input.config.generate(
-        u,
-        if let Timeout::None = input.timeout {
-            Some(1000)
-        } else {
-            None
-        },
-    )?;
-    Ok(module.to_bytes())
+fn gen_module(
+    input: &mut InstantiateInput,
+    u: &mut Unstructured<'_>,
+) -> Result<(Vec<u8>, KnownValid)> {
+    // With a small-ish chance take raw fuzz input and put it in the module to
+    // stress module compilation/validation. In such a situation we can't use
+    // `ensure_termination` in wasm-smith so list the timeout as `None` to time
+    // out via epochs or Wasmtime-level fuel.
+    //
+    // Otherwise though if no timeout is configured use wasm-smith fuel to
+    // ensure termination.
+    let allow_invalid_funcs = u.ratio(1, 10)?;
+
+    let default_fuel = if allow_invalid_funcs {
+        input.config.module_config.config.allow_invalid_funcs = true;
+        input.timeout = Timeout::None;
+        None
+    } else if let Timeout::None = input.timeout {
+        Some(1000)
+    } else {
+        None
+    };
+    let module = input.config.generate(u, default_fuel)?;
+    let known_valid = if allow_invalid_funcs {
+        KnownValid::No
+    } else {
+        KnownValid::Yes
+    };
+    Ok((module.to_bytes(), known_valid))
 }
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index b4cc454f4..fd7a42fef 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1068,6 +1068,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-encoder]]
+version = "0.41.2"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-metadata]]
 version = "0.10.17"
 when = "2024-01-29"
@@ -1075,6 +1082,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-metadata]]
+version = "0.10.20"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-mutate]]
 version = "0.2.46"
 when = "2024-01-29"
@@ -1082,6 +1096,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-mutate]]
+version = "0.2.48"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-smith]]
 version = "0.15.1"
 when = "2024-01-29"
@@ -1089,6 +1110,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-smith]]
+version = "0.16.1"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmparser]]
 version = "0.118.1"
 when = "2023-11-29"
@@ -1103,6 +1131,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmparser]]
+version = "0.121.2"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmprinter]]
 version = "0.2.78"
 when = "2024-01-29"
@@ -1110,6 +1145,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmprinter]]
+version = "0.2.80"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmtime]]
 version = "17.0.0"
 when = "2024-01-25"
@@ -1261,6 +1303,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wast]]
+version = "71.0.1"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wat]]
 version = "1.0.85"
 when = "2024-01-29"
@@ -1268,6 +1317,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wat]]
+version = "1.0.88"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wiggle]]
 version = "17.0.0"
 when = "2024-01-25"
@@ -1523,6 +1579,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-bindgen-core]]
 version = "0.15.0"
 when = "2023-11-27"
@@ -1530,6 +1593,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen-core]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-bindgen-rust]]
 version = "0.15.0"
 when = "2023-11-27"
@@ -1537,6 +1607,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen-rust]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-bindgen-rust-macro]]
 version = "0.15.0"
 when = "2023-11-27"
@@ -1544,6 +1621,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen-rust-macro]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-component]]
 version = "0.18.2"
 when = "2023-11-20"
@@ -1558,6 +1642,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-component]]
+version = "0.21.0"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-parser]]
 version = "0.13.1"
 when = "2024-01-09"
@@ -1565,6 +1656,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-parser]]
+version = "0.14.0"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[audits.embark-studios.wildcard-audits.spdx]]
 who = "Jake Shadle <opensource@embark-studios.com>"
 criteria = "safe-to-deploy"


Test patch:
diff --git a/crates/test-programs/src/bin/preview1_fd_filestat_set.rs b/crates/test-programs/src/bin/preview1_fd_filestat_set.rs
index 240664dd9..d950d7df6 100644
--- a/crates/test-programs/src/bin/preview1_fd_filestat_set.rs
+++ b/crates/test-programs/src/bin/preview1_fd_filestat_set.rs
@@ -1,9 +1,10 @@
 use std::{env, process, time::Duration};
-use test_programs::preview1::{assert_fs_time_eq, open_scratch_directory, TestConfig};
+use test_programs::preview1::{
+    assert_errno, assert_fs_time_eq, open_scratch_directory, TestConfig,
+};
 
-unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
-    let cfg = TestConfig::from_env();
-    // Create a file in the scratch directory.
+unsafe fn test_fd_filestat_set_size_rw(dir_fd: wasi::Fd) {
+    // Create a file in the scratch directory, opened read/write
     let file_fd = wasi::path_open(
         dir_fd,
         0,
@@ -29,6 +30,56 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
     let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2");
     assert_eq!(stat.size, 100, "file size should be 100");
 
+    wasi::fd_close(file_fd).expect("failed to close fd");
+    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file");
+}
+
+unsafe fn test_fd_filestat_set_size_ro(dir_fd: wasi::Fd) {
+    // Create a file in the scratch directory. Creating a file implies opening it for writing, so
+    // we have to close and re-open read-only to observe read-only behavior.
+    let file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0)
+        .expect("failed to create file");
+    wasi::fd_close(file_fd).expect("failed to close fd");
+
+    // Open the created file read-only
+    let file_fd = wasi::path_open(dir_fd, 0, "file", 0, wasi::RIGHTS_FD_READ, 0, 0)
+        .expect("failed to create file");
+
+    // Check file size
+    let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat");
+    assert_eq!(stat.size, 0, "file size should be 0");
+
+    // Check fd_filestat_set_size on a file opened read-only fails with EINVAL, like ftruncate is defined to do on posix
+    assert_errno!(
+        wasi::fd_filestat_set_size(file_fd, 100)
+            .expect_err("fd_filestat_set_size should error when file is opened read-only"),
+        windows => wasi::ERRNO_ACCES,
+        wasi::ERRNO_INVAL
+    );
+
+    let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2");
+    assert_eq!(stat.size, 0, "file size should remain 0");
+
+    wasi::fd_close(file_fd).expect("failed to close fd");
+    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file");
+}
+
+unsafe fn test_fd_filestat_set_times(dir_fd: wasi::Fd, rights: wasi::Rights) {
+    let cfg = TestConfig::from_env();
+
+    // Create a file in the scratch directory. OFLAGS_CREAT implies opening for writing, so we will
+    // close it and re-open with the desired rights (FD_READ for read only, FD_READ | FD_WRITE for
+    // readwrite)
+    let file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0)
+        .expect("failed to create file");
+    wasi::fd_close(file_fd).expect("failed to close fd");
+
+    // Open the file with the rights given.
+    let file_fd =
+        wasi::path_open(dir_fd, 0, "file", 0, rights, 0, 0).expect("failed to create file");
+
+    let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2");
+
     // Check fd_filestat_set_times
     let old_atim = Duration::from_nanos(stat.atim);
     let new_mtim = Duration::from_nanos(stat.mtim) - cfg.fs_time_precision() * 2;
@@ -41,7 +92,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
     .expect("fd_filestat_set_times");
 
     let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 3");
-    assert_eq!(stat.size, 100, "file size should remain unchanged at 100");
+    assert_eq!(stat.size, 0, "file size should remain unchanged at 0");
 
     // Support accuracy up to at least 1ms
     assert_fs_time_eq!(
@@ -59,7 +110,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
     // assert_eq!(status, wasi::EINVAL, "ATIM & ATIM_NOW can't both be set");
 
     wasi::fd_close(file_fd).expect("failed to close fd");
-    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove dir");
+    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file");
 }
 fn main() {
     let mut args = env::args();
@@ -81,5 +132,18 @@ fn main() {
     };
 
     // Run the tests.
-    unsafe { test_fd_filestat_set(dir_fd) }
+
+    unsafe { test_fd_filestat_set_size_rw(dir_fd) }
+    unsafe { test_fd_filestat_set_size_ro(dir_fd) }
+
+    // The fd_filestat_set_times function should behave the same whether the file
+    // descriptor is open for read-only or read-write, because the underlying
+    // permissions of the file determine whether or not the filestat can be
+    // set or not, not than the open mode.
+    if test_programs::preview1::config().support_dangling_filesystem() {
+        // Guarding to run on non-windows filesystems. Windows rejects set-times on read-only
+        // files.
+        unsafe { test_fd_filestat_set_times(dir_fd, wasi::RIGHTS_FD_READ) }
+    }
+    unsafe { test_fd_filestat_set_times(dir_fd, wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE) }
 }
diff --git a/crates/test-programs/src/bin/preview1_path_link.rs b/crates/test-programs/src/bin/preview1_path_link.rs
index b087e0740..cc14a0a01 100644
--- a/crates/test-programs/src/bin/preview1_path_link.rs
+++ b/crates/test-programs/src/bin/preview1_path_link.rs
@@ -1,89 +1,82 @@
 use std::{env, process};
 use test_programs::preview1::{assert_errno, config, create_file, open_scratch_directory};
 
-unsafe fn create_or_open(dir_fd: wasi::Fd, name: &str, flags: wasi::Oflags) -> wasi::Fd {
-    let file_fd = wasi::path_open(dir_fd, 0, name, flags, 0, 0, 0)
-        .unwrap_or_else(|_| panic!("opening '{}'", name));
-    assert!(
-        file_fd > libc::STDERR_FILENO as wasi::Fd,
-        "file descriptor range check",
-    );
-    file_fd
-}
-
-unsafe fn open_link(dir_fd: wasi::Fd, name: &str) -> wasi::Fd {
-    let file_fd = wasi::path_open(dir_fd, 0, name, 0, 0, 0, 0)
-        .unwrap_or_else(|_| panic!("opening a link '{}'", name));
-    assert!(
-        file_fd > libc::STDERR_FILENO as wasi::Fd,
-        "file descriptor range check",
-    );
-    file_fd
-}
-
-// This is temporary until `wasi` implements `Debug` and `PartialEq` for
-// `wasi::Filestat`.
-fn filestats_assert_eq(left: wasi::Filestat, right: wasi::Filestat) {
-    assert_eq!(left.dev, right.dev, "dev should be equal");
-    assert_eq!(left.ino, right.ino, "ino should be equal");
-    assert_eq!(left.atim, right.atim, "atim should be equal");
-    assert_eq!(left.ctim, right.ctim, "ctim should be equal");
-    assert_eq!(left.mtim, right.mtim, "mtim should be equal");
-    assert_eq!(left.size, right.size, "size should be equal");
-    assert_eq!(left.nlink, right.nlink, "nlink should be equal");
-    assert_eq!(left.filetype, right.filetype, "filetype should be equal");
+// These are all macro-rules so the panic line number shows us where
+// things went wrong.
+
+macro_rules! filestats_assert_eq {
+    ($left:ident, $right:ident) => {
+        assert_eq!($left.dev, $right.dev, "dev should be equal");
+        assert_eq!($left.ino, $right.ino, "ino should be equal");
+        assert_eq!($left.atim, $right.atim, "atim should be equal");
+        assert_eq!($left.ctim, $right.ctim, "ctim should be equal");
+        assert_eq!($left.mtim, $right.mtim, "mtim should be equal");
+        assert_eq!($left.size, $right.size, "size should be equal");
+        assert_eq!($left.nlink, $right.nlink, "nlink should be equal");
+        assert_eq!($left.filetype, $right.filetype, "filetype should be equal");
+    };
 }
 
-// This is temporary until `wasi` implements `Debug` and `PartialEq` for
-// `wasi::Fdstat`.
-fn fdstats_assert_eq(left: wasi::Fdstat, right: wasi::Fdstat) {
-    assert_eq!(left.fs_flags, right.fs_flags, "fs_flags should be equal");
-    assert_eq!(
-        left.fs_filetype, right.fs_filetype,
-        "fs_filetype should be equal"
-    );
-    assert_eq!(
-        left.fs_rights_base, right.fs_rights_base,
-        "fs_rights_base should be equal"
-    );
-    assert_eq!(
-        left.fs_rights_inheriting, right.fs_rights_inheriting,
-        "fs_rights_inheriting should be equal"
-    );
+macro_rules! fdstats_assert_eq {
+    ($left:ident, $right:ident) => {
+        assert_eq!($left.fs_flags, $right.fs_flags, "fs_flags should be equal");
+        assert_eq!(
+            $left.fs_filetype, $right.fs_filetype,
+            "fs_filetype should be equal"
+        );
+        assert_eq!(
+            $left.fs_rights_base, $right.fs_rights_base,
+            "fs_rights_base should be equal"
+        );
+        assert_eq!(
+            $left.fs_rights_inheriting, $right.fs_rights_inheriting,
+            "fs_rights_inheriting should be equal"
+        );
+    };
 }
 
-unsafe fn check_rights(orig_fd: wasi::Fd, link_fd: wasi::Fd) {
-    // Compare Filestats
-    let orig_filestat = wasi::fd_filestat_get(orig_fd).expect("reading filestat of the source");
-    let link_filestat = wasi::fd_filestat_get(link_fd).expect("reading filestat of the link");
-    filestats_assert_eq(orig_filestat, link_filestat);
-
-    // Compare Fdstats
-    let orig_fdstat = wasi::fd_fdstat_get(orig_fd).expect("reading fdstat of the source");
-    let link_fdstat = wasi::fd_fdstat_get(link_fd).expect("reading fdstat of the link");
-    fdstats_assert_eq(orig_fdstat, link_fdstat);
+macro_rules! check_rights {
+    ($orig_fd:ident, $link_fd:ident) => {
+        let orig_filestat =
+            wasi::fd_filestat_get($orig_fd).expect("reading filestat of the source");
+        let link_filestat = wasi::fd_filestat_get($link_fd).expect("reading filestat of the link");
+        filestats_assert_eq!(orig_filestat, link_filestat);
+
+        // Compare Fdstats
+        let orig_fdstat = wasi::fd_fdstat_get($orig_fd).expect("reading fdstat of the source");
+        let link_fdstat = wasi::fd_fdstat_get($link_fd).expect("reading fdstat of the link");
+        fdstats_assert_eq!(orig_fdstat, link_fdstat);
+    };
 }
-
 // Extra calls of fd_close are needed for Windows, which will not remove
 // the directory until all handles are closed.
 unsafe fn test_path_link(dir_fd: wasi::Fd) {
     // Create a file
-    let file_fd = create_or_open(dir_fd, "file", wasi::OFLAGS_CREAT);
+    let create_fd =
+        wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("create file");
+    wasi::fd_close(create_fd).unwrap();
+
+    // Open a fresh descriptor to the file. We won't have a write right that was implied by OFLAGS_CREAT
+    // above.
+    let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("open file");
 
     // Create a link in the same directory and compare rights
     wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
         .expect("creating a link in the same directory");
-    let link_fd = open_link(dir_fd, "link");
-    check_rights(file_fd, link_fd);
+
+    let link_fd = wasi::path_open(dir_fd, 0, "link", 0, 0, 0, 0).expect("open link");
+
+    check_rights!(file_fd, link_fd);
     wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
     wasi::path_unlink_file(dir_fd, "link").expect("removing a link");
 
     // Create a link in a different directory and compare rights
     wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
-    let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
+    let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
+        .expect("open subdir directory");
     wasi::path_link(dir_fd, 0, "file", subdir_fd, "link").expect("creating a link in subdirectory");
-    let link_fd = open_link(subdir_fd, "link");
-    check_rights(file_fd, link_fd);
+    let link_fd = wasi::path_open(subdir_fd, 0, "link", 0, 0, 0, 0).expect("open link in subdir");
+    check_rights!(file_fd, link_fd);
     wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
     wasi::path_unlink_file(subdir_fd, "link").expect("removing a link");
     wasi::fd_close(subdir_fd).expect("Closing subdir_fd"); // needed for Windows
@@ -118,7 +111,8 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
 
     // Create a link to a directory
     wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
-    let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
+    let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
+        .expect("open new descriptor to subdir");
 
     assert_errno!(
         wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link")
diff --git a/crates/test-programs/src/bin/preview1_path_open_read_write.rs b/crates/test-programs/src/bin/preview1_path_open_read_write.rs
index 3d19daffc..ccee4f5d6 100644
--- a/crates/test-programs/src/bin/preview1_path_open_read_write.rs
+++ b/crates/test-programs/src/bin/preview1_path_open_read_write.rs
@@ -30,10 +30,14 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) {
         buf: write_buffer.as_ptr(),
         buf_len: write_buffer.len(),
     };
+    // PERM is only the failure on windows under wasmtime-wasi. wasi-common
+    // fails on windows with BADF, so we cant use the `windows =>` syntax
+    // because that doesnt support alternatives like the agnostic syntax does.
     assert_errno!(
         wasi::fd_write(f_readonly, &[ciovec])
             .err()
             .expect("read of writeonly fails"),
+        wasi::ERRNO_PERM,
         wasi::ERRNO_BADF
     );
 
@@ -53,10 +57,12 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) {
         "writeonly has write right"
     );
 
+    // See above for description of PERM
     assert_errno!(
         wasi::fd_read(f_writeonly, &[iovec])
             .err()
             .expect("read of writeonly fails"),
+        wasi::ERRNO_PERM,
         wasi::ERRNO_BADF
     );
     let bytes_written = wasi::fd_write(f_writeonly, &[ciovec]).expect("write to writeonly");
diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs
index bb5938944..08059df4c 100644
--- a/tests/all/component_model/resources.rs
+++ b/tests/all/component_model/resources.rs
@@ -137,44 +137,70 @@ fn resource_any() -> Result<()> {
         "#,
     )?;
 
-    let mut store = Store::new(&engine, ());
     let linker = Linker::new(&engine);
-    let i = linker.instantiate(&mut store, &c)?;
-    let t = i.get_resource(&mut store, "t").unwrap();
-    let u = i.get_resource(&mut store, "u").unwrap();
+    {
+        let mut store = Store::new(&engine, ());
+        let i = linker.instantiate(&mut store, &c)?;
+        let t = i.get_resource(&mut store, "t").unwrap();
+        let u = i.get_resource(&mut store, "u").unwrap();
+
+        assert_ne!(t, u);
+
+        let t_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t")?;
+        let u_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]u")?;
+        let t_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-t")?;
+        let u_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-u")?;
+
+        let (t1,) = t_ctor.call(&mut store, (100,))?;
+        t_ctor.post_return(&mut store)?;
+        let (t2,) = t_ctor.call(&mut store, (200,))?;
+        t_ctor.post_return(&mut store)?;
+        let (u1,) = u_ctor.call(&mut store, (300,))?;
+        u_ctor.post_return(&mut store)?;
+        let (u2,) = u_ctor.call(&mut store, (400,))?;
+        u_ctor.post_return(&mut store)?;
 
-    assert_ne!(t, u);
+        assert_eq!(t1.ty(), t);
+        assert_eq!(t2.ty(), t);
+        assert_eq!(u1.ty(), u);
+        assert_eq!(u2.ty(), u);
 
-    let t_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t")?;
-    let u_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]u")?;
-    let t_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-t")?;
-    let u_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-u")?;
+        u_dtor.call(&mut store, (u2,))?;
+        u_dtor.post_return(&mut store)?;
 
-    let (t1,) = t_ctor.call(&mut store, (100,))?;
-    t_ctor.post_return(&mut store)?;
-    let (t2,) = t_ctor.call(&mut store, (200,))?;
-    t_ctor.post_return(&mut store)?;
-    let (u1,) = u_ctor.call(&mut store, (300,))?;
-    u_ctor.post_return(&mut store)?;
-    let (u2,) = u_ctor.call(&mut store, (400,))?;
-    u_ctor.post_return(&mut store)?;
+        u_dtor.call(&mut store, (u1,))?;
+        u_dtor.post_return(&mut store)?;
 
-    assert_eq!(t1.ty(), t);
-    assert_eq!(t2.ty(), t);
-    assert_eq!(u1.ty(), u);
-    assert_eq!(u2.ty(), u);
+        t_dtor.call(&mut store, (t1,))?;
+        t_dtor.post_return(&mut store)?;
 
-    u_dtor.call(&mut store, (u2,))?;
-    u_dtor.post_return(&mut store)?;
+        t_dtor.call(&mut store, (t2,))?;
+        t_dtor.post_return(&mut store)?;
+    }
+
+    {
+        let mut store = Store::new(&engine, ());
+        let i = linker.instantiate(&mut store, &c)?;
+        let t_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t")?;
+        let u_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]u")?;
+        let t_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-t")?;
 
-    u_dtor.call(&mut store, (u1,))?;
-    u_dtor.post_return(&mut store)?;
+        // `t` is placed at host index 0
+        let (t,) = t_ctor.call(&mut store, (100,))?;
+        t_ctor.post_return(&mut store)?;
+        t_dtor.call(&mut store, (t,))?;
+        t_dtor.post_return(&mut store)?;
 
-    t_dtor.call(&mut store, (t1,))?;
-    t_dtor.post_return(&mut store)?;
+        // `u` is also placed at host index 0 since `t` was deallocated
+        let (_u,) = u_ctor.call(&mut store, (100,))?;
+        u_ctor.post_return(&mut store)?;
 
-    t_dtor.call(&mut store, (t2,))?;
-    t_dtor.post_return(&mut store)?;
+        // reuse of `t` should fail, despite it pointing to a valid resource
+        assert_eq!(
+            t_dtor.call(&mut store, (t,)).unwrap_err().to_string(),
+            "host-owned resource is being used with the wrong type"
+        );
+    }
 
     Ok(())
 }