Repository navigation
dnsmasq in the net zone drops to nobody once its sockets are bound, and reads its servers' file itself when it changes - #257
Merged
Merged
Conversation
… user of their own in an empty root, and a hook that trusts nothing writes the zone's resolv.conf dhcpcd parsed every lease, DHCPv6 reply and router advertisement as the net zone's root, with that zone's capabilities, because the zone denied what its privilege separation needs. Now the net zone's policy keeps CAP_SETUID, CAP_SETGID and CAP_SYS_CHROOT (caps::PRIVSEP: kept together, by the nic zone alone), and those open setuid, setgid, setgroups and chroot in its filter (seccomp::CAP_CALLS) while every other zone's filter stays as it was. Its user namespace maps a third id, 100, to uid_base + 100 and allows setgroups, its passwd and group name dhcpcd there, and /var/empty is made on the root before it is sealed. dhcpcd's own seccomp filter then confines the parsers. The helper that stays root runs the hook with the environment the parsers send, so netzone-init.sh starts dhcpcd with Kryptik's hook instead of dhcpcd-run-hooks: it checks every value for form, keeps its state in /run/lease-dns, and writes only nameserver lines. dhcpcd -x leaves cleanup: the zone's root cannot signal the dhcpcd user, and the zone's end ends dhcpcd. The design doc said the opposite: that three capabilities for the hostile zone were a net loss with the zone as the sandbox. It now says what the helper still does for the parsers and what a parser bug no longer reaches.
…igits str.isdigit() is true for "²", which int() then refuses, so a lifetime like that from the unprivileged side crashed the hook before it rebuilt resolv.conf: nothing written, but a crash where a refusal was meant. The suite now sends one and needs the hook to exit 0 with the file unchanged.
The one conflict is zones-check.sh's net-dns line, which main now reads through the catch-all log's helpers; dhcpcd-separated follows it. With the merge, zones-test.sh lists dhcpcd-separated, and the comment over the setuid check says what stage 06 does since c431bf4: it fails the build on a bit the allowlist does not justify, where it used to strip it.
CodeQL read the failure message of an assertion on passwd_for's output as cleartext logging of sensitive information. The synthesized passwd holds no secret, but the message adds nothing the assertion does not already say.
…nd reads its servers' file itself when it changes It ran as the zone's root under --no-daemon, which skips dnsmasq's own privilege drop whatever --user says. Now it binds as root and runs as the zone's nobody with at most CAP_NET_BIND_SERVICE, kept for fd19::1, which stays tentative until the bridge has a port. Its drop calls capset, which CAP_SETUID now opens beside setuid for the nic zone. The zone's root cannot signal nobody, so dnsmasq polls the file sync_upstream replaces, with --clear-on-reload, and logs to the zone's pipe with --log-facility=-.
…t, which names --no-daemon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #245 (dhcpcd-privsep): it uses the capabilities and the nobody mapping that PR gives the net zone. Once #245 merges, this PR's diff is only its own commit.
What was wrong. dnsmasq parses every answer from the uplink's resolvers, and every query from the routed zones, as the net zone's root. That puts
CAP_NET_ADMIN,CAP_NET_RAW, the firewall, the Wi-Fi credentials and every root process in the zone within reach of a parser bug. It was started with--no-daemon, which is dnsmasq's debug mode and skips its own privilege drop whatever--usersays.What changed.
--keep-in-foregroundand without--no-daemon. It binds as the zone's root, then drops to the zone's nobody (--user=nobody --group=nogroup; host uiduid_base+ 65534). It keeps at mostCAP_NET_BIND_SERVICE, so it can bind fd19::1, which stays tentative until the bridge has a port. Every zone keeps that capability.capset(keepCAP_SETUIDacrosssetuid, then let it go).seccomp::CAP_CALLSnow openscapsetwithsetuidunderCAP_SETUID, which only the nic zone may keep (caps::PRIVSEP, from dhcpcd in the net zone separates its privileges: its parsers run as a user of their own in an empty root, and a hook that trusts nothing writes the zone's resolv.conf #245).capsetcan only narrow or move capabilities the process already holds, so it reaches nothing past the zone's own set. Every other zone still gets EPERM.sync_upstreamreplaces by itself. Without--no-pollit checks the file before the next query, at most once a second.--clear-on-reloaddrops what the last servers answered, as SIGHUP did./procrather than withkill -0, which would fail with EPERM and restart dnsmasq every pass.--log-facility=-keeps dnsmasq's own lines in the zone's log.--pid-filewith no path writes no pid file; nothing read it, and the zone's root could not have chowned it.net-zone.md(the resolver, the calls) andzone-policy-files.md.Tests.
kept_capabilities_open_their_callscoverscapset: it is opened byCAP_SETUIDalone and still EPERM in the base program.tools/tests/netzone-dns.sh:--no-pollor--no-daemon.zones-check.sh:dnsmasq-unprivileged: every dnsmasq is at host uiduid_base+ 65534 for real, effective, saved and fs ids, has no capability butCAP_NET_BIND_SERVICE, and none runs as the zone's root.dns-follows-leaseis now stricter. It used to count the zone's own "told dnsmasq" lines. Now it queries dnsmasq until dnsmasq logs that it read the file again, then requires dnsmasq's ownusing nameserver 192.0.2.53#53after the server is added, and its absence after the server is removed.How the run proves it. Distro run 37879216831 on e60836b is green in every part, with zones 84/0:
dnsmasq-unprivileged: one dnsmasq at host uid 262142 (the net zone's nobody) in all four ids, with no capability butCAP_NET_BIND_SERVICE, and none as the zone's root.dns-follows-lease: dnsmasq logged reading its file, used 192.0.2.53 once the lease named it, and stopped using it after the lease dropped it.dhcpcd-separated,net-dns,routed-dnsanddns-after-reloadstill pass.CI 37875047166 is green on the same head. The first run, 37874542851 on 79e76c2, had the same zones result. It failed only the
netzone-dns.shcheck that e60836b fixes: that check had matched--no-daemonin a comment.