Index | Thread | Search

From:
Christoph Liebender <christoph@liebender.dev>
Subject:
net/wstunnel: add unveil(2) restrictions
To:
ports@openbsd.org
Date:
Sat, 24 May 2025 18:18:15 +0200

Download raw body.

Thread
Hello ports@,

this patch adds unveil restrictions to the server part of net/wstunnel. 
It seems quite straightforward as the only files that are opened are 
specified in the commandline. Additionally, the server may use the libc 
resolver, therefore, /etc/{hosts, resolv.conf} are required as well. 
Reason being that either the user has configured the usage of the libc 
resolver, or the server falls back to it... At least that's what the 
code seems to be doing.

I haven't added client restrictions as I'm not using that on OpenBSD 
right now.

Fortunately, there is a crate that makes unveil(2) somewhat comforable 
to use in rust, which is an additional dependency now.

Running with bad args gets you:

# wstunnel server ws://0.0.0.0:4444 --restrict-config /asdf/jk.l

thread 'main' panicked at wstunnel-cli/src/main.rs:122:69:
unveil(/asdf/jk.l, r) failed: No such file or directory (os error 2)

Otherwise, it works fine on my machine. :)

testers, comments, ok?

- Christoph
diff --git a/net/wstunnel/Makefile b/net/wstunnel/Makefile
index 51d79bfb1ea..c812de49650 100644
--- a/net/wstunnel/Makefile
+++ b/net/wstunnel/Makefile
@@ -6,16 +6,16 @@ COMMENT =		tunnel all your traffic over Websocket or HTTP2
 GH_ACCOUNT =		erebe
 GH_PROJECT =		wstunnel
 GH_TAGNAME =		v10.3.0
+REVISION =		0
 
 CATEGORIES =		net
 
 MAINTAINER =		Christoph Liebender <christoph@liebender.dev>
 
-HOMEPAGE =		https://github.com/erebe/wstunnel
-
 # BSD-3
 PERMIT_PACKAGE =	Yes
 
+# uses unveil(2)
 WANTLIB =		${MODCARGO_WANTLIB} m
 
 MODULES =		devel/cargo
diff --git a/net/wstunnel/crates.inc b/net/wstunnel/crates.inc
index 7714b5bd591..fe788c9766d 100644
--- a/net/wstunnel/crates.inc
+++ b/net/wstunnel/crates.inc
@@ -318,6 +318,7 @@ MODCARGO_CRATES +=	unicode-segmentation	1.12.0	# MIT OR Apache-2.0
 MODCARGO_CRATES +=	unicode-xid	0.2.6	# MIT OR Apache-2.0
 MODCARGO_CRATES +=	unsafe-libyaml	0.2.11	# MIT
 MODCARGO_CRATES +=	untrusted	0.9.0	# ISC
+MODCARGO_CRATES +=	unveil	0.3.2	# MIT/Apache-2.0
 MODCARGO_CRATES +=	url	2.5.4	# MIT OR Apache-2.0
 MODCARGO_CRATES +=	urlencoding	2.1.3	# MIT
 MODCARGO_CRATES +=	utf-8	0.7.6	# MIT OR Apache-2.0
diff --git a/net/wstunnel/distinfo b/net/wstunnel/distinfo
index 7c92399f0c2..d02792215b7 100644
--- a/net/wstunnel/distinfo
+++ b/net/wstunnel/distinfo
@@ -318,6 +318,7 @@ SHA256 (cargo/unicode-segmentation-1.12.0.tar.gz) = 9szyUSEhFLVEM+yUn9anhBJ1+a2i
 SHA256 (cargo/unicode-xid-0.2.6.tar.gz) = 68HATHFRDH9wK1K3w1BzTJ/xKVxGSgMzWwC7hPxU+FM=
 SHA256 (cargo/unsafe-libyaml-0.2.11.tar.gz) = ZzqsWfrLq4qQB8f2EI0R9jtgP3yr/5n6v2UP6lwyuGE=
 SHA256 (cargo/untrusted-0.9.0.tar.gz) = jsttoouKNR13O2jVglrDkBfmgHUPmA86GoXNjdKKR8E=
+SHA256 (cargo/unveil-0.3.2.tar.gz) = Xn+oZ9VZECAB7GlBZe0X1fgulSEwYKZfnItigAhLv+w=
 SHA256 (cargo/url-2.5.4.tar.gz) = Mvi2hsrdFHP0vQEXpdKNNrGt44Tqm1BpocQK7+1/2mA=
 SHA256 (cargo/urlencoding-2.1.3.tar.gz) = 2vjbo7frhwyvHd7te8nSoEnzz9+ufLUhsIfMM65MSdo=
 SHA256 (cargo/utf-8-0.7.6.tar.gz) = CcyO5y0qm+zy8v6+AgW77Y/GYVt8tCmtBi3Ht93QNqk=
@@ -732,6 +733,7 @@ SIZE (cargo/unicode-segmentation-1.12.0.tar.gz) = 106323
 SIZE (cargo/unicode-xid-0.2.6.tar.gz) = 15744
 SIZE (cargo/unsafe-libyaml-0.2.11.tar.gz) = 62101
 SIZE (cargo/untrusted-0.9.0.tar.gz) = 14447
+SIZE (cargo/unveil-0.3.2.tar.gz) = 7280
 SIZE (cargo/url-2.5.4.tar.gz) = 81097
 SIZE (cargo/urlencoding-2.1.3.tar.gz) = 6538
 SIZE (cargo/utf-8-0.7.6.tar.gz) = 10422
diff --git a/net/wstunnel/patches/patch-Cargo_lock b/net/wstunnel/patches/patch-Cargo_lock
new file mode 100644
index 00000000000..433a8ccfbe7
--- /dev/null
+++ b/net/wstunnel/patches/patch-Cargo_lock
@@ -0,0 +1,27 @@
+Index: Cargo.lock
+--- Cargo.lock.orig
++++ Cargo.lock
+@@ -3201,6 +3201,15 @@ source = "registry+https://github.com/rust-lang/crates
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+ 
+ [[package]]
++name = "unveil"
++version = "0.3.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5e7fa867d559102001ec694165ed17d5f82e95213060a65f9c8b6280084bbfec"
++dependencies = [
++ "libc",
++]
++
++[[package]]
+ name = "url"
+ version = "2.5.4"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+@@ -3925,6 +3934,7 @@ dependencies = [
+  "tokio",
+  "tracing",
+  "tracing-subscriber",
++ "unveil",
+  "wstunnel",
+ ]
+ 
diff --git a/net/wstunnel/patches/patch-wstunnel-cli_Cargo_toml b/net/wstunnel/patches/patch-wstunnel-cli_Cargo_toml
new file mode 100644
index 00000000000..9b02c41479c
--- /dev/null
+++ b/net/wstunnel/patches/patch-wstunnel-cli_Cargo_toml
@@ -0,0 +1,11 @@
+Index: wstunnel-cli/Cargo.toml
+--- wstunnel-cli/Cargo.toml.orig
++++ wstunnel-cli/Cargo.toml
+@@ -13,6 +13,7 @@ tracing-subscriber = { version = "0.3.19", features = 
+ wstunnel = { path = ".." , features = ["clap"] }
+ 
+ tikv-jemallocator = {  version = "0.6", optional = true }
++unveil = "0.3.2"
+ 
+ [features]
+ default = []
diff --git a/net/wstunnel/patches/patch-wstunnel-cli_src_main_rs b/net/wstunnel/patches/patch-wstunnel-cli_src_main_rs
new file mode 100644
index 00000000000..8dd046d86f2
--- /dev/null
+++ b/net/wstunnel/patches/patch-wstunnel-cli_src_main_rs
@@ -0,0 +1,43 @@
+Index: wstunnel-cli/src/main.rs
+--- wstunnel-cli/src/main.rs.orig
++++ wstunnel-cli/src/main.rs
+@@ -8,6 +8,8 @@ use wstunnel::LocalProtocol;
+ use wstunnel::config::{Client, Server};
+ use wstunnel::{run_client, run_server};
+ 
++use unveil::unveil;
++
+ #[cfg(feature = "jemalloc")]
+ use tikv_jemallocator::Jemalloc;
+ 
+@@ -96,6 +98,30 @@ async fn main() -> anyhow::Result<()> {
+             run_client(*args).await?;
+         }
+         Commands::Server(args) => {
++            [
++                args.restrict_config.as_ref(),
++                args.tls_certificate.as_ref(),
++                args.tls_private_key.as_ref(),
++                args.tls_client_ca_certs.as_ref(),
++            ]
++            .iter()
++            .flatten()
++            .filter_map(|path| path.as_os_str().to_str())
++            .chain(["/etc/resolv.conf", "/etc/hosts"])
++            .map(|rpath| {
++                (
++                    rpath,
++                    unveil(rpath, "r").map_err(|err| -> anyhow::Error {
++                        match err {
++                            unveil::Error::Os(errno) => io::Error::from_raw_os_error(errno).into(),
++                            _ => err.into(),
++                        }
++                    }),
++                )
++            })
++            .for_each(|(rpath, result)| result.unwrap_or_else(|err| panic!("unveil({}, r) failed: {}", rpath, err)));
++
++            unveil::unveil("", "").expect("unveil(NULL, NULL) failed");
+             run_server(*args).await?;
+         }
+     }