netlink: make synthesized replies acceptable to iproute2 - #234
Merged
Merged
Conversation
The synthesized NLMSG_ERROR copied nlmsg_pid from the request, where senders leave it 0. The kernel puts the receiving socket's port id there, and readers rely on it: iproute2's dump loop skips a message whose pid is not its own, so it dropped the EOPNOTSUPP answer to an unsupported dump and blocked in recvmsg forever (`ip route` hung). Use the same pid the dump replies and getsockname already report. The request header echoed inside the error keeps the sender's value, as it does in a kernel reply. Signed-off-by: Cong Wang <cwang@multikernel.io>
The child's NETLINK_ROUTE socket is one end of a unix socketpair, which has no peer address, so recvmsg reported msg_namelen == 0. glibc does not look, but iproute2's rtnl_talk and libnl require a sockaddr_nl and give up otherwise: `ip link show` died with "Sender address length == 0" on the RTM_NEWLINK probe it sends before listing anything. The recvmsg handler cannot supply the length, since the kernel writes it after the handler has returned Continue. Bind the responder's end to an abstract unix name of 10 bytes instead: the address the kernel then reports is exactly sizeof(sockaddr_nl) and is zero where nl_pid lies, which is how a kernel reply reads. Abstract names are host-wide, so the bytes over nl_groups, which no reader checks, carry a random value. The socket is a connected SEQPACKET that never listens, so the name gives nobody a way to reach it. Signed-off-by: Cong Wang <cwang@multikernel.io>
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.
Two bugs in the NETLINK_ROUTE virtualization. glibc's
getifaddrstolerates both, which is why the existing tests never saw them; iproute2 (and libnl) do not.1. Error replies carried the wrong pid (
ip routehung)The synthesized
NLMSG_ERRORcopiednlmsg_pidfrom the request, where senders leave it 0. The kernel puts the receiving socket's port id there. iproute2's dump loop skips a message whose pid is not its own, so it dropped theEOPNOTSUPPanswer to an unsupported dump and blocked inrecvmsgforever.The outer header now uses the same pid the dump replies and
getsocknamealready report. The request header echoed inside the error keeps the sender's value, as in a kernel reply.2. Replies had no sender address (
Sender address length == 0)The child's socket is one end of a unix socketpair, which has no peer address, so
recvmsgreportedmsg_namelen == 0. iproute2'srtnl_talkand libnl require asockaddr_nland give up otherwise:ip link showdied on theRTM_NEWLINKprobe it sends before listing anything. The recvmsg handler cannot supply the length, since the kernel writes it after the handler has returnedContinue.The responder's end is now bound to an abstract unix name of 10 bytes. The address the kernel reports is then exactly
sizeof(sockaddr_nl)and is zero wherenl_pidlies, which is how a kernel reply reads. Abstract names are host-wide, so the bytes overnl_groups, which no reader checks, carry a random value, with a retry onEADDRINUSE. The socket is a connected SEQPACKET that never listens, so the name gives nobody a way to reach it (ECONNREFUSED).Tests
synth.rsunit test: the error's outer pid is the socket's, the echoed header keeps the sender's.test_netlink_virt.rs: a raw NETLINK_ROUTE socket sends an unsupported request and checks the reply's sender address is 12 bytes withnl_pid == 0. It fails withNonewhen the bind is removed. Neither test runsip.ip li showlistslo,ip routeprints "Operation not supported" and exits. Both failed before.sandlock-core772 lib and 446 integration tests pass. Python and Go suites were not run locally.Follow-up, not in this PR
After fix 2 the kernel overwrites all 12 bytes of
msg_name, so the recvmsg/recvfrom handler that zeroes them looks redundant. Removing it would stop everyrecvmsgandrecvfromin the sandbox from trapping to the supervisor.🤖 Generated with Claude Code