Files
forgefirm/meta-forgefirm/recipes-forgefirm/forgefirm-sandbox/files/ffx.nft
T
ScottW514 2894269115 The deny rules: the machine itself is never a destination
The way through the extension sandbox's deny rules is an allowlist, and an
allowlist names addresses. The machine's own LAN address is not a fact
anybody can pin: a new DHCP lease can turn a peer's address on some
package's list into the machine's, and with it open the Grbl port or
forgectrl's listeners to that package.

ffx.nft now refuses it structurally. Everything a host sends to one of its
own addresses, the LAN one included, leaves through lo, so chain pool
refuses `oifname "lo"` before it looks at the allow map; the two refusals
(a reset for TCP, a drop for the rest, both counted) move into chain
refuse, which pool jumps to from both places. No kernel option is new:
oifname is in the nf_tables core.

scripts/sandbox-rules-test.py gets a destination that is not the machine:
a second network namespace joined by a veth pair, with listeners of its
own. A pool uid is refused on loopback, IPv6 loopback, its own LAN address,
and the peer; an allow chain opens one port of the peer to one uid and
nothing else; with loopback, IPv6 loopback, and the machine's own address
added to that list the uid is still refused at all three while the peer
still answers; a reload closes it. It needs ip and nsenter now.

exthost.platform reads its counters from chain refuse, holds the rule's
place ahead of the map, and adds the case on the machine: an allow chain
for the last pool uid that names forgectrl on loopback and on the LAN
address opens neither, and the chain is removed.

Proven. The rules test passes with nft 1.0.9, and four controls each fail
it: the range one uid short, the TCP reject turned to accept, the
delete-table line removed, and the lo rule removed (the uid then reaches
all three of the machine's addresses). On the bench reference, image
20260920211625, this rule file loaded from /tmp with nft -f and this suite
file mounted: exthost.platform PASS, uid 831 refused at 127.0.0.1:443 and
172.16.1.97:443 with both on its allowlist, the counters [0, 0] to
[12, 4]. Against the image's own rules the same test fails on the rule's
absence, which is the control. The image's rules were reloaded after. The
unit suite passes (422).

Acceptance. exthost.platform gates the rule on the machine; sandbox-ci
gates the file. The rule file is layer content, in the platform identity
of every fingerprint.
2026-09-20 19:53:05 -04:00

56 lines
2.0 KiB
Plaintext

#!/usr/sbin/nft -f
# Copyright 2026 514 LLC d/b/a OpenGlow
# Written by Scott Wiederhold
# https://community.openglow.org
# SPDX-License-Identifier: MIT
#
# The extension sandbox's network rules (forgefirm-sandbox loads them from
# rcS, before the network starts). Every packet sent from a socket that an
# extension account owns (ffx0 to ffx31, uid 800 to 831) is refused: on
# loopback, on the machine's own LAN address, IPv4 and IPv6 alike. That is
# what keeps a package off the Grbl port, off forgectrl's listeners, and off
# the controller's report route, whatever else fails.
#
# The rules sit on the output hook and match the sending socket's uid, so
# they need no connection tracking, and no packet of the firmware's own pays
# for more than one comparison. A refused TCP connect gets a reset, so it
# fails at once instead of timing out; anything else is dropped, which the
# sender sees as EPERM.
#
# The one way through is the allow map: uid -> a chain holding that package's
# declared destinations. Whatever starts a package adds the element and the
# chain, and removes both when the package stops; a chain that accepts
# nothing returns here and the packet is refused. Loading this file again
# replaces the whole table, allowlists included: it fails closed.
#
# The machine itself is not a destination, whatever the allow map says.
# Everything a host sends to one of its own addresses, the LAN one included,
# leaves through lo, so that is refused before the map is looked at. An
# allowlist names addresses, and the machine's own address can change under
# it (a new DHCP lease); this rule does not depend on knowing it.
table inet ffx
delete table inet ffx
table inet ffx {
map allow {
typeof meta skuid : verdict
}
chain output {
type filter hook output priority filter; policy accept;
meta skuid 800-831 jump pool
}
chain pool {
oifname "lo" jump refuse
meta skuid vmap @allow
jump refuse
}
chain refuse {
meta l4proto tcp counter reject with tcp reset
counter drop
}
}