Skip to content

Commit 767cf4d

Browse files
authored
Harden Win32 FFI boundary and fix safety/correctness issues (0.7.0) (#39)
* feat!: harden Win32 FFI boundary for 0.7.0 Harden the Win32/driver boundary and bump the crate to 0.7.0. Remove `Clone` from `Ndisapi` to prevent double-closing the owned driver handle, and mark generic `ndis_get_request` / `ndis_set_request` as `unsafe` because the driver reads and writes raw `T` bytes. Fix IPv6 sockaddr truncation, uninitialized storage reads, async event lost-wakeup and teardown races, partial-construction handle leaks, registry UB/leaks, UTF-16 friendly-name writes, frame-length clamping, IPv4 byte-layout conversion, masked per-entry errors, adapter-count/name handling, and async batch-send return counts. Also clean up docs, comments, formatting, and clippy warnings.
1 parent c234b77 commit 767cf4d

20 files changed

Lines changed: 460 additions & 361 deletions

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ndisapi"
3-
version = "0.6.6"
3+
version = "0.7.0"
44
edition = "2021"
55
authors = ["Vadim Smirnov <vadim@ntkernel.com>"]
66
description = "Rust crate for interacting with the Windows Packet Filter driver (NDISAPI)"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Add the following to your `Cargo.toml` file:
2828

2929
```toml
3030
[dependencies]
31-
ndisapi = "0.6.6"
31+
ndisapi = "0.7.0"
3232
```
3333

3434
## Usage

examples/async-packthru.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ impl PacketInfo {
285285
/// All other fields are set to `None` because they are not applicable to ARP packets.
286286
fn handle_arp_packet(eth_hdr: &EthernetFrame<&[u8]>) -> PacketInfo {
287287
let arp_packet = ArpPacket::new_unchecked(eth_hdr.payload());
288-
288+
289289
// Convert slices to fixed-size arrays
290290
let src_bytes: [u8; 4] = arp_packet
291291
.source_protocol_addr()
@@ -295,7 +295,7 @@ impl PacketInfo {
295295
.target_protocol_addr()
296296
.try_into()
297297
.unwrap_or([0u8; 4]);
298-
298+
299299
PacketInfo {
300300
ethertype: EthernetProtocol::Arp,
301301
src_addr: Some(IpAddress::Ipv4(Ipv4Address::from_octets(src_bytes))),

examples/listadapters.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ fn main() -> Result<()> {
8282
OID_802_3_CURRENT_ADDRESS,
8383
MacAddress::default(),
8484
);
85-
if let Err(err) = driver.ndis_get_request::<_>(&mut current_address_request) {
85+
// SAFETY: `MacAddress` wraps `[u8; 6]`, a plain-old-data type for which every byte
86+
// pattern returned by the driver is a valid value.
87+
if let Err(err) = unsafe { driver.ndis_get_request::<_>(&mut current_address_request) } {
8688
println!("Getting OID_802_3_CURRENT_ADDRESS Error: {}", err.message(),)
8789
} else {
8890
println!(

src/async_api.rs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::sync::Arc;
2020
use windows::{
2121
core::Result,
2222
Win32::{
23-
Foundation::{HANDLE, WIN32_ERROR},
23+
Foundation::{CloseHandle, HANDLE, WIN32_ERROR},
2424
System::Threading::CreateEventW,
2525
},
2626
};
@@ -78,13 +78,26 @@ impl AsyncNdisapiAdapter {
7878
CreateEventW(None, true, false, None)?
7979
};
8080

81-
// Setting the event for packet capture for the specified adapter.
81+
// Hand the event to the `Win32EventStream` first. On success the stream owns the handle
82+
// (and its wait registration) and closes it in its own `Drop`. If the stream cannot be
83+
// created the handle is not yet owned by anything, so close it here to avoid leaking it.
84+
let notif = match Win32EventStream::new(event_handle) {
85+
Ok(notif) => notif,
86+
Err(e) => {
87+
let _ = unsafe { CloseHandle(event_handle) };
88+
return Err(e);
89+
}
90+
};
91+
92+
// Register the event with the driver for packet-capture notifications. If this fails,
93+
// `notif` is dropped, which unregisters the wait and closes the event handle — so the
94+
// handle does not leak on this path either.
8295
driver.set_packet_event(adapter_handle, event_handle)?;
8396

8497
Ok(Self {
8598
adapter_handle,
8699
driver,
87-
notif: Win32EventStream::new(event_handle)?, // Creating a new Win32EventStream with the event handle.
100+
notif,
88101
})
89102
}
90103

@@ -227,7 +240,7 @@ impl AsyncNdisapiAdapter {
227240
/// # Arguments
228241
///
229242
/// * `packet` - An `IntermediateBuffer` that will be encapsulated in an `EthPacket`
230-
/// representing the Ethernet packet to be sent.
243+
/// representing the Ethernet packet to be sent.
231244
///
232245
/// # Safety
233246
///
@@ -277,7 +290,7 @@ impl AsyncNdisapiAdapter {
277290
///
278291
/// # Returns
279292
///
280-
/// On successful operation, this function returns an `Ok(usize)` that represents the number of packets successfully sent to the network adapter. If the operation fails, an error is returned.
293+
/// On successful operation, this function returns an `Ok(usize)` with the number of packets that were submitted to the driver for sending. If the operation fails, an error is returned.
281294
pub fn send_packets_to_adapter<'a, const N: usize>(
282295
&mut self,
283296
packets: impl IntoIterator<Item = &'a IntermediateBuffer>,
@@ -287,10 +300,14 @@ impl AsyncNdisapiAdapter {
287300
ndisapi::EthMRequest::<N>::from_iter(self.adapter_handle, packets.into_iter());
288301

289302
// Try to send packets to the network adapter.
290-
match self.driver.send_packets_to_adapter(&request) {
291-
Ok(_) => Ok(request.get_packet_success() as usize),
292-
Err(err) => Err(err),
293-
}
303+
//
304+
// The send IOCTL takes no output buffer, so the driver never writes back the
305+
// `packet_success` counter (unlike the read path); it would always read as 0 here.
306+
// Report the number of packets submitted in the request instead, which is the
307+
// meaningful value on the success path.
308+
self.driver
309+
.send_packets_to_adapter(&request)
310+
.map(|_| request.get_packet_number() as usize)
294311
}
295312

296313
/// Sends an Ethernet packet upwards through the network stack to the Microsoft TCP/IP protocol driver.
@@ -346,7 +363,7 @@ impl AsyncNdisapiAdapter {
346363
///
347364
/// # Returns
348365
///
349-
/// On successful operation, this function returns `Ok(usize)`, where `usize` is the number of packets sent. If the operation fails, an error is returned.
366+
/// On successful operation, this function returns `Ok(usize)`, where `usize` is the number of packets submitted to the driver for sending. If the operation fails, an error is returned.
350367
pub fn send_packets_to_mstcp<'a, const N: usize>(
351368
&mut self,
352369
packets: impl IntoIterator<Item = &'a IntermediateBuffer>,
@@ -356,10 +373,13 @@ impl AsyncNdisapiAdapter {
356373
ndisapi::EthMRequest::<N>::from_iter(self.adapter_handle, packets.into_iter());
357374

358375
// Try to send packets upwards the network stack.
359-
match self.driver.send_packets_to_mstcp(&request) {
360-
Ok(_) => Ok(request.get_packet_success() as usize),
361-
Err(err) => Err(err),
362-
}
376+
//
377+
// As with `send_packets_to_adapter`, the send IOCTL has no output buffer, so
378+
// `packet_success` is never populated by the driver. Return the number of packets
379+
// submitted in the request rather than the always-zero success counter.
380+
self.driver
381+
.send_packets_to_mstcp(&request)
382+
.map(|_| request.get_packet_number() as usize)
363383
}
364384
}
365385

@@ -377,4 +397,4 @@ impl Drop for AsyncNdisapiAdapter {
377397
.driver
378398
.set_packet_event(self.adapter_handle, HANDLE(std::ptr::null_mut()));
379399
}
380-
}
400+
}

src/async_api/win32_event_stream.rs

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use std::{
3636
use windows::{
3737
core::Result,
3838
Win32::{
39-
Foundation::{CloseHandle, HANDLE},
39+
Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE},
4040
System::Threading::{
4141
RegisterWaitForSingleObject, ResetEvent, UnregisterWaitEx, INFINITE,
4242
WT_EXECUTEINWAITTHREAD,
@@ -89,14 +89,18 @@ impl Stream for Win32EventStream {
8989
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
9090
let this = Pin::into_inner(self);
9191

92-
if this.ready.swap(false, Ordering::Relaxed) {
93-
// The Win32 event is ready, so we clear the ready flag and wake the waker, if present.
94-
// Then we reset the event to non-signaled state.
95-
// We signal readiness by returning `Poll::Ready`.
92+
// Register the waker *before* checking readiness. The callback always stores `true` into
93+
// `ready` and only then calls `waker.wake()`. By registering first and re-checking, a
94+
// signal that fires between the check and the registration cannot be lost: either we
95+
// observe `ready == true` here, or the callback's `wake()` targets the waker we just
96+
// registered. (The previous order — check then register — could sleep forever if the
97+
// event was signaled in the gap between the two.)
98+
this.waker.register(cx.waker());
99+
100+
if this.ready.swap(false, Ordering::SeqCst) {
101+
// The Win32 event was signaled; clear the flag and report readiness.
96102
Poll::Ready(Some(Ok(())))
97103
} else {
98-
// The Win32 event is not ready, so we register the waker and return `Poll::Pending`.
99-
this.waker.register(cx.waker());
100104
Poll::Pending
101105
}
102106
}
@@ -165,18 +169,33 @@ impl Win32EventNotification {
165169
impl Drop for Win32EventNotification {
166170
/// Implementing the Drop trait for the Win32EventNotification struct.
167171
fn drop(&mut self) {
168-
unsafe {
169-
// Deregistering the wait object.
170-
if UnregisterWaitEx(self.wait_object, Some(self.win32_event)).is_err() {
171-
//log::error!("error deregistering notification: {}", GetLastError);
172+
// Deregister the wait and *block until any in-flight callback has finished*. Passing
173+
// `INVALID_HANDLE_VALUE` as the completion event makes `UnregisterWaitEx` wait for
174+
// outstanding callbacks to complete before returning. Only once that succeeds is it
175+
// safe to free the callback box and close the event.
176+
//
177+
// Passing the event handle itself (as the original code did) merely asks the OS to
178+
// signal that event on completion *without* waiting; tearing down a registered wait
179+
// while it is still pending is explicitly undefined per the
180+
// `RegisterWaitForSingleObject` documentation.
181+
match unsafe { UnregisterWaitEx(self.wait_object, Some(INVALID_HANDLE_VALUE)) } {
182+
Ok(()) => {
183+
// The wait is gone and no callback can be running, so the boxed callback can be
184+
// dropped and the event handle closed safely.
185+
drop(unsafe { Box::from_raw(self.callback) });
186+
let _ = unsafe { CloseHandle(self.win32_event) };
187+
}
188+
Err(_e) => {
189+
// Deregistration failed, so we cannot prove that the wait is gone or that no
190+
// callback is (or will later be) running. Freeing the callback box or closing
191+
// the event here would risk a use-after-free of the callback and a
192+
// use-after-close of the event handle (the callback also touches the event via
193+
// `ResetEvent`). A leak is strictly preferable to undefined behavior, so we
194+
// deliberately leak both the callback allocation and the event handle on this
195+
// (expected-to-be-unreachable) path.
196+
//log::error!("error deregistering notification: {_e}");
172197
}
173-
drop(Box::from_raw(self.callback)); // Dropping the callback function.
174198
}
175-
176-
let _ = unsafe {
177-
// Closing the handle to the event.
178-
CloseHandle(self.win32_event)
179-
};
180199
}
181200
}
182201

@@ -190,4 +209,4 @@ impl Drop for Win32EventNotification {
190209
/// Windows API documentation. Our struct only contains raw pointers and handles
191210
/// that are essentially IDs which can be freely copied and are not tied to a
192211
/// specific thread. As such, it's safe to implement Send for this type.
193-
unsafe impl Send for Win32EventNotification {}
212+
unsafe impl Send for Win32EventNotification {}

src/driver.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@
1010
//! # Submodules
1111
//!
1212
//! * [`constants`] - Provides various constants and bitflag structures used to configure the
13-
//! packet filtering mechanism, specify filtering options for different protocols, and define
14-
//! the conditions for filtering at specific layers.
13+
//! packet filtering mechanism, specify filtering options for different protocols, and define
14+
//! the conditions for filtering at specific layers.
1515
//!
1616
//! * [`base`] - Provides Rust equivalents of several structures used in the NDISAPI Rust library
17-
//! for communicating with the Windows Packet Filter driver. he structures in this submodule are related
18-
//! to network adapters, Ethernet packets, adapter events, and Remote Access Service (RAS) links.
17+
//! for communicating with the Windows Packet Filter driver. The structures in this submodule are related
18+
//! to network adapters, Ethernet packets, adapter events, and Remote Access Service (RAS) links.
1919
//!
2020
//! * [`ioctl`] - Provides a collection of constants for IOCTL (Input/Output Control) codes and
21-
//! the `ctl_code` function used to generate these codes. IOCTL codes are used to communicate
22-
//! with the Windows Packet Filter driver to perform various operations.
21+
//! the `ctl_code` function used to generate these codes. IOCTL codes are used to communicate
22+
//! with the Windows Packet Filter driver to perform various operations.
2323
//!
2424
//! * [`filters`] - Provides structures for specifying filter conditions and actions for various protocols,
25-
//! including Ethernet 802.3, IPv4, IPv6, TCP, UDP, and ICMP. These structures allow users to define complex
26-
//! filtering rules based on multiple packet fields and layers.
25+
//! including Ethernet 802.3, IPv4, IPv6, TCP, UDP, and ICMP. These structures allow users to define complex
26+
//! filtering rules based on multiple packet fields and layers.
2727
//!
2828
//! * [`fastio`] - Provides Rust equivalents of several structures related to Fast I/O operations
29-
//! for the NDISAPI Rust library used in communicating with the Windows Packet Filter driver.
30-
//! The structures in this submodule are related to Fast I/O sections, which include headers and packet data,
31-
//! and are involved in read and write operations.
29+
//! for the NDISAPI Rust library used in communicating with the Windows Packet Filter driver.
30+
//! The structures in this submodule are related to Fast I/O sections, which include headers and packet data,
31+
//! and are involved in read and write operations.
3232
//!
3333
3434
// Submodules

src/driver/base.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,26 +161,36 @@ impl IntermediateBuffer {
161161

162162
/// Sets the length of the packet data stored in the `IntermediateBuffer`.
163163
///
164+
/// The value is clamped to `MAX_ETHER_FRAME` (the capacity of the underlying buffer) so that
165+
/// a subsequent call to [`get_data`](Self::get_data) or [`get_data_mut`](Self::get_data_mut)
166+
/// can never panic with an out-of-bounds slice.
167+
///
164168
/// # Arguments
165169
/// * `length`: A `u32` value representing the new length of the packet data.
166170
pub fn set_length(&mut self, length: u32) {
167-
self.length = length
171+
self.length = length.min(MAX_ETHER_FRAME as u32);
168172
}
169173

170174
/// Returns a reference to the data stored in the buffer.
171175
///
172176
/// This method returns a reference to the data stored in the buffer as a slice of bytes.
173177
/// The length of the slice is determined by the `length` field of the `buffer` struct.
178+
///
179+
/// The length is clamped to the buffer capacity, so even a corrupt or version-mismatched
180+
/// driver response that reports an oversized length cannot cause a panic here.
174181
pub fn get_data(&self) -> &[u8] {
175-
&self.buffer.0[..self.length as usize]
182+
&self.buffer.0[..(self.length as usize).min(MAX_ETHER_FRAME)]
176183
}
177184

178185
/// Returns a mutable reference to the data stored in the buffer.
179186
///
180187
/// This method returns a mutable reference to the data stored in the buffer as a slice of bytes.
181188
/// The length of the slice is determined by the `length` field of the `buffer` struct.
189+
///
190+
/// The length is clamped to the buffer capacity, so even a corrupt or version-mismatched
191+
/// driver response that reports an oversized length cannot cause a panic here.
182192
pub fn get_data_mut(&mut self) -> &mut [u8] {
183-
&mut self.buffer.0[..self.length as usize]
193+
&mut self.buffer.0[..(self.length as usize).min(MAX_ETHER_FRAME)]
184194
}
185195
}
186196

src/driver/filters.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//! * [`TcpUdpFilter`] - Represents a static filter for TCP and UDP packets.
1616
//! * [`IcmpFilter`] - Represents a static filter for ICMP packets.
1717
//! * [`StaticFilter`] - Represents a single static filter entry that combines filter conditions for various
18-
//! layers and the filter action to be taken.
18+
//! layers and the filter action to be taken.
1919
//! * [`StaticFilterTable`] - Represents a table of static filters, used for managing multiple static filter entries.
2020
2121
// Import required external crates and types

src/lib.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ pub use crate::ndisapi::{
3232
IpAddressV4, IpAddressV4Union, IpAddressV6, IpAddressV6Union, IpRangeV4, IpRangeV6, IpSubnetV4,
3333
IpSubnetV6, IpV4Filter, IpV4FilterFlags, IpV6Filter, IpV6FilterFlags, Ndisapi,
3434
NetworkAdapterInfo, NetworkLayerFilter, NetworkLayerFilterUnion, PacketOidData, PortRange,
35-
RasLinks, StaticFilter, StaticFilterTable, StaticFilterWithPosition, TcpUdpFilter, TcpUdpFilterFlags,
36-
TransportLayerFilter, TransportLayerFilterUnion, UnsortedReadRequest, UnsortedSendRequest,
37-
Version, ETHER_ADDR_LENGTH, ETH_802_3, FILTER_PACKET_DROP, FILTER_PACKET_DROP_RDR,
38-
FILTER_PACKET_PASS, FILTER_PACKET_PASS_RDR, FILTER_PACKET_REDIRECT, ICMP, IPV4, IPV6,
39-
IP_RANGE_V4_TYPE, IP_RANGE_V6_TYPE, IP_SUBNET_V4_TYPE, IP_SUBNET_V6_TYPE, TCPUDP,
35+
RasLinks, StaticFilter, StaticFilterTable, StaticFilterWithPosition, TcpUdpFilter,
36+
TcpUdpFilterFlags, TransportLayerFilter, TransportLayerFilterUnion, UnsortedReadRequest,
37+
UnsortedSendRequest, Version, ETHER_ADDR_LENGTH, ETH_802_3, FILTER_PACKET_DROP,
38+
FILTER_PACKET_DROP_RDR, FILTER_PACKET_PASS, FILTER_PACKET_PASS_RDR, FILTER_PACKET_REDIRECT,
39+
ICMP, IPV4, IPV6, IP_RANGE_V4_TYPE, IP_RANGE_V6_TYPE, IP_SUBNET_V4_TYPE, IP_SUBNET_V6_TYPE,
40+
TCPUDP,
4041
};
4142

4243
pub use crate::async_api::AsyncNdisapiAdapter;

0 commit comments

Comments
 (0)