Skip to content

Commit

Permalink
feat: sockets deny functionality (bytecodealliance#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored Dec 14, 2023
1 parent 5f62577 commit c4565b7
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 24 deletions.
21 changes: 14 additions & 7 deletions packages/preview2-shim/lib/io/worker-thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ import {
} from "./calls.js";
import { createUdpSocket } from "./worker-socket-udp.js";

const symbolSocketUdpIpUnspecified = Symbol.symbolSocketUdpIpUnspecified ?? Symbol.for("symbolSocketUdpIpUnspecified");
const symbolSocketUdpIpUnspecified =
Symbol.symbolSocketUdpIpUnspecified ??
Symbol.for("symbolSocketUdpIpUnspecified");

let streamCnt = 0,
pollCnt = 0;
Expand Down Expand Up @@ -127,8 +129,7 @@ function streamError(streamId, stream, err) {
* @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream, flushPromise: Promise<void> | null }}
*/
export function getStreamOrThrow(streamId) {
if (!streamId)
throw new Error('Internal error: no stream id provided');
if (!streamId) throw new Error("Internal error: no stream id provided");
const stream = unfinishedStreams.get(streamId);
// not in unfinished streams <=> closed
if (!stream) throw { tag: "closed" };
Expand All @@ -148,7 +149,9 @@ export function getSocketOrThrow(socketId) {
}

export function getSocketByPort(port) {
return Array.from(openedSockets.values()).find((socket) => socket.address().port === port);
return Array.from(openedSockets.values()).find(
(socket) => socket.address().port === port
);
}

export function getBoundSockets(socketId) {
Expand All @@ -158,7 +161,9 @@ export function getBoundSockets(socketId) {
}

export function dequeueReceivedSocketDatagram(socketInfo, maxResults) {
const dgrams = queuedReceivedSocketDatagrams.get(`PORT:${socketInfo.port}`).splice(0, Number(maxResults));
const dgrams = queuedReceivedSocketDatagrams
.get(`PORT:${socketInfo.port}`)
.splice(0, Number(maxResults));
return dgrams;
}
export function enqueueReceivedSocketDatagram(socketInfo, { data, rinfo }) {
Expand Down Expand Up @@ -292,7 +297,8 @@ function handle(call, id, payload) {
// We need to cache the original bound IP type and fix rinfo.address when receiving datagrams (see below)
// See https://github.com/WebAssembly/wasi-sockets/issues/86
socket[symbolSocketUdpIpUnspecified] = {
isUnspecified: localAddress === "0.0.0.0" || localAddress === "0:0:0:0:0:0:0:0",
isUnspecified:
localAddress === "0.0.0.0" || localAddress === "0:0:0:0:0:0:0:0",
localAddress,
};

Expand All @@ -314,7 +320,8 @@ function handle(call, id, payload) {

if (remoteSocket[symbolSocketUdpIpUnspecified].isUnspecified) {
// cache original bound address
rinfo._address = remoteSocket[symbolSocketUdpIpUnspecified].localAddress;
rinfo._address =
remoteSocket[symbolSocketUdpIpUnspecified].localAddress;
}

const receiverSocket = {
Expand Down
36 changes: 25 additions & 11 deletions packages/preview2-shim/lib/nodejs/sockets.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { WasiSockets } from "./sockets/wasi-sockets.js";

export const {
ipNameLookup,
instanceNetwork,
network,
tcpCreateSocket,
udpCreateSocket,
tcp,
udp,
} = new WasiSockets();
import { WasiSockets, denyDnsLookup, denyTcp, denyUdp } from "./sockets/wasi-sockets.js";

export function _denyDnsLookup() {
denyDnsLookup(sockets);
}

export function _denyTcp() {
denyTcp(sockets);
}

export function _denyUdp() {
denyUdp(sockets);
}

const sockets = new WasiSockets();

export const {
ipNameLookup,
instanceNetwork,
network,
tcpCreateSocket,
udpCreateSocket,
tcp,
udp,
} = sockets;
9 changes: 9 additions & 0 deletions packages/preview2-shim/lib/nodejs/sockets/tcp-socket-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const globalBoundAddresses = new Map();
// TODO: implement would-block exceptions
// TODO: implement concurrency-conflict exceptions
export class TcpSocketImpl {
#allowed;
id = 1;
/** @type {TCP.TCPConstants.SOCKET} */ #socket = null;
/** @type {Network} */ network = null;
Expand Down Expand Up @@ -206,6 +207,8 @@ export class TcpSocketImpl {
* @throws {invalid-state} The socket is already bound. (EINVAL)
*/
startBind(network, localAddress) {
if (!this.allowed())
throw 'access-denied';
try {
assert(this[symbolSocketState].isBound, "invalid-state", "The socket is already bound");

Expand Down Expand Up @@ -292,6 +295,8 @@ export class TcpSocketImpl {
* @throws {invalid-state} The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows)
*/
startConnect(network, remoteAddress) {
if (!this.allowed())
throw 'access-denied';
const host = serializeIpAddress(remoteAddress);
const ipFamily = `ipv${isIP(host)}`;
try {
Expand Down Expand Up @@ -396,6 +401,8 @@ export class TcpSocketImpl {
* @throws {invalid-state} The socket is already in the Listener state.
*/
startListen() {
if (!this.allowed())
throw 'access-denied';
try {
assert(this[symbolSocketState].lastErrorState !== null, "invalid-state");
assert(this[symbolSocketState].isBound === false, "invalid-state");
Expand Down Expand Up @@ -447,6 +454,8 @@ export class TcpSocketImpl {
* @throws {new-socket-limit} The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)
*/
accept() {
if (!this.allowed())
throw 'access-denied';
this[symbolOperations].accept++;

try {
Expand Down
2 changes: 2 additions & 0 deletions packages/preview2-shim/lib/nodejs/sockets/udp-socket-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ export class UdpSocket {
* @throws {invalid-state} The socket is already bound. (EINVAL)
*/
startBind(network, localAddress) {
if (!this.allowed())
throw 'access-denied';
try {
assert(this[symbolSocketState].isBound, "invalid-state", "The socket is already bound");

Expand Down
36 changes: 33 additions & 3 deletions packages/preview2-shim/lib/nodejs/sockets/wasi-sockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ export const IpAddressFamily = {
};

export class WasiSockets {
#allowDnsLookup = true;
#allowTcp = true;
#allowUdp = true;
networkCnt = 1;
socketCnt = 1;

Expand Down Expand Up @@ -155,6 +158,9 @@ export class WasiSockets {
super(addressFamily, TcpSocket, net.socketCnt++);
net.tcpSockets.set(this.id, this);
}
allowed () {
return net.#allowTcp;
}
}

this.tcp = {
Expand Down Expand Up @@ -196,9 +202,12 @@ export class WasiSockets {

try {
const id = net.socketCnt++;
const updSocket = udpSocketImplCreate(addressFamily, id);
net.udpSockets.set(id, updSocket);
return updSocket;
const udpSocket = udpSocketImplCreate(addressFamily, id);
udpSocket.allowed = () => {
return net.#allowUdp;
};
net.udpSockets.set(id, udpSocket);
return udpSocket;
} catch (err) {
console.log("udp socket create error", {
err,
Expand Down Expand Up @@ -301,13 +310,34 @@ export class WasiSockets {
* @throws {invalid-argument} `name` is a syntactically invalid domain name or IP address.
*/
resolveAddresses(network, name) {
if (!net.#allowDnsLookup)
throw 'permanent-resolver-failure';
// TODO: bind to network
return resolveAddressStreamCreate(name);
},
};
}

static _denyDnsLookup (sockets) {
sockets.#allowDnsLookup = false;
}
static _denyTcp (sockets) {
sockets.#allowTcp = false;
}
static _denyUdp (sockets) {
sockets.#allowUdp = false;
}
}

export const denyDnsLookup = WasiSockets._denyDnsLookup;
delete WasiSockets._denyDnsLookup;

export const denyTcp = WasiSockets._denyTcp;
delete WasiSockets._denyTcp;

export const denyUdp = WasiSockets._denyUdp;
delete WasiSockets._denyUdp;

function convertResolveAddressError(err) {
switch (err.code) {
default:
Expand Down
2 changes: 1 addition & 1 deletion tests/generated/cli_no_ip_name_lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn cli_no_ip_name_lookup() -> anyhow::Result<()> {
let wasi_file = "./tests/rundir/cli_no_ip_name_lookup.component.wasm";
let _ = fs::remove_dir_all("./tests/rundir/cli_no_ip_name_lookup");

let cmd = cmd!(sh, "node ./src/jco.js run --jco-dir ./tests/rundir/cli_no_ip_name_lookup --jco-import ./tests/virtualenvs/base.js {wasi_file} hello this '' 'is an argument' 'with 🚩 emoji'");
let cmd = cmd!(sh, "node ./src/jco.js run --jco-dir ./tests/rundir/cli_no_ip_name_lookup --jco-import ./tests/virtualenvs/deny-dns.js {wasi_file} hello this '' 'is an argument' 'with 🚩 emoji'");

cmd.run()?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/generated/cli_no_tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn cli_no_tcp() -> anyhow::Result<()> {
let wasi_file = "./tests/rundir/cli_no_tcp.component.wasm";
let _ = fs::remove_dir_all("./tests/rundir/cli_no_tcp");

let cmd = cmd!(sh, "node ./src/jco.js run --jco-dir ./tests/rundir/cli_no_tcp --jco-import ./tests/virtualenvs/base.js {wasi_file} hello this '' 'is an argument' 'with 🚩 emoji'");
let cmd = cmd!(sh, "node ./src/jco.js run --jco-dir ./tests/rundir/cli_no_tcp --jco-import ./tests/virtualenvs/deny-tcp.js {wasi_file} hello this '' 'is an argument' 'with 🚩 emoji'");

cmd.run()?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/generated/cli_no_udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn cli_no_udp() -> anyhow::Result<()> {
let wasi_file = "./tests/rundir/cli_no_udp.component.wasm";
let _ = fs::remove_dir_all("./tests/rundir/cli_no_udp");

let cmd = cmd!(sh, "node ./src/jco.js run --jco-dir ./tests/rundir/cli_no_udp --jco-import ./tests/virtualenvs/base.js {wasi_file} hello this '' 'is an argument' 'with 🚩 emoji'");
let cmd = cmd!(sh, "node ./src/jco.js run --jco-dir ./tests/rundir/cli_no_udp --jco-import ./tests/virtualenvs/deny-udp.js {wasi_file} hello this '' 'is an argument' 'with 🚩 emoji'");

cmd.run()?;
Ok(())
Expand Down
3 changes: 3 additions & 0 deletions tests/virtualenvs/deny-dns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { _denyDnsLookup } from '@bytecodealliance/preview2-shim/sockets';

_denyDnsLookup();
3 changes: 3 additions & 0 deletions tests/virtualenvs/deny-tcp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { _denyTcp } from '@bytecodealliance/preview2-shim/sockets';

_denyTcp();
3 changes: 3 additions & 0 deletions tests/virtualenvs/deny-udp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { _denyUdp } from '@bytecodealliance/preview2-shim/sockets';

_denyUdp();
3 changes: 3 additions & 0 deletions xtask/src/generate/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ fn generate_test(test_name: &str) -> String {
"cli_file_append" => "bar-jabberwock",
"proxy_handler" => "server-api-proxy",
"proxy_echo" | "proxy_hash" => "server-api-proxy-streaming",
"cli_no_ip_name_lookup" => "deny-dns",
"cli_no_tcp" => "deny-tcp",
"cli_no_udp" => "deny-udp",
_ => {
if test_name.starts_with("preview1") {
"scratch"
Expand Down

0 comments on commit c4565b7

Please sign in to comment.