Repository: bytecodealliance/wasmtime
Pull request #6444 (2023-05-24 14:17:53)

fix: respect `fd_prestat_dir_name` `path_max_len` argument
Refs https://github.com/bytecodealliance/wasmtime/pull/6440#discussion_r1202998079

github-actions[bot]: #### Subscribe to Label Action

cc @kubkon

<details>
This issue or pull request has been labeled: "wasi"

Thus the following users have been cc'd because of the following labels:

* kubkon: wasi

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
rvolosatovs: @alexcrichton @pchickey please take a look. I've addressed the last bit of feedback from @alexcrichton.
Previous merge attempt has failed due to `poll_oneof_files` failure on macos, which appears to be a flaky test

Patch:
diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt
index 3f8ed9364..0d1fa79c2 100644
--- a/crates/c-api/CMakeLists.txt
+++ b/crates/c-api/CMakeLists.txt
@@ -122,4 +122,17 @@ else()
 endif()
 
 target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
-	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include)
\ No newline at end of file
+	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include)
+
+include(GNUInstallDirs)
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/wasmtime.h
+	${CMAKE_CURRENT_SOURCE_DIR}/include/wasi.h
+	${CMAKE_CURRENT_SOURCE_DIR}/include/doc-wasm.h
+	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include/wasm.h
+	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include/wasm.hh
+	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/wasmtime
+	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(FILES ${WASMTIME_BUILD_PRODUCT} 
+	DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/crates/c-api/include/wasmtime/store.h b/crates/c-api/include/wasmtime/store.h
index 61e46e51a..c86ac5723 100644
--- a/crates/c-api/include/wasmtime/store.h
+++ b/crates/c-api/include/wasmtime/store.h
@@ -177,6 +177,19 @@ WASM_API_EXTERN wasmtime_error_t *wasmtime_context_add_fuel(wasmtime_context_t *
  */
 WASM_API_EXTERN bool wasmtime_context_fuel_consumed(const wasmtime_context_t *context, uint64_t *fuel);
 
+/**
+ * \brief Returns the amount of fuel remaining in this context's store execution
+ * before engine traps execution.
+ *
+ * If fuel consumption is not enabled via #wasmtime_config_consume_fuel_set
+ * then this function will return false. Otherwise true is returned and the
+ * fuel parameter is filled in with remaining fuel.
+ *
+ * Also note that fuel, if enabled, must be originally configured via
+ * #wasmtime_context_add_fuel.
+ */
+WASM_API_EXTERN bool wasmtime_context_fuel_remaining(const wasmtime_context_t *context, uint64_t *fuel);
+
 /**
  * \brief Attempt to manually consume fuel from the store.
  *
diff --git a/crates/c-api/src/store.rs b/crates/c-api/src/store.rs
index dbaf1ab0c..5aedaf371 100644
--- a/crates/c-api/src/store.rs
+++ b/crates/c-api/src/store.rs
@@ -227,6 +227,20 @@ pub extern "C" fn wasmtime_context_fuel_consumed(store: CStoreContext<'_>, fuel:
     }
 }
 
+#[no_mangle]
+pub extern "C" fn wasmtime_context_fuel_remaining(
+    store: CStoreContextMut<'_>,
+    fuel: &mut u64,
+) -> bool {
+    match store.fuel_remaining() {
+        Some(remaining) => {
+            *fuel = remaining;
+            true
+        }
+        None => false,
+    }
+}
+
 #[no_mangle]
 pub extern "C" fn wasmtime_context_consume_fuel(
     mut store: CStoreContextMut<'_>,
diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs
index 0b7fd584c..599d27830 100644
--- a/crates/wasi-common/src/snapshots/preview_1.rs
+++ b/crates/wasi-common/src/snapshots/preview_1.rs
@@ -509,7 +509,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
                 .ok_or_else(|| Error::not_supported())?
                 .as_bytes();
             let path_len = path_bytes.len();
-            if path_len < path_max_len as usize {
+            if path_len > path_max_len as usize {
                 return Err(Error::name_too_long());
             }
             path.as_array(path_len as u32).copy_from_slice(path_bytes)?;
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index f7dd36315..00ad0470b 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -743,10 +743,10 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno {
 
 /// Return a description of the given preopened file descriptor.
 #[no_mangle]
-pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno {
+pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno {
     State::with(|state| {
         if let Some(preopen) = state.descriptors().get_preopen(fd) {
-            if preopen.path.len < path_len as usize {
+            if preopen.path.len > path_max_len as usize {
                 Err(ERRNO_NAMETOOLONG)
             } else {
                 ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len);
diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs
index be830fb79..14b3a3d63 100644
--- a/crates/wasmtime/src/store.rs
+++ b/crates/wasmtime/src/store.rs
@@ -751,6 +751,16 @@ impl<T> Store<T> {
         self.inner.fuel_consumed()
     }
 
+    /// Returns remaining fuel in this [`Store`].
+    ///
+    /// If fuel consumption is not enabled via
+    /// [`Config::consume_fuel`](crate::Config::consume_fuel) then this
+    /// function will return `None`. Also note that fuel, if enabled, must be
+    /// originally configured via [`Store::add_fuel`].
+    pub fn fuel_remaining(&mut self) -> Option<u64> {
+        self.inner.fuel_remaining()
+    }
+
     /// Adds fuel to this [`Store`] for wasm to consume while executing.
     ///
     /// For this method to work fuel consumption must be enabled via
@@ -991,6 +1001,13 @@ impl<'a, T> StoreContext<'a, T> {
     pub fn fuel_consumed(&self) -> Option<u64> {
         self.0.fuel_consumed()
     }
+
+    /// Returns remaining fuel in this store.
+    ///
+    /// For more information see [`Store::fuel_remaining`]
+    pub fn fuel_remaining(&mut self) -> Option<u64> {
+        self.0.fuel_remaining()
+    }
 }
 
 impl<'a, T> StoreContextMut<'a, T> {
@@ -1027,6 +1044,13 @@ impl<'a, T> StoreContextMut<'a, T> {
         self.0.fuel_consumed()
     }
 
+    /// Returns remaining fuel in this store.
+    ///
+    /// For more information see [`Store::fuel_remaining`]
+    pub fn fuel_remaining(&self) -> Option<u64> {
+        self.0.fuel_remaining()
+    }
+
     /// Inject more fuel into this store to be consumed when executing wasm code.
     ///
     /// For more information see [`Store::add_fuel`]
@@ -1270,6 +1294,14 @@ impl StoreOpaque {
         Some(u64::try_from(self.fuel_adj + consumed).unwrap())
     }
 
+    fn fuel_remaining(&self) -> Option<u64> {
+        if !self.engine.config().tunables.consume_fuel {
+            return None;
+        }
+        let consumed = unsafe { *self.runtime_limits.fuel_consumed.get() };
+        Some(u64::try_from(-consumed).unwrap())
+    }
+
     fn out_of_fuel_trap(&mut self) {
         self.out_of_gas_behavior = OutOfGas::Trap;
     }


Test patch:
diff --git a/crates/test-programs/wasi-tests/src/bin/dir_fd_op_failures.rs b/crates/test-programs/wasi-tests/src/bin/dir_fd_op_failures.rs
index 35c0f2827..bb081c696 100644
--- a/crates/test-programs/wasi-tests/src/bin/dir_fd_op_failures.rs
+++ b/crates/test-programs/wasi-tests/src/bin/dir_fd_op_failures.rs
@@ -5,6 +5,22 @@ unsafe fn test_fd_dir_ops(dir_fd: wasi::Fd) {
     let stat = wasi::fd_filestat_get(dir_fd).expect("failed to fdstat");
     assert_eq!(stat.filetype, wasi::FILETYPE_DIRECTORY);
 
+    let (pr_fd, pr_name_len) = (3..)
+        .map_while(|fd| wasi::fd_prestat_get(fd).ok().map(|stat| (fd, stat)))
+        .find_map(|(fd, wasi::Prestat { tag, u })| {
+            (tag == wasi::PREOPENTYPE_DIR.raw()).then_some((fd, u.dir.pr_name_len))
+        })
+        .expect("failed to find preopen directory");
+
+    let mut pr_name = vec![];
+    let r = wasi::fd_prestat_dir_name(pr_fd, pr_name.as_mut_ptr(), 0);
+    assert_eq!(r, Err(wasi::ERRNO_NAMETOOLONG));
+
+    // Test that passing a larger than necessary buffer works correctly
+    let mut pr_name = vec![0; pr_name_len + 1];
+    let r = wasi::fd_prestat_dir_name(pr_fd, pr_name.as_mut_ptr(), pr_name_len + 1);
+    assert_eq!(r, Ok(()));
+
     let mut read_buf = vec![0; 128].into_boxed_slice();
     let iovec = wasi::Iovec {
         buf: read_buf.as_mut_ptr(),
diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs
index 093645835..c3f300d09 100644
--- a/tests/all/fuel.rs
+++ b/tests/all/fuel.rs
@@ -130,8 +130,10 @@ fn manual_fuel() {
     let mut store = Store::new(&engine, ());
     store.add_fuel(10_000).unwrap();
     assert_eq!(store.fuel_consumed(), Some(0));
+    assert_eq!(store.fuel_remaining(), Some(10_000));
     assert_eq!(store.consume_fuel(1).unwrap(), 9_999);
     assert_eq!(store.fuel_consumed(), Some(1));
+    assert_eq!(store.fuel_remaining(), Some(9_999));
     assert!(store.consume_fuel(10_000).is_err());
     assert_eq!(store.consume_fuel(999).unwrap(), 9_000);
     assert!(store.consume_fuel(10_000).is_err());
@@ -140,6 +142,7 @@ fn manual_fuel() {
     assert_eq!(store.consume_fuel(1).unwrap(), 1);
     assert_eq!(store.consume_fuel(1).unwrap(), 0);
     assert_eq!(store.consume_fuel(0).unwrap(), 0);
+    assert_eq!(store.fuel_remaining(), Some(0));
 }
 
 #[test]
@@ -213,7 +216,9 @@ fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() {
     .unwrap();
 
     let mut store = Store::new(&engine, ());
-    store.add_fuel(1_000).unwrap();
+    let init_fuel = 1_000;
+    store.add_fuel(init_fuel).unwrap();
+    assert_eq!(init_fuel, store.fuel_remaining().unwrap());
 
     let instance = Instance::new(&mut store, &module, &[]).unwrap();
     let f = instance
@@ -225,5 +230,7 @@ fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() {
 
     // The `i32.add` consumed some fuel before the unconditionally trapping
     // memory access.
-    assert!(store.fuel_consumed().unwrap() > 0);
+    let consumed_fuel = store.fuel_consumed().unwrap();
+    assert!(consumed_fuel > 0);
+    assert_eq!(init_fuel, consumed_fuel + store.fuel_remaining().unwrap());
 }