Skip to content

fix(classifier): decode masked ct-mark writes in the install's conflict scan (0.7 backport) - #3033

Merged
norrietaylor merged 1 commit into
release/0.7.xfrom
fix/0.7.3-classifier-ct-mark
Nov 5, 2026
Merged

norrietaylor merged 1 commit into
release/0.7.xfrom
fix/0.7.3-classifier-ct-mark

Conversation

@norrietaylor

Copy link
Copy Markdown
Member

Backports #3004 (fce331f5b) to release/0.7.x, as one cherry-pick -x commit.

On a host where tailscaled runs in iptables-nft mode, min finalize-install refused deny-all egress, because the ct-mark conflict scan read Tailscale's CONNMARK rule as writing every bit of the connection mark. The scan now decodes masked writes and reads xt CONNMARK masks from the iptables-save listings. It refuses a rule whose masks it can't read. The root cause and the full behaviour are in #3004.

Adaptation

Each of the three files conflicted once, all on main's QUIC443 work, which the line doesn't have. I took #3004's side and left out:

File Left out Why
scripts/install-host-classifier.sh host_ruleset="$listing" and its comment Only feeds kept_quic443_leaves
scripts/install_test.sh The nft -j list set stub branch Only serves the QUIC leaf set
crates/minimald/src/net/classifier.rs The eight BEP-018 tests and their helpers beside HostTools They predate #3004 and test QUIC code

The classifier's T105 content is already on the line, through c30a54291.

Testing

Run in the Lima VM at d1091c4cd:

  • just fmt-check, just clippy, cargo check -p minimald --all-targets --locked, just lint-shell: pass.
  • just test: 3927 of 3931 pass. The 4 failures are the known VM-only ones, the same as on #3019: a_hook_launch_does_not_advise_unenforced_placement and three finalize_install_undo_removes_*.
  • just test-one install_ -p minimald: 6 pass. This includes install_reads_xt_connmark_masks_from_iptables_save.
  • just test-one classifier -p minimald: 59 pass. just test-one host_ip -p minimald: 16 pass.

Not run locally: scripts/install_test.sh, the root integration tests, the VM lanes and macOS. CI covers those. #3004's open item also applies here: nft -a --debug=netlink list ruleset hasn't been checked on a real host with iptables-nft rules.

Merging

Use Rebase and merge, so the fix(classifier) subject lands as written.

…ct scan (#3004)

* fix(classifier): decode masked ct-mark writes in the install's conflict scan

nft prints an iptables-nft CONNMARK --save-mark --nfmask M --ctmask M
through libxtables as `ct mark set mark and 0xM`, which drops the
--ctmask. The scan read it as a write of every bit and refused to
install over Tailscale's rp_filter workaround rules. The kernel keeps
every connection-mark bit outside --ctmask, so those rules change the
bits M alone.

The scan now decodes each `ct mark set` value for the bits it can
change: the libxtables save spelling changes M, a value read from the
ct mark changes the bits an `and` clears and an `or`/`xor` names, and
any other value replaces the whole mark. An `and` that clears the
install's bits is now a conflict too.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* test(minimald): pin the conflict scan's verdict on libxtables ct-mark saves

The mask 0x00030000 lies in Tailscale's well-known range, so the install
refused it before the conflict scan ran, and the assertion held whatever
the decoder did. The table now runs `ct mark set mark and 0xM` against
the default bits, over them and beside them, and each refusal must be
the conflict scan's.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* fix(classifier): decode every ct mark set on a rule line in the conflict scan

The scan decoded only the first `ct mark set` on a line and read later
ones for 0x values alone, so a whole-mark write after a masked one
(`ct mark set mark and 0xff0000 ct mark set meta mark`) passed. Each
statement is now decoded from where the previous value ended.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* fix(classifier): read xt compat CONNMARK masks from iptables-save

A rule iptables-nft wrote is an xt compat target in the kernel
(`[ target name CONNMARK rev 2 ]`), and `nft list` prints it through
libxtables, which drops a --ctmask or names no bits at all. The scan
now reads `nft -a --debug=netlink list ruleset`, finds each rule whose
kernel form carries an xt target by family, table, chain and handle,
and decodes it from the nf_tables iptables-save and ip6tables-save
listings instead of its nft text. A CONNMARK no listing covers is
refused, because the step cannot see which bits it keeps.

ip6tables-save is now read the way iptables-save is, for both
backends.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* fix(classifier): fall back to the plain listing when nft prints no netlink form

An nft that cannot run `nft -a --debug=netlink list ruleset` blocked
every install, though the plain listing had just been read. The scan
now uses the plain listing there, and finds an untranslated CONNMARK
by its `xt target CONNMARK` text, so one no iptables-save listing
covers is still refused.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* test(install): serve the annotated ruleset listing from the stand-in nft

The conflict scan now also runs `nft -a --debug=netlink list ruleset`,
and the installer harness's stand-in nft answered it with nothing, so
the staged conflict was never read. The stand-in serves the same
fixture for it, and the scan falls back to the plain listing when the
annotated one carries no table the plain one shows.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

---------

Co-authored-by: Karl Godard <karl@minimal.dev>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fce331f)

Ported to the 0.7 line without main's QUIC443 work, which the line doesn't
have: host_ruleset for kept_quic443_leaves, the nft -j list set stub branch
and the BEP-018 tests next to the new test helper.
@norrietaylor
norrietaylor requested a review from a team as a code owner November 5, 2026 02:20
@coderabbitai

coderabbitai Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: cf5dee5f-2fbb-4e43-b862-8204d9914768

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@minimal-foundry

minimal-foundry Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Approved: no findings at d1091c4cd3d0.

Nothing to do.

Clobber: none seen: the branch holds one cherry-pick commit.

@minimal-foundry minimal-foundry Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — approved

Verdict APPROVE — No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 5, 2026
@norrietaylor
norrietaylor merged commit 81f7ce2 into release/0.7.x Nov 5, 2026
34 checks passed
@norrietaylor
norrietaylor deleted the fix/0.7.3-classifier-ct-mark branch November 5, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved minimal-review approved the current head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants