Skip to content

Commit 2ac2015

Browse files
authored
[WASI] Dynamic SocketAddr check for outbound socket traffic (#7662)
* Add API to WasiCtx for adding dynamic SocketAddr checks The user can now specify a closure which gets run any time a connection to an external address is attempted. If the closure returns true, the address is allowed, but if it returns false, the underlying pool is then checked. In otherwords, false does not imply denied, but rather, check the underlyin pool. Signed-off-by: Ryan Levick <ryan.levick@fermyon.com> * Get rid of Pool in favor of just a closure Signed-off-by: Ryan Levick <ryan.levick@fermyon.com> * Add a SocketAddrUse arg to the SocketAddrCheck Signed-off-by: Ryan Levick <ryan.levick@fermyon.com> --------- Signed-off-by: Ryan Levick <ryan.levick@fermyon.com>
1 parent 17091e6 commit 2ac2015

File tree

9 files changed

+134
-137
lines changed

9 files changed

+134
-137
lines changed

crates/wasi/src/preview2/ctx.rs

Lines changed: 27 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
use super::clocks::host::{monotonic_clock, wall_clock};
21
use crate::preview2::{
3-
clocks::{self, HostMonotonicClock, HostWallClock},
2+
clocks::{
3+
host::{monotonic_clock, wall_clock},
4+
HostMonotonicClock, HostWallClock,
5+
},
46
filesystem::Dir,
7+
network::{SocketAddrCheck, SocketAddrUse},
58
pipe, random, stdio,
69
stdio::{StdinStream, StdoutStream},
710
DirPerms, FilePerms,
811
};
912
use cap_rand::{Rng, RngCore, SeedableRng};
10-
use cap_std::ipnet::{self, IpNet};
11-
use cap_std::net::Pool;
12-
use cap_std::{ambient_authority, AmbientAuthority};
13-
use std::mem;
14-
use std::net::{Ipv4Addr, Ipv6Addr};
1513
use std::sync::Arc;
14+
use std::{mem, net::SocketAddr};
1615
use wasmtime::component::ResourceTable;
1716

1817
pub struct WasiCtxBuilder {
@@ -22,8 +21,7 @@ pub struct WasiCtxBuilder {
2221
env: Vec<(String, String)>,
2322
args: Vec<String>,
2423
preopens: Vec<(Dir, String)>,
25-
26-
pool: Pool,
24+
socket_addr_check: SocketAddrCheck,
2725
random: Box<dyn RngCore + Send + Sync>,
2826
insecure_random: Box<dyn RngCore + Send + Sync>,
2927
insecure_random_seed: u128,
@@ -73,7 +71,7 @@ impl WasiCtxBuilder {
7371
env: Vec::new(),
7472
args: Vec::new(),
7573
preopens: Vec::new(),
76-
pool: Pool::new(),
74+
socket_addr_check: SocketAddrCheck::default(),
7775
random: random::thread_rng(),
7876
insecure_random,
7977
insecure_random_seed,
@@ -173,80 +171,36 @@ impl WasiCtxBuilder {
173171
self.insecure_random = Box::new(insecure_random);
174172
self
175173
}
174+
176175
pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {
177176
self.insecure_random_seed = insecure_random_seed;
178177
self
179178
}
180179

181-
pub fn wall_clock(&mut self, clock: impl clocks::HostWallClock + 'static) -> &mut Self {
180+
pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {
182181
self.wall_clock = Box::new(clock);
183182
self
184183
}
185184

186-
pub fn monotonic_clock(
187-
&mut self,
188-
clock: impl clocks::HostMonotonicClock + 'static,
189-
) -> &mut Self {
185+
pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {
190186
self.monotonic_clock = Box::new(clock);
191187
self
192188
}
193189

194-
/// Add all network addresses accessable to the host to the pool.
195-
pub fn inherit_network(&mut self, ambient_authority: AmbientAuthority) -> &mut Self {
196-
self.pool.insert_ip_net_port_any(
197-
IpNet::new(Ipv4Addr::UNSPECIFIED.into(), 0).unwrap(),
198-
ambient_authority,
199-
);
200-
self.pool.insert_ip_net_port_any(
201-
IpNet::new(Ipv6Addr::UNSPECIFIED.into(), 0).unwrap(),
202-
ambient_authority,
203-
);
204-
self
205-
}
206-
207-
/// Add network addresses to the pool.
208-
pub fn insert_addr<A: cap_std::net::ToSocketAddrs>(
209-
&mut self,
210-
addrs: A,
211-
) -> std::io::Result<&mut Self> {
212-
self.pool.insert(addrs, ambient_authority())?;
213-
Ok(self)
214-
}
215-
216-
/// Add a specific [`cap_std::net::SocketAddr`] to the pool.
217-
pub fn insert_socket_addr(&mut self, addr: cap_std::net::SocketAddr) -> &mut Self {
218-
self.pool.insert_socket_addr(addr, ambient_authority());
219-
self
220-
}
221-
222-
/// Add a range of network addresses, accepting any port, to the pool.
223-
///
224-
/// Unlike `insert_ip_net`, this function grants access to any requested port.
225-
pub fn insert_ip_net_port_any(&mut self, ip_net: ipnet::IpNet) -> &mut Self {
226-
self.pool
227-
.insert_ip_net_port_any(ip_net, ambient_authority());
228-
self
190+
/// Allow all network addresses accessible to the host
191+
pub fn inherit_network(&mut self) -> &mut Self {
192+
self.socket_addr_check(|_, _| true)
229193
}
230194

231-
/// Add a range of network addresses, accepting a range of ports, to
232-
/// per-instance networks.
195+
/// A check that will be called for each socket address that is used.
233196
///
234-
/// This grants access to the port range starting at `ports_start` and, if
235-
/// `ports_end` is provided, ending before `ports_end`.
236-
pub fn insert_ip_net_port_range(
237-
&mut self,
238-
ip_net: ipnet::IpNet,
239-
ports_start: u16,
240-
ports_end: Option<u16>,
241-
) -> &mut Self {
242-
self.pool
243-
.insert_ip_net_port_range(ip_net, ports_start, ports_end, ambient_authority());
244-
self
245-
}
246-
247-
/// Add a range of network addresses with a specific port to the pool.
248-
pub fn insert_ip_net(&mut self, ip_net: ipnet::IpNet, port: u16) -> &mut Self {
249-
self.pool.insert_ip_net(ip_net, port, ambient_authority());
197+
/// Returning `true` will permit socket connections to the `SocketAddr`,
198+
/// while returning `false` will reject the connection.
199+
pub fn socket_addr_check<F>(&mut self, check: F) -> &mut Self
200+
where
201+
F: Fn(&SocketAddr, SocketAddrUse) -> bool + Send + Sync + 'static,
202+
{
203+
self.socket_addr_check = SocketAddrCheck(Arc::new(check));
250204
self
251205
}
252206

@@ -286,7 +240,7 @@ impl WasiCtxBuilder {
286240
env,
287241
args,
288242
preopens,
289-
pool,
243+
socket_addr_check,
290244
random,
291245
insecure_random,
292246
insecure_random_seed,
@@ -304,7 +258,7 @@ impl WasiCtxBuilder {
304258
env,
305259
args,
306260
preopens,
307-
pool: Arc::new(pool),
261+
socket_addr_check,
308262
random,
309263
insecure_random,
310264
insecure_random_seed,
@@ -334,7 +288,7 @@ pub struct WasiCtx {
334288
pub(crate) stdin: Box<dyn StdinStream>,
335289
pub(crate) stdout: Box<dyn StdoutStream>,
336290
pub(crate) stderr: Box<dyn StdoutStream>,
337-
pub(crate) pool: Arc<Pool>,
291+
pub(crate) socket_addr_check: SocketAddrCheck,
338292
pub(crate) allowed_network_uses: AllowedNetworkUses,
339293
}
340294

@@ -360,8 +314,7 @@ impl AllowedNetworkUses {
360314
return Err(std::io::Error::new(
361315
std::io::ErrorKind::PermissionDenied,
362316
"UDP is not allowed",
363-
)
364-
.into());
317+
));
365318
}
366319

367320
Ok(())
@@ -372,8 +325,7 @@ impl AllowedNetworkUses {
372325
return Err(std::io::Error::new(
373326
std::io::ErrorKind::PermissionDenied,
374327
"TCP is not allowed",
375-
)
376-
.into());
328+
));
377329
}
378330

379331
Ok(())

crates/wasi/src/preview2/host/instance_network.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use wasmtime::component::Resource;
66
impl<T: WasiView> instance_network::Host for T {
77
fn instance_network(&mut self) -> Result<Resource<Network>, anyhow::Error> {
88
let network = Network {
9-
pool: self.ctx().pool.clone(),
9+
socket_addr_check: self.ctx().socket_addr_check.clone(),
1010
allow_ip_name_lookup: self.ctx().allowed_network_uses.ip_name_lookup,
1111
};
1212
let network = self.table_mut().push(network)?;

crates/wasi/src/preview2/host/network.rs

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ pub(crate) mod util {
218218
use crate::preview2::bindings::sockets::network::ErrorCode;
219219
use crate::preview2::network::SocketAddressFamily;
220220
use crate::preview2::SocketResult;
221-
use cap_net_ext::{Blocking, TcpBinder, TcpConnecter, TcpListenerExt, UdpBinder};
221+
use cap_net_ext::{Blocking, TcpListenerExt};
222222
use cap_std::net::{TcpListener, TcpStream, UdpSocket};
223223
use rustix::fd::AsFd;
224224
use rustix::io::Errno;
@@ -302,42 +302,38 @@ pub(crate) mod util {
302302
* Syscalls wrappers with (opinionated) portability fixes.
303303
*/
304304

305-
pub fn tcp_bind(listener: &TcpListener, binder: &TcpBinder) -> std::io::Result<()> {
306-
binder
307-
.bind_existing_tcp_listener(listener)
308-
.map_err(|error| match Errno::from_io_error(&error) {
309-
// See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
310-
// Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
311-
#[cfg(windows)]
312-
Some(Errno::NOBUFS) => Errno::ADDRINUSE.into(),
313-
_ => error,
314-
})
305+
pub fn tcp_bind(listener: &TcpListener, addr: &SocketAddr) -> std::io::Result<()> {
306+
rustix::net::bind(listener, addr).map_err(|error| match error {
307+
// See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
308+
// Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
309+
#[cfg(windows)]
310+
Errno::NOBUFS => Errno::ADDRINUSE.into(),
311+
_ => error.into(),
312+
})
315313
}
316314

317-
pub fn udp_bind(socket: &UdpSocket, binder: &UdpBinder) -> std::io::Result<()> {
318-
binder.bind_existing_udp_socket(socket).map_err(|error| {
319-
match Errno::from_io_error(&error) {
315+
pub fn udp_bind(socket: &UdpSocket, addr: &SocketAddr) -> std::io::Result<()> {
316+
rustix::net::bind(socket, addr).map_err(|error| {
317+
match error {
320318
// See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
321319
// Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
322320
#[cfg(windows)]
323-
Some(Errno::NOBUFS) => Errno::ADDRINUSE.into(),
324-
_ => error,
321+
Errno::NOBUFS => Errno::ADDRINUSE.into(),
322+
_ => error.into(),
325323
}
326324
})
327325
}
328326

329-
pub fn tcp_connect(listener: &TcpListener, connecter: &TcpConnecter) -> std::io::Result<()> {
330-
connecter.connect_existing_tcp_listener(listener).map_err(
331-
|error| match Errno::from_io_error(&error) {
332-
// On POSIX, non-blocking `connect` returns `EINPROGRESS`.
333-
// Windows returns `WSAEWOULDBLOCK`.
334-
//
335-
// This normalized error code is depended upon by: tcp.rs
336-
#[cfg(windows)]
337-
Some(Errno::WOULDBLOCK) => Errno::INPROGRESS.into(),
338-
_ => error,
339-
},
340-
)
327+
pub fn tcp_connect(listener: &TcpListener, addr: &SocketAddr) -> std::io::Result<()> {
328+
rustix::net::connect(listener, addr).map_err(|error| match error {
329+
// On POSIX, non-blocking `connect` returns `EINPROGRESS`.
330+
// Windows returns `WSAEWOULDBLOCK`.
331+
//
332+
// This normalized error code is depended upon by: tcp.rs
333+
#[cfg(windows)]
334+
Errno::WOULDBLOCK => Errno::INPROGRESS.into(),
335+
_ => error.into(),
336+
})
341337
}
342338

343339
pub fn tcp_listen(listener: &TcpListener, backlog: Option<i32>) -> std::io::Result<()> {

crates/wasi/src/preview2/host/tcp.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::preview2::host::network::util;
2+
use crate::preview2::network::SocketAddrUse;
23
use crate::preview2::tcp::{TcpSocket, TcpState};
34
use crate::preview2::{
45
bindings::{
@@ -9,7 +10,7 @@ use crate::preview2::{
910
network::SocketAddressFamily,
1011
};
1112
use crate::preview2::{Pollable, SocketResult, WasiView};
12-
use cap_net_ext::{Blocking, PoolExt};
13+
use cap_net_ext::Blocking;
1314
use cap_std::net::TcpListener;
1415
use io_lifetimes::AsSocketlike;
1516
use rustix::io::Errno;
@@ -44,11 +45,12 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
4445
util::validate_address_family(&local_address, &socket.family)?;
4546

4647
{
47-
let binder = network.pool.tcp_binder(local_address)?;
48+
// Ensure that we're allowed to connect to this address.
49+
network.check_socket_addr(&local_address, SocketAddrUse::TcpBind)?;
4850
let listener = &*socket.tcp_socket().as_socketlike_view::<TcpListener>();
4951

5052
// Perform the OS bind call.
51-
util::tcp_bind(listener, &binder).map_err(|error| {
53+
util::tcp_bind(listener, &local_address).map_err(|error| {
5254
match Errno::from_io_error(&error) {
5355
// From https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html:
5456
// > [EAFNOSUPPORT] The specified address is not a valid address for the address family of the specified socket
@@ -112,11 +114,12 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
112114
util::validate_remote_address(&remote_address)?;
113115
util::validate_address_family(&remote_address, &socket.family)?;
114116

115-
let connecter = network.pool.tcp_connecter(remote_address)?;
117+
// Ensure that we're allowed to connect to this address.
118+
network.check_socket_addr(&remote_address, SocketAddrUse::TcpConnect)?;
116119
let listener = &*socket.tcp_socket().as_socketlike_view::<TcpListener>();
117120

118121
// Do an OS `connect`. Our socket is non-blocking, so it'll either...
119-
util::tcp_connect(listener, &connecter)
122+
util::tcp_connect(listener, &remote_address)
120123
};
121124

122125
match r {

0 commit comments

Comments
 (0)