. "$BST_LIB" # libXdmcp 1.1.4 -- the X Display Manager Control Protocol codec (poky # recipes-graphics/xorg-lib). See libxdmcp_%.bbappend for what the port adds # and why. # # --------------------------------------------------------------------------- # WHAT CAN BE ASSERTED ABOUT A PROTOCOL LIBRARY WITH NO PEER # # XDMCP is a conversation between an X terminal and a display manager over UDP, # and this image has neither. There is nothing to connect to, so the obvious # test -- talk to a display manager -- is not available and would not be the # interesting one anyway. What libXdmcp actually IS is a codec: fourteen message # types, each a fixed sequence of length-prefixed fields in network byte order, # plus the XDM-AUTHENTICATION-1 block cipher and the key arithmetic around it. # # A codec is checkable with no peer at all. Encode a message into a memory # buffer, print the buffer, read it back with the matching decoder, compare # field by field -- and then compare the WHOLE RUN against the same source # compiled for the build host. That last step is what makes this more than a # self-consistency check: a codec that got a shift or a byte order wrong would # round-trip its own output perfectly and still put the wrong bytes on the wire. # The recipe builds the probe twice from one source and ships the build host's # run as xdmcp-wire.ref; § 4 below diffs them. # # WHAT IS DELIBERATELY NOT DIFFED, and why each is separate. XD-MODEL states the # arm's type sizes, which legitimately differ -- `long' and a pointer are 8 bytes # on the build host and 4 here -- so § 5 asserts the target's values directly # instead. XDK reports the generated-key path, whose entire point is that it is # NOT reproducible run to run; § 6 asserts its properties. # # --------------------------------------------------------------------------- # § 6 IS THE ONE THE PORT TURNS ON. Key.c compiles a static arc4random_buf of # its own when HAVE_ARC4RANDOM_BUF is undefined, and this libc declares a # non-static one -- the collision that stopped the build, repaired with a # per-recipe configure answer rather than a patch. That answer routes # XdmcpGenerateKey into the libc's arc4random_buf, and on this arm an unresolved # symbol becomes a wasm import rather than a link error, so a libc that did not # really provide it would trap here rather than fail to build. The probe fills # each key with a sentinel first, so "the generator ran" is asserted against # bytes that were something else beforehand rather than against zero. # # THE PACKAGE HAS NO PROGRAMS AND NO SHARED OBJECT. brintos.conf puts # --disable-shared on every class-target autotools build, so the deliverable is # libXdmcp.a, one header and one .pc file. That is why § 1 counts files rather # than running anything the package ships. INC=/usr/include LIBD=/usr/lib PCD=/usr/lib/pkgconfig XT=/usr/libexec/libxdmcp-tests work=${TMPDIR:-/tmp}/bst-libxdmcp.$$ rm -rf "$work"; mkdir -p "$work" # hasfile -- ok iff is a non-empty regular file. Fork-free. hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "missing or empty: $2"; fi } # --------------------------------------------------------------------------- # 0. THE CANARY, which is not an assertion about libXdmcp at all. # # The guest console drops a suite's FIRST BST result line before the host parser # sees it -- brintos/linux#96. The harness computes its pass count as # total-minus-failures, so THE DROPPED LINE IS COUNTED AS A PASS, and a suite # whose first case is a genuine `not ok` loses the one line that mattered. # So the first line out of this file asserts something already true. # Remove this case when #96 is fixed. bst_ok libxdmcp/suite-canary # --------------------------------------------------------------------------- # 1. PAYLOAD -- everything the package installs, by name. # # Three files and no program. The header and the archive are what a client # links against; the .pc is how it finds them. hasfile libxdmcp/hdr-Xdmcp "$INC/X11/Xdmcp.h" hasfile libxdmcp/lib-static "$LIBD/libXdmcp.a" hasfile libxdmcp/pc-file "$PCD/xdmcp.pc" hasfile libxdmcp/probe-shipped "$XT/xdmcp-wire-probe" hasfile libxdmcp/ref-shipped "$XT/xdmcp-wire.ref" # 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 libxdmcp/no-shared-object "$LIBD/libXdmcp.so" "$LIBD/libXdmcp.so.6" # The header must declare the wrapper entry points. They are compiled under # HASXDMAUTH, which configure defines if and only if Wraphelp.c is in the # tarball -- so a tarball that stopped shipping it would leave the library # without a cipher and every other case here would still pass. if grep -q 'XdmcpWrap' "$INC/X11/Xdmcp.h"; then bst_ok libxdmcp/hdr-declares-wrap else bst_notok libxdmcp/hdr-declares-wrap \ "X11/Xdmcp.h does not declare XdmcpWrap; HASXDMAUTH was undefined, so this library has no XDM-AUTHENTICATION-1" fi # --------------------------------------------------------------------------- # 2. HOW A CLIENT FINDS IT. libXdmcp exists to be linked by libX11 and by # display managers, and all of them reach it through pkg-config. require pkg-config libxdmcp/pkg-config-present check libxdmcp/pc-exists pkg-config --exists xdmcp check libxdmcp/pc-validate pkg-config --validate xdmcp check_eq libxdmcp/pc-modversion 1.1.4 \ "$(pkg-config --modversion xdmcp 2>/dev/null)" # The recipe carries PE = "1", which is a packaging epoch and not part of the # library's identity. Asserted because a reader who saw `1:1.1.4` in the package # database might reasonably try to make the .pc agree, and should see this go red. check_eq libxdmcp/pc-no-epoch "" \ "$(pkg-config --modversion xdmcp 2>/dev/null | grep ':' || true)" # PKG_CONFIG_ALLOW_SYSTEM_* IS REQUIRED HERE. pkg-config suppresses an -I or -L # naming a directory already on the compiler's default paths, so both queries # answer with the flag stripped on an image whose prefix is /usr -- correctly, # and uselessly for this assertion. Setting the variables asks for the flags # pkg-config COMPUTED, which is what says whether the .pc's prefix arithmetic # still resolves to the directory holding the files. The trailing space is # pkg-config's own and is trimmed rather than tolerated, because the result is # used as a path below. cf=$(PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --cflags xdmcp 2>/dev/null | sed 's/[[:space:]]*$//') check_eq libxdmcp/pc-cflags "-I$INC" "$cf" d=${cf#-I} check_rc libxdmcp/pc-cflags-dir-real 0 test -f "$d/X11/Xdmcp.h" lf=$(PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs xdmcp 2>/dev/null | sed 's/[[:space:]]*$//') check_eq libxdmcp/pc-libs "-L$LIBD -lXdmcp" "$lf" # xdmcp.pc declares Requires: xproto, so it does not resolve at all unless # xorgproto is on the image. That is a real dependency of the port and is # asserted as one rather than assumed from the build having succeeded. check libxdmcp/pc-requires-xproto pkg-config --exists 'xdmcp xproto' # --------------------------------------------------------------------------- # 3. THE PROBE RUNS. # # do_install ships the payload -m 0644 for the reference and -m 0755 for the # binary, and the rootfs is read-only at runtime, so the probe is copied to # /tmp -- a mode=1777 tmpfs with no noexec -- rather than exec'd where it sits. probe=$work/xdmcp-wire-probe if cp "$XT/xdmcp-wire-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then # The probe exits 0 whatever it FINDS -- the recipe redirects the build-host # run into a file inside a bitbake shell task, where a non-zero exit would # fail do_install over a finding. So a non-zero status here means the binary # did not run: a trap, an unresolved import, an exec refusal. On this arm that # is a real and common failure and is what this case is for. "$probe" > "$work/wire.out" 2>"$work/wire.err" rc=$? if [ "$rc" -ne 0 ]; then bst_notok libxdmcp/probe-runs "rc=$rc $(_bst_1line "$(cat "$work/wire.err")")" else bst_ok libxdmcp/probe-runs fi tgt_total=$(sed -n 's/^XD-TOTAL //p' "$work/wire.out") ref_total=$(sed -n 's/^XD-TOTAL //p' "$XT/xdmcp-wire.ref") # The probe reached its end. Without this, a run truncated after ten lines # would present a short set of results that happened to match a short prefix. if [ -n "$tgt_total" ]; then bst_ok libxdmcp/probe-complete else bst_notok libxdmcp/probe-complete \ "no XD-TOTAL line: $(_bst_1line "$(cat "$work/wire.out")")" fi # Coverage floor, bounded from below and dated 2026-08-27. It is the same # number the recipe refuses to build under; asserting it again here is what # catches a rootfs carrying a probe from an older, thinner build. if [ -n "$tgt_total" ] && [ "$tgt_total" -ge 55 ]; then bst_ok libxdmcp/probe-coverage else bst_notok libxdmcp/probe-coverage \ "emitted ${tgt_total:-0} deterministic lines, expected not less than 55" fi # The two runs must cover the same set, or § 4 is comparing different # questions and a clean diff would say nothing. check_eq libxdmcp/probe-total-agrees "$ref_total" "$tgt_total" # ------------------------------------------------------------------------- # 4. THE ASSERTION THIS SUITE EXISTS FOR. # # Every deterministic line of the target run, against the build host's. This # covers the encoding of all fourteen message types byte for byte, the # field-by-field decode of each, the four documented refusals, the key # arithmetic and the XDM-AUTHENTICATION-1 ciphertext. A line only the target # reports, or a line that differs, is a codec fault on this arm. sed -n 's/^XD //p' "$work/wire.out" > "$work/tgt.xd" sed -n 's/^XD //p' "$XT/xdmcp-wire.ref" > "$work/ref.xd" if cmp -s "$work/tgt.xd" "$work/ref.xd"; then bst_ok libxdmcp/wire-matches-buildhost else bst_notok libxdmcp/wire-matches-buildhost \ "wasm32 and the build host encode XDMCP differently: $(_bst_1line "$(diff "$work/ref.xd" "$work/tgt.xd" 2>&1)")" fi # THE SAME COMPARISON, BROKEN OUT, so a red says WHICH half moved rather than # only that something did. The case above is the one that matters; these four # are what a reader looks at next, and they cost four greps. for grp in FIX DEC REJ WRAP; do grep "^$grp " "$work/tgt.xd" > "$work/g.t" 2>/dev/null grep "^$grp " "$work/ref.xd" > "$work/g.r" 2>/dev/null if cmp -s "$work/g.t" "$work/g.r"; then bst_ok "libxdmcp/wire-$grp" else bst_notok "libxdmcp/wire-$grp" \ "$(_bst_1line "$(diff "$work/g.r" "$work/g.t" 2>&1)")" fi done # Every message type present, by name, so a probe that silently stopped # emitting fixtures cannot pass § 4 on a short list that matches a short # reference. Fourteen protocol opcodes plus the ARRAY32 carrier. nfix=$(grep -c '^FIX ' "$work/tgt.xd") check_eq libxdmcp/fixture-count 15 "$nfix" for m in broadcast-query query indirect-query forward-query willing unwilling \ request accept decline manage refuse failed keepalive alive; do if grep -q "^DEC $m hdr=ok items=ok tail=0$" "$work/tgt.xd"; then bst_ok "libxdmcp/roundtrip-$m" else bst_notok "libxdmcp/roundtrip-$m" \ "$(_bst_1line "$(grep "^DEC $m " "$work/tgt.xd" || echo 'no DEC line for this message type')")" fi done # ------------------------------------------------------------------------- # 5. THE PRECONDITION THAT MAKES § 4 MEAN ANYTHING. # # The two runs agree on the wire bytes because XDMCP is encoded byte at a # time, in network order, by code that never casts a struct onto the wire. If # that stopped being true the diff would go red without saying why, so the # arm's own data model is asserted here in its own right. `long' and a pointer # are 4 bytes on wasm32 and 8 on the build host, which is exactly why this # line is excluded from the diff rather than compared. mdl=$(sed -n 's/^XD-MODEL //p' "$work/wire.out") check_eq libxdmcp/wire-model \ "long=4 ptr=4 card8=1 card16=2 card32=4 hdr=6 a8=8 a16=8 a32=8 aa8=8 key=8 msglen=8192" \ "$mdl" # ------------------------------------------------------------------------- # 6. THE GENERATED-KEY PATH -- the code the port's configure answer selects. # # Not diffed and cannot be: the whole point of XdmcpGenerateKey is that its # output differs every run. What is asserted is what must hold of any correct # generator, over sixteen draws. gen=$(sed -n 's/^XDK generate //p' "$work/wire.out") if [ -z "$gen" ]; then bst_notok libxdmcp/key-generate-ran \ "the probe emitted no XDK generate line, so XdmcpGenerateKey never returned" else bst_ok libxdmcp/key-generate-ran fi # `untouched' counts keys still holding the 0xa5 sentinel the probe wrote # before calling the generator. Anything but zero means the generator did not # write the buffer -- which is what a stubbed-out or unwired arc4random_buf # looks like when it does not trap outright. check_eq libxdmcp/key-sentinel-overwritten 0 \ "$(printf '%s\n' "$gen" | sed -n 's/.*untouched=\([0-9][0-9]*\).*/\1/p')" # An all-zero key is the other silent failure: a generator that ran and # produced nothing. It is also a real weak key, so it is worth its own case. check_eq libxdmcp/key-not-zero 0 \ "$(printf '%s\n' "$gen" | sed -n 's/.*allzero=\([0-9][0-9]*\).*/\1/p')" # Sixteen draws of eight bytes, all distinct. A generator seeded once per # process -- Key.c's own srandom fallback, which is what the port would get if # the configure answer stopped landing -- repeats within a run, so this is the # case that tells the two code paths apart from the outside. check_eq libxdmcp/key-distinct 16 \ "$(printf '%s\n' "$gen" | sed -n 's/.*distinct=\([0-9][0-9]*\).*/\1/p')" # ------------------------------------------------------------------------- # 7. SPOT ASSERTIONS on the values themselves, so a diff-only failure is not # the only thing a reader has to go on. These are pinned against the protocol # rather than against the reference file: XDMCP's header is version 1 and its # length field counts the payload, and a QUERY carrying one 20-byte # authentication name and one empty one is 25 bytes of payload after a 6-byte # header. Read them as the worked example that says the encoding is the # protocol's and not merely self-consistent. check_eq libxdmcp/enc-query \ "FIX query op=2 plen=25 enc=00010002001902001458444d2d41555448454e5449434154494f4e2d310000" \ "$(grep '^FIX query ' "$work/tgt.xd")" check_eq libxdmcp/proto-constants "PROTO version=1 opcodes=14 states=15" \ "$(grep '^PROTO ' "$work/tgt.xd")" # The cipher round-trips, and a one-bit change to the wrapper changes every # byte of the ciphertext. Both are diffed above; both are named here because # "the DES tables were mis-shifted" is the failure a reader most needs the # suite to be able to say out loud. check_eq libxdmcp/wrap-roundtrip "WRAP roundtrip 1" \ "$(grep '^WRAP roundtrip ' "$work/tgt.xd")" check_eq libxdmcp/wrap-key-sensitivity "WRAP key-sensitivity differing=24 of=24" \ "$(grep '^WRAP key-sensitivity ' "$work/tgt.xd")" # The four refusals, named. A decoder that accepted a truncated array would # read past the end of a datagram an attacker supplied, and a round-trip test # can never reach that case because a round trip only presents well-formed # input. for r in a8 a16 a32 aa8; do check_eq "libxdmcp/reject-$r-truncated" \ "REJ $r-truncated read=0 data-null=1" \ "$(grep "^REJ $r-truncated " "$work/tgt.xd")" done check_eq libxdmcp/reject-alloc-overlong "REJ alloc-a8 max=1 over=0 neg=0" \ "$(grep '^REJ alloc-a8 ' "$work/tgt.xd")" else bst_notok libxdmcp/probe-stage "cannot stage $XT/xdmcp-wire-probe" fi rm -rf "$work"