# BST-TIMEOUT: 300 # MEASURED: see the ==BST-SUITE== line on any run. The # # scoring forks almost nothing -- every case below is a # # shell string compare against values awk lifted from ONE # # probe run -- so the cost is the single exec of the probe. # # 300 is the bound the ticket gates and the nightly pass. . "$BST_LIB" # libnetfilter_queue 1.0.5 -- the userspace library for packets the kernel # queues to user space: bind a queue number over the NFNL_SUBSYS_QUEUE message # family, set a copy mode, read the queued packet, return a verdict on it, and # optionally mangle the packet before reinjecting it. What ships under # --disable-shared is /usr/lib/libnetfilter_queue.a, seven headers under # /usr/include/libnetfilter_queue/ and one .pc file. There is no CLI and no # daemon; the tarball's utils/ and examples/ are check_PROGRAMS that `make` # does not build and `make install` discards, and each opens a netlink socket # immediately, so none can self-drive. On an image with no compiler that leaves # nothing that ships able to drive a line of this library -- hence the probe, # built in the recipe against the archive that ships. # # --------------------------------------------------------------------------- # THIS LIBRARY IS THREE LAYERS, NOT TWO, AND THAT IS WHAT DISTINGUISHES IT FROM # ITS SIBLINGS. # # libnetfilter_conntrack (79-libnetfilter-conntrack, BRINTOS-1243) and # libnetfilter_log (79-libnetfilter-log, BRINTOS-1245) are framing + transport. # This one carries a third layer that neither has, and it is the largest: # # * A PACKET-BUFFER layer -- pktb_* and the nfq_ip/ip6/tcp/udp mangle, # checksum and snprintf helpers over it. It touches NO descriptor and no # kernel at all: it is byte manipulation on a heap buffer. Every case runs # here and a wrong answer is a real defect, not a platform gap. Groups B, # M, K, N -- 61 of the 129 probe cases. This is the half of the port that # is fully proven on this image. # * A FRAMING layer over libmnl -- nfq_nlmsg_put, the cfg and verdict # putters, nfq_nlmsg_parse. Composes the wire messages; no descriptor # either, so it runs here too. Groups Z, H, C, V, P. # * A TRANSPORT layer -- mnl_socket_open(NETLINK_NETFILTER) for the modern # API and nfq_open() for the legacy one, both beginning at # socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER). Group S, expected red # here for named reasons. # # --------------------------------------------------------------------------- # THE QUEUE-HANDLE API CANNOT BE LINKED ON THIS ARM. GROUP L MEASURES IT. # # SUPERSEDED: libnfnetlink now carries 0001, which moves nfnl_step's VLA (and # the two others in src/libnfnetlink.c) to the heap. The refusal below no # longer happens. libnetfilter-queue's canaries 4 and 5 link, and its # L01/L02 expect that. This section is kept as the record of why the probe # drives the transport over libmnl. # # nfq_create_queue() and nfq_handle_packet() reach libnfnetlink's nfnl_step(), # whose VLA `struct nfattr *tb[ssh->cb[type].attr_count]` # (src/libnfnetlink.c:1347) has its address escape to the indirect subsystem # callback across a suspend. The coroutinize pass REFUSES that frame and the # link FATALs -- brintos/hwjs-cc#15 # (https://brintos.io/brintos/hwjs-cc/issues/15). It is a LINK-time refusal, # not a runtime failure, so a program that called them would not exist to run. # # MEASURED by the recipe, five canary links with the probe's own command line, # each level adding exactly one call to the level below: # # 1 framing over libmnl LINK-OK # 2 1 + the packet-buffer layer LINK-OK # 3 2 + nfq_open() / nfq_close() LINK-OK # 4 3 + nfq_create_queue/set_mode/destroy_queue LINK-FAILED # REFUSE fn=nfnl_step, then fn=nfnl_catch; nfnl_step mentions = 3 # 5 3 + nfq_handle_packet() LINK-FAILED # REFUSE fn=__nfnl_handle_msg; nfnl_step mentions = 0 # # LEVEL 3 IS THE ONE WORTH READING, AND IT CONTRADICTS THE MECHANISM THE # SIBLING PORT PROPOSED. nfq_open() LINKS, with nfnl_step mentions = 0. # # The libnetfilter_log port (BRINTOS-1245) found nflog_open() itself refused, # and explained it this way on hwjs-cc#15: nflog_open_nfnl() calls # nfnl_callback_register() at open time, which keeps libnfnetlink's # callback-dispatch graph live in the merged module, and nfnl_step() comes with # it and refuses. # # THAT EXPLANATION DOES NOT PREDICT THIS LIBRARY. nfq_open_nfnl() performs the # SAME registration -- `nfnl_callback_register(h->nfnlssh, NFQNL_MSG_PACKET, # &pkt_cb)` at src/libnetfilter_queue.c:420, which is the same idiom as # src/libnetfilter_log.c:273 -- and nfq_open() links anyway, with zero # nfnl_step in the plugin's output by the same `grep -c` the sibling used. So # registering a callback at open time is NOT sufficient on its own, and the # real discriminator is still unidentified. Recorded on hwjs-cc#15 rather than # left as a passing remark, because a wrong stated cause is the copy the next # engineer acts on. # # What IS established here: the refusal is reached from nfq_create_queue() by # an ordinary call path (nfnl_query -> nfnl_process -> nfnl_step), and from # nfq_handle_packet() by a SEPARATE one that never mentions nfnl_step and # refuses __nfnl_handle_msg instead. That is why levels 4 and 5 are independent # rather than one nested ladder: a single ladder would have reported only the # first and hidden the second entirely. # # The practical rule, which is the part a future port needs: MEASURE the line # per library. It falls in a different place in each of the three siblings, and # reading it off any one of them -- in either direction -- would have been # wrong. # # The recipe turns those five results into probe_link.h and the probe reports # them as L01-L03, so THE VALUES BELOW ARE A CLAIM ABOUT THE PLATFORM THAT # SOMETHING CHECKS. (As written, the handle API was expected to start linking # the day hwjs-cc#15 landed, and L01/L02 would then go RED. It links now # because of libnfnetlink's 0001, so L01/L02 expect "linkable".) A hard-coded # verdict -- which an earlier draft of this port had -- would have gone stale # in silence instead, and passed forever either way. # # CONSEQUENCE FOR THIS SUITE. The ticket's pass criterion -- bind a queue # number, set the copy mode, unbind cleanly, report each step's return value, # with the expected errno on a double bind -- is driven over libmnl, which is # the transport upstream's own examples/nf-queue.c uses. The messages are # composed by THIS LIBRARY (nfq_nlmsg_put + nfq_nlmsg_cfg_put_cmd + # nfq_nlmsg_cfg_put_params), so the criterion is exercised through the library # under test; only the write syscall is somebody else's. Every S case ACKs, so # the value it reports is the KERNEL'S, lifted out of NLMSG_ERROR by # mnl_cb_run -- not a local success that only says the write left the process. # That is what makes S08 mean anything. # # The classic entry points are still asserted PRESENT in the archive by the # symbol census below. What is in the archive and what can be linked against it # are different questions, and the census answers the first. # # --------------------------------------------------------------------------- # THE ORACLE. # # Every composed buffer and every parsed, mangled and checksummed value is # scored against output pinned from a native x86-64 build of this same 1.0.5 # source against libmnl 1.0.5 and libnfnetlink 1.0.2. Netlink's TLV encoding is # a wire format -- 4-byte alignment, a 4-byte struct nlattr of {u16 len, u16 # type}, struct nlmsghdr 16 bytes and struct nfgenmsg 4 on a 32- and a 64-bit # build alike -- and this library puts its scalars on the wire in NETWORK byte # order, so the two architectures MUST agree exactly and a difference is a real # defect, never an expected arch difference. # # Set-then-get through the library's own parser would prove only that its # writer and its reader agree: a port that placed nla_len at the wrong offset # would round-trip through its own mistake and report every field correct. The # same argument is why the mangle and checksum groups are pinned as whole-packet # hex rather than field reads -- an IP checksum is arithmetic over all twenty # header bytes, so a single byte at a wrong offset changes it, which a # field-by-field comparison would not notice. # # THE ORACLE REACHES THE TRANSPORT TOO, so the S values are MEASURED against a # kernel that HAS nfnetlink_queue rather than asserted as what a configured # kernel ought to do. Run under `unshare -rn` on x86-64, the same probe source # binds queue 7, sets NFQNL_COPY_PACKET/0xffff, binds it a second time and # unbinds -- and S08 answers EBUSY. # # S08's EBUSY IS MEASURED AND THE OBVIOUS GUESS IS WRONG. nfqnl_recv_config # refuses on the mere existence of an instance for that queue number, so # rebinding a queue THIS PROCESS ALREADY HOLDS is EBUSY too -- not the 0 that a # same-portid rebind looks like it should return. An earlier draft of the probe # asserted the latter in a comment; it could not have been caught, because that # draft read errno in the same argument list as the call that sets it and C # leaves the evaluation order unspecified. gcc x86-64 evaluated the errno read # FIRST, so every S case printed "ok" whatever the kernel answered. Fixed in # the probe (emit_send), and it is the reason the PERMISSION branch of _attrib # below is reachable at all: before the fix the no-namespace arm reported # "-1 ok" rather than EPERM. # # --------------------------------------------------------------------------- # TWO DIFFERENT KERNEL GAPS HOLD GROUP S, AND SEPARATING THEM IS THE POINT. # # 1. Netlink protocol 12 is not registered. wasm32_defconfig sets CONFIG_NET=y # and no CONFIG_NETFILTER of any kind, so net/netfilter/nfnetlink.c is not # built -- brintos/linux#86 (https://brintos.io/brintos/linux/issues/86). # That is what stops S01 and S05 at socket(). # 2. Even when #86 lands, NFNL_SUBSYS_QUEUE (3) is still not registered. # nfnetlink_queue.o is built by CONFIG_NETFILTER_NETLINK_QUEUE, which #86's # proposed diff does not add -- it adds CONFIG_NETFILTER and # CONFIG_NETFILTER_NETLINK only, and says so -- and which additionally # depends on NETFILTER_ADVANCED. So S06-S09, the ticket's own pass # criterion, sit one symbol BEYOND #86 and stay red on the day it lands. # That is a second gap surfacing, not this port regressing. # # The attribution below is COMPUTED from the probe's own output, so this file # needs no edit on either day. R=${BST_LIBNETFILTER_QUEUE_ROOT:-} PROBE=${BST_LIBNETFILTER_QUEUE_PROBE:-$R/usr/libexec/libnetfilter-queue-tests/libnetfilter-queue-probe} ARCHIVE=$R/usr/lib/libnetfilter_queue.a PCFILE=$R/usr/lib/pkgconfig/libnetfilter_queue.pc INC=$R/usr/include/libnetfilter_queue # Asserted against the .pc rather than assumed: a recipe bumped without # regenerating the pinned bytes below must go red on ONE named case instead of # silently changing the subject of the other hundred and thirty. WANT_VERSION=1.0.5 if [ ! -f "$ARCHIVE" ]; then bst_skip libnetfilter-queue/present "libnetfilter-queue not installed on this image" exit 0 fi bst_ok libnetfilter-queue/present check libnetfilter-queue/staticlib test -s "$ARCHIVE" check libnetfilter-queue/pkgconfig test -s "$PCFILE" check libnetfilter-queue/hdr-main test -s "$INC/libnetfilter_queue.h" check libnetfilter-queue/hdr-pktbuff test -s "$INC/pktbuff.h" check libnetfilter-queue/hdr-ipv4 test -s "$INC/libnetfilter_queue_ipv4.h" check libnetfilter-queue/hdr-ipv6 test -s "$INC/libnetfilter_queue_ipv6.h" check libnetfilter-queue/hdr-tcp test -s "$INC/libnetfilter_queue_tcp.h" check libnetfilter-queue/hdr-udp test -s "$INC/libnetfilter_queue_udp.h" # The uapi copy the package ships itself. It is a pkginclude_HEADER and it # matters here more than on a normal distro: this image installs NO kernel # headers at all, so it is the only definition of the nfnetlink_queue attribute # numbers anything on target could compile against. check libnetfilter-queue/hdr-uapi test -s "$INC/linux_nfnetlink_queue.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libnetfilter-queue/no-shared "$R/usr/lib/libnetfilter_queue.so" "$R/usr/lib/libnetfilter_queue.so.1" pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libnetfilter-queue/pkgconfig-version "$WANT_VERSION" "$pcver" check_out libnetfilter-queue/pkgconfig-libs "-lnetfilter_queue" cat "$PCFILE" # LIBMNL IS NAMED NOWHERE IN THIS .pc, AND THAT IS A WORSE FACT THAN THE # SIBLING PORTS' -- CHECKED HERE RATHER THAN INHERITED FROM THEM. # # libnetfilter_log's .pc puts BOTH dependencies under Requires.private, so # `pkg-config --static --libs` expands them and a consumer that knows to pass # --static links. This .pc names libnfnetlink under the PUBLIC Requires and # does not mention libmnl in Requires, Requires.private or Libs.private at all # -- so there is no pkg-config invocation that produces a working link line for # the mnl half of this library, which is src/nlmsg.c: every nfq_nlmsg_* entry # point, i.e. the entire modern API and the only one that links on this arm. # # MEASURED on the x86-64 oracle build of this same source, linking the probe: # # pkg-config --libs -> -lnetfilter_queue -lnfnetlink 39 undefined # pkg-config --static --libs -> -lnetfilter_queue -lnfnetlink 39 undefined # explicit -lnetfilter_queue -lnfnetlink -lmnl 0 undefined # # --static changes NOTHING here, which is exactly the difference from the # sibling. Pinned as two cases: one that libnfnetlink is public, one that # libmnl is absent -- so a release that fixes this is a visible, deliberate # change rather than a silent one, and the bbappend's explicit -lmnl can be # dropped on the same day. _pcfield() { awk -v k="$2" 'index($0, k) == 1 { sub(/^[^:]*:[ \t]*/, ""); v = $0 while (v ~ /\\$/) { sub(/[ \t]*\\$/, "", v); if ((getline nx) <= 0) break sub(/^[ \t]+/, "", nx); v = v " " nx } print v; exit }' "$1" 2>/dev/null } check_eq libnetfilter-queue/pkgconfig-requires "libnfnetlink" "$(_pcfield "$PCFILE" 'Requires:')" if grep -q 'libmnl' "$PCFILE" 2>/dev/null; then bst_notok libnetfilter-queue/pkgconfig-omits-libmnl \ "libmnl is now named in $PCFILE -- upstream fixed the omission, so the explicit -lmnl in libnetfilter-queue_%.bbappend can be dropped and this case rewritten" else bst_ok libnetfilter-queue/pkgconfig-omits-libmnl fi # --------------------------------------------------------------------------- # The symbol census. This library exports 88 entry points, which is few enough # to be EXHAUSTIVE rather than a deliberate sample -- so a red here names the # exact entry point that did not ship instead of saying that one arbitrary # member of a subsystem went missing. # # nfq_create_queue and nfq_handle_packet ARE asserted present. The arm cannot # LINK a program that calls them (see the header comment and group L); what is # in the archive is a different question from what can be linked against it, # and this census answers the archive one. # grep -a: the archive is binary and some greps refuse it without it. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in nfq_open nfq_open_nfnl nfq_close nfq_fd nfq_nfnlh \ nfq_bind_pf nfq_unbind_pf \ nfq_create_queue nfq_destroy_queue nfq_handle_packet \ nfq_set_mode nfq_set_queue_flags nfq_set_queue_maxlen \ nfq_set_verdict nfq_set_verdict2 nfq_set_verdict_mark \ nfq_set_verdict_batch nfq_set_verdict_batch2 \ nfq_get_msg_packet_hdr nfq_get_nfmark nfq_get_timestamp \ nfq_get_indev nfq_get_physindev \ nfq_get_outdev nfq_get_physoutdev \ nfq_get_indev_name nfq_get_physindev_name \ nfq_get_outdev_name nfq_get_physoutdev_name \ nfq_get_packet_hw nfq_get_payload \ nfq_get_uid nfq_get_gid nfq_get_secctx nfq_get_skbinfo \ nfq_snprintf_xml \ nfq_nlmsg_put nfq_nlmsg_parse \ nfq_nlmsg_cfg_put_cmd nfq_nlmsg_cfg_put_params \ nfq_nlmsg_cfg_put_qmaxlen \ nfq_nlmsg_verdict_put nfq_nlmsg_verdict_put_mark \ nfq_nlmsg_verdict_put_pkt \ nfq_checksum nfq_checksum_tcpudp_ipv4 nfq_checksum_tcpudp_ipv6 \ nfq_ip_get_hdr nfq_ip_set_transport_header nfq_ip_set_checksum \ nfq_ip_mangle nfq_ip_snprintf \ nfq_ip6_get_hdr nfq_ip6_set_transport_header \ nfq_ip6_mangle nfq_ip6_snprintf \ nfq_tcp_get_hdr nfq_tcp_get_payload nfq_tcp_get_payload_len \ nfq_tcp_mangle_ipv4 nfq_tcp_mangle_ipv6 nfq_tcp_snprintf \ nfq_tcp_compute_checksum_ipv4 nfq_tcp_compute_checksum_ipv6 \ nfq_udp_get_hdr nfq_udp_get_payload nfq_udp_get_payload_len \ nfq_udp_mangle_ipv4 nfq_udp_mangle_ipv6 nfq_udp_snprintf \ nfq_udp_compute_checksum_ipv4 nfq_udp_compute_checksum_ipv6 \ pktb_alloc pktb_free pktb_data pktb_len \ pktb_mac_header pktb_network_header pktb_transport_header \ pktb_mangle pktb_mangled \ pktb_push pktb_pull pktb_put pktb_trim pktb_tailroom; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libnetfilter-queue/sym-$sym" else bst_notok "libnetfilter-queue/sym-$sym" "$sym absent from $ARCHIVE" fi done # --------------------------------------------------------------------------- # The probe. Both it and the archive are this ticket's deliverable, so a skip # on either is a broken port and not a legitimate absence -- but an exec that # the shell could not stage at all is platform noise, and bst_unrun is what # tells those two apart. if [ ! -x "$PROBE" ]; then bst_skip libnetfilter-queue/probe-runs "$PROBE absent from the image" exit 0 fi tmp=${TMPDIR:-/tmp} pout=$tmp/bst-libnetfilter-queue-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libnetfilter-queue/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libnetfilter-queue/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libnetfilter-queue/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi # A probe killed part-way still emits usable lines, so the run is scored either # way -- but PROBE-END is what separates "these values are the whole story" # from "the rest is missing", and want() reports an absent value as its own red # rather than as a mismatch. if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libnetfilter-queue/probe-complete else bst_notok libnetfilter-queue/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # One awk pass lifts every case line into a V_ shell variable, so the # hundred and thirty comparisons below fork nothing. First occurrence wins. eval "$(awk ' function q(v) { gsub(/'"'"'/, "'"'"'\\'"'"''"'"'", v); return "'"'"'" v "'"'"'" } $1 ~ /^[ZHCVPBMKNLS][0-9][0-9]$/ && seen[$1]++ == 0 { id = $1; v = $0; sub(/^[^ ]+ ?/, "", v); print "V_" id "=" q(v) } ' "$pout")" # want : the strict comparison. An absent # value is its own message -- "the probe stopped before here" and "the probe # answered differently" are different findings and a bare mismatch would hide # the first behind an empty got. want() { eval "_got=\${V_$1-}" if [ -z "$_got" ]; then bst_notok "libnetfilter-queue/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libnetfilter-queue/$2" else bst_notok "libnetfilter-queue/$2" "want:'$3' got:'$_got'" fi } # --------------------------------------------------------------------------- # Z -- THE WIRE FORMAT ITSELF. Scored first because a disagreement here # explains every hex mismatch after it. All of these are identical on a 32- and # a 64-bit build, so a difference is a real defect and never an arch artefact. # Z05 is the one most worth reading: nfqnl_msg_config_params is 5 bytes, NOT 8 # -- it is __attribute__((packed)) with a __be32 followed by a __u8, and a # build that lost the packing would pad it to 8 and break every C03 byte. want Z01 sizeof-nlmsghdr '16' want Z02 sizeof-nfgenmsg '4' want Z03 sizeof-nlattr '4' want Z04 sizeof-cfg-cmd '4' want Z05 sizeof-cfg-params-packed '5' want Z06 sizeof-verdict-hdr '8' want Z07 sizeof-packet-hdr-packed '7' want Z08 sizeof-packet-hw '12' want Z09 sizeof-packet-timestamp '16' want Z10 sizeof-iphdr '20' want Z11 sizeof-udphdr '8' want Z12 sizeof-tcphdr '20' want Z13 subsys-queue '3' want Z14 msgtype-config '2' want Z15 msgtype-verdict '1' want Z16 copy-packet '2' # --------------------------------------------------------------------------- # H -- nfq_nlmsg_put. The 20 bytes every message of this protocol opens with, # and the only place the subsystem id and the queue number reach the wire. H02 # is (NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_CONFIG = 770; H10 puts a queue number # in both halves of the __be16 so a build that byte-swapped res_id wrongly # cannot round-trip through its own mistake. want H01 put-len '20' want H02 put-type-config '770' want H03 put-flags '1' want H04 put-seq '0' want H05 put-pid '0' want H06 nfgen-family '0' want H07 nfgen-version '0' want H08 nfgen-resid '7' want H09 put-header-bytes '1400000002030100000000000000000000000007' want H10 put-verdict-resid-bytes '1400000001030100000000000000000000000102' # --------------------------------------------------------------------------- # C -- THE COMPOSE HALF OF THE TICKET'S PASS CRITERION. Bind a queue number, # set the copy mode, unbind -- as bytes. This half runs on a kernel with no # netfilter at all, because composing a buffer touches no descriptor, and it is # where the port is actually proven: C02/C03/C05 are the three messages a real # consumer sends, byte for byte. C06 is all three in ONE message, the way # upstream's own examples/nf-queue.c composes it, and it is what pins attribute # ORDER and 4-byte alignment rather than just each attribute in isolation. want C01 pf-bind-bytes '1c000000020301000000000000000000000000000800010003000002' want C02 bind-bytes '1c000000020301000000000000000000000000070800010001000000' want C03 copy-mode-bytes '2000000002030100000000000000000000000007090002000000ffff02000000' want C04 qmaxlen-bytes '1c000000020301000000000000000000000000070800030000000400' want C05 unbind-bytes '1c000000020301000000000000000000000000070800010002000000' want C06 full-bind-bytes '30000000020301000000000000000000000000070800010001000000090002000000ffff020000000800030000000400' want C07 full-bind-len '48' # --------------------------------------------------------------------------- # V -- THE VERDICT MESSAGES. The other half of what an NFQUEUE consumer sends, # and the half that carries a mangled packet back to the kernel. # # V05 is the case that earns its keep: a 5-byte payload attribute must be # padded to 8 on the wire while nla_len stays 9. A composer that padded nla_len # too, or that failed to pad the buffer, produces bytes that look reasonable # and that no receiver can walk. V07 pins the NF_QUEUE bypass idiom, where the # target queue number lives in the HIGH half of the verdict word. want V01 verdict-accept-bytes '20000000010301000000000000000000000000070c000200000000010000002a' want V02 verdict-drop-bytes '20000000010301000000000000000000000000070c000200000000000000002a' want V03 verdict-mark-bytes '28000000010301000000000000000000000000070c000200000000010000002a0800030011223344' want V04 verdict-pkt-bytes '2c000000010301000000000000000000000000070c000200000000010000002a0c000a00deadbeef01020304' want V05 verdict-pkt-pad-bytes '2c000000010301000000000000000000000000070c000200000000010000002a09000a00deadbeef01000000' want V06 verdict-pkt-pad-len '44' want V07 verdict-nf-queue-bytes '20000000010301000000000000000000000000070c000200000380030000002a' # --------------------------------------------------------------------------- # P -- nfq_nlmsg_parse, over a NFQNL_MSG_PACKET message this probe synthesises. # This is the receive half of the protocol, and it is measurable here for the # same reason the compose half is: parsing a buffer touches no descriptor. # # P15 is the important one. A NFQA_PACKET_HDR attribute truncated to 3 bytes # must be REJECTED by the validator, not read past its end. That is the case a # parser which trusts nla_len fails, and it is the only memory-safety assertion # in this suite. want P01 packet-msg-bytes '50000000000301000000000000000000020000070b0001000000002a080000000800030011223344080005000000000108000d000000001418000a004500000000000000000000000000000000000000' want P02 parse-rc '1' want P03 attr-packet-hdr '1' want P04 attr-mark '1' want P05 attr-payload '1' want P06 attr-cap-len '1' want P07 attr-timestamp-absent '0' want P08 packet-id '42' want P09 hw-protocol '0x0800' want P10 hook '0' want P11 nfmark '0x11223344' want P12 indev '1' want P13 payload-len '20' want P14 payload-bytes '4500000000000000000000000000000000000000' want P15 parse-truncated-rejected '-1' # --------------------------------------------------------------------------- # B -- THE PACKET BUFFER. Runs entirely here; no kernel, no descriptor, no # excuse for a red. # # B10 is a real assertion and not a tautology: pktb_alloc must COPY, so # mutating the caller's buffer afterwards must not move what the pkt_buff # holds. A pktb that aliased the source would report 6 (IPPROTO_TCP) here. # B15 pins that AF_BRIDGE with an unsupported ethertype REFUSES and says why, # rather than allocating something unusable. want B01 build-len '33' want B02 pktb-len '33' want B03 pktb-tailroom '64' want B04 pktb-data '1' want B05 network-header-at-data '1' want B06 mac-header-null '1' want B07 transport-header-null '1' want B08 not-mangled '0' want B09 pktb-data-bytes '450000211234000040110000c0a80001c0a8000204d2162e000d000068656c6c6f' want B10 pktb-is-a-copy '17' want B11 after-put '37 60' want B12 after-trim '33 64' want B13 after-pull '13' want B14 after-push '33' want B15 bridge-bad-ethertype 'EPROTONOSUPPORT' # --------------------------------------------------------------------------- # M -- pktb_mangle in its three length regimes, then the protocol manglers that # additionally fix tot_len and the checksums. M10/M11 are the negative case: a # growth larger than the tailroom must be REFUSED and must leave the buffer # unmangled, not overflow it. M25 is the whole reinject path a real NFQUEUE # consumer drives, pinned as one packet. want M01 mangle-equal-rc '1' want M02 mangle-equal-state '33 1' want M03 mangle-equal-bytes '450000211234000040110000c0a80001c0a8000204d2162e000d0000776f726c64' want M04 mangle-grow-rc '1' want M05 mangle-grow-state '39 58' want M06 mangle-grow-bytes '450000211234000040110000c0a80001c0a8000204d2162e000d0000676f6f646279652d6e6f77' want M07 mangle-shrink-rc '1' want M08 mangle-shrink-len '30' want M09 mangle-shrink-bytes '450000211234000040110000c0a80001c0a8000204d2162e000d00006869' want M10 mangle-overflow-refused '0' want M11 mangle-overflow-state '33 0' want M12 ip-get-hdr '1' want M13 ip-set-transport-hdr '0' want M14 transport-hdr-offset '1' want M15 ip-mangle-rc '1' want M16 ip-mangle-tot-len '39' want M17 ip-mangle-check '0xe73e' want M18 ip-mangle-bytes '45000027123400004011e73ec0a80001c0a8000204d2162e000d0000676f6f646279652d6e6f77' want M19 udp-mangle-rc '1' want M20 udp-mangle-tot-len '39' want M21 udp-mangle-ip-check '0xe73e' want M22 udp-mangle-udp-len '19' want M23 udp-mangle-udp-check '0xdf89' want M24 udp-mangle-payload-len '11' want M25 udp-mangle-bytes '45000027123400004011e73ec0a80001c0a8000204d2162e0013df89676f6f646279652d6e6f77' # --------------------------------------------------------------------------- # K -- THE CHECKSUMS. The strongest cases in the probe: each is arithmetic over # a whole header, so a single byte at a wrong offset changes the result, which # a field-by-field read would not notice. # # K03 is an INDEPENDENT check of K01, computed by this probe's own arithmetic # rather than by the library: a correct IPv4 header summed over itself # including its checksum field is zero. If the library and this loop ever # disagree, K01 and K03 disagree, and neither has to be trusted on its own. want K01 ipv4-checksum '0xe744' want K02 ipv4-header-bytes '45000021123400004011e744c0a80001c0a80002' want K03 ipv4-checksum-selfsum '0x0000' want K04 udp-get-hdr '1' want K05 udp-checksum-ipv4 '0x1fae' want K06 udp-payload-len '5' want K07 udp-payload-offset '1' want K08 tcp-get-hdr '1' want K09 tcp-payload-len '4' want K10 tcp-ip-checksum '0x23a9' want K11 tcp-checksum-ipv4 '0xecf2' want K12 tcp-mangle-rc '1' want K13 tcp-mangle-bytes '4500002c43210000400623a90a0000010a0000020050115c111111112222222250027210c0c600007778797a' want K14 ipv6-get-hdr '1' want K15 ipv6-set-transport-hdr '1' want K16 ipv6-udp-get-hdr '1' want K17 udp-checksum-ipv6 '0x11c0' # --------------------------------------------------------------------------- # N -- THE FORMATTERS. Format strings are part of the library's contract -- a # consumer logs with them -- and they are the one place where a %u/%hu # confusion on a 32-bit arm shows up as readable output rather than as a wrong # number. Both strings carry a TRAILING SPACE upstream; it is pinned as-is # rather than trimmed, because trimming it would hide a change to the format. # # N05 pins the C99 snprintf contract on a short buffer: report what WOULD have # been written (80) while writing 9 characters plus the terminator. A # hand-rolled formatter that returned the truncated length instead is the # classic version of this bug and it reports success. want N01 ip-snprintf-len '80' want N02 ip-snprintf-text 'SRC=192.168.0.1 DST=192.168.0.2 LEN=33 TOS=0x0 PREC=0x0 TTL=64 ID=4660 PROTO=17 ' want N03 udp-snprintf-len '18' want N04 udp-snprintf-text 'SPT=1234 DPT=5678 ' want N05 ip-snprintf-truncated '80 9' # --------------------------------------------------------------------------- # L -- WHAT COULD NOT BE LINKED INTO THE PROBE, as measured by this build. # # These are not decoration and they are not constants: probe_link.h is # regenerated by do_compile from five canary links every time the recipe runs # (see the header comment for the five levels and their results). The # expectations below are therefore a live claim about the platform. # # L01/L02 EXPECT THE HANDLE API TO LINK. They used to expect the refusal: # libnfnetlink's nfnl_step, nfnl_catch and __nfnl_handle_msg held VLAs the # coroutinize pass refused (brintos/hwjs-cc#15). libnfnetlink's own 0001 now # moves all three arrays to the heap, and canaries 4 and 5 link. If either # case reads "unlinkable" again, that patch was lost or the pass regressed. # The probe still drives the transport over libmnl. Dropping that route is a # change to the probe, not to these expectations. # # L03 is the control: nfq_open() linked even while the handle API did not. # If L03 reds together with L01/L02, the toolchain regressed broadly. If # L01/L02 red alone, the regression is in the handle API's path. want L01 link-nfq-create-queue 'nfq_create_queue-linkable' want L02 link-nfq-handle-packet 'nfq_handle_packet-linkable' want L03 link-nfq-open 'nfq_open-linkable' # --------------------------------------------------------------------------- # S -- THE TRANSPORT, and the run half of the ticket's pass criterion. # # Composed by this library, carried by libmnl, ANSWERED BY THE KERNEL: each # case sends its config message with NLM_F_ACK and reports what mnl_cb_run # lifted out of NLMSG_ERROR, so "0 ok" means the kernel accepted it and an # errno is the kernel's own. # # S00 IS THE CONTROL AND IT IS WHAT MAKES EVERY ATTRIBUTION BELOW MEAN # ANYTHING. It opens NETLINK_ROUTE -- protocol 0, registered unconditionally # under CONFIG_NET. Green control beside a red S01 says "protocol 12 is # unregistered"; BOTH red would say "AF_NETLINK is unavailable", and those are # different faults in different places. Every S red below would then be a # consequence rather than a finding. want S00 rtnetlink-socket 'ok' # The attribution is COMPUTED from the probe's own output rather than written # in, so it re-aims itself as the platform moves and this file needs no edit on # the day brintos/linux#86 lands: # # the failing case answered EPERM/EACCES -> PERMISSION. Both the protocol and # the subsystem are present and we may not use # them. Takes priority over everything below, # whatever S01 said. # S01 not ok, EPROTONOSUPPORT -> protocol 12 is unregistered. #86. # S01 not ok, EPERM/EACCES -> the protocol EXISTS and we may not open it. # S01 ok -> the protocol is registered and a red past # here is the QUEUE SUBSYSTEM missing # (CONFIG_NETFILTER_NETLINK_QUEUE), the second # gap #86 explicitly does not close. # # Hard-coding "#86" into every red is what would make this suite quietly lie # the day half of it goes green. Keying only on S01 is the subtler version of # the same mistake, and it is what the sibling libnetfilter-log suite shipped # first: run this probe natively WITHOUT a namespace and S01 opens fine while # every case past it answers EPERM, so an S01-only rule prints "protocol 12 is # registered, therefore the queue subsystem is missing" over a red whose actual # cause is that we may not use it -- which sends the reader to a kernel .config # for a permissions problem. That arm is part of this port's evidence for # exactly this reason, and on THIS port it could not have been run at all until # the probe's errno reporting was fixed (see the oracle note above). NF_SOCK=${V_S01-} _attrib() { case "$1" in *EPERM*|*EACCES*) printf '%s' "held by PERMISSION, not absence: the kernel answered EPERM/EACCES, so netlink protocol 12 AND the queue subsystem are both present and this process lacks CAP_NET_ADMIN in its net namespace -- do not attribute this to brintos/linux#86" return ;; esac case "$NF_SOCK" in ok) printf '%s' "held by the QUEUE SUBSYSTEM, not by the protocol: S01 opened netlink protocol 12, so brintos/linux#86 has landed, but net/netfilter/nfnetlink_queue.c (CONFIG_NETFILTER_NETLINK_QUEUE, which additionally needs NETFILTER_ADVANCED) does not register NFNL_SUBSYS_QUEUE -- the second gap, which #86's own diff states it does not close" ;; EPERM|EACCES) printf '%s' "held by PERMISSION at the socket: S01 answered $NF_SOCK, so protocol 12 IS registered and this process lacks CAP_NET_ADMIN -- do not attribute this to brintos/linux#86" ;; '') printf '%s' "unattributed: the probe emitted no S01, so the transport was never reached and the cause is unknown" ;; *) printf '%s' "held by S01 netfilter-socket ($NF_SOCK): wasm32_defconfig sets no CONFIG_NETFILTER, so netlink protocol 12 is unregistered (brintos/linux#86)" ;; esac } # want_s # Same comparison as want(), with the computed attribution appended to a red so # no S failure can reach a reviewer without naming what holds it. want_s() { eval "_got=\${V_$1-}" if [ -z "$_got" ]; then bst_notok "libnetfilter-queue/$2" "no value on target (probe stopped before $1); want='$3' -- $(_attrib '')" elif [ "$_got" = "$3" ]; then bst_ok "libnetfilter-queue/$2" else bst_notok "libnetfilter-queue/$2" "want:'$3' got:'$_got' -- $(_attrib "$_got")" fi } want_s S01 netfilter-socket 'ok' want_s S02 netfilter-bind 'ok' # THE TICKET'S PASS CRITERION, in order and in its own words: "binds a queue # number, sets the copy mode, unbinds cleanly and reports each step's return # value, with the expected errno on a double bind." want_s S06 queue-bind '0 ok' want_s S07 queue-set-copy-mode '0 ok' # "...with the expected errno on a double bind." EBUSY, MEASURED on the x86-64 # oracle under `unshare -rn` -- see the header comment for why the obvious # guess (0, because it is our own portid) is wrong. want_s S08 queue-double-bind '-1 EBUSY' want_s S09 queue-unbind '0 ok' # The legacy handle. nfq_open()/nfq_close() are the only two entry points of # that transport that LINK on this arm (group L), and they are driven for that # reason: it is the one part of the classic API this image can still exercise # at all, so leaving it out would understate what the port delivers. S10 # separates "the handle opened" from "the handle has a usable descriptor" -- # and the probe emits it on BOTH branches, so a failure to open is reported as # 'no-handle' rather than vanishing and being read as a truncated run. want_s S05 nfq-open 'ok' want_s S10 nfq-fd '1' rm -f "$pout"