. "$BST_LIB" # libtar 1.2.20 (meta-oe recipes-support/libtar, with meta-oe's 14 patches) is a # POSIX tar reader/writer as a LIBRARY, plus libtar(1), upstream's sample CLI # linked against the very archive that ships. # # --------------------------------------------------------------------------- # WHY THIS SUITE RUNS A PROBE INSTEAD OF STOPPING AT PAYLOAD SHAPE # # BRINTOS-217 is why: oniguruma COMPILED and PACKAGED perfectly on this arm and # then returned silently wrong offsets to every caller, so a payload-only suite # would have gone green on the exact defect the port existed to find. # # What libtar computes is BYTE LAYOUT, and that is a different question from any # sibling port on this Epic. The 512-byte header is not a struct the compiler # lays out for you -- it is fifteen fixed-width ASCII fields the library formats # and re-parses by hand: # # det-oct2int-* every numeric field on disk is space- and NUL-padded octal # det-int2oct* TEXT, produced by snprintf("%*lo") and re-read by # sscanf("%o"). A width, a pad character or an integer # conversion that is off by one byte produces an archive that # is structurally valid and silently wrong. # det-h1..h8-* the encoder across every typeflag: regular file, directory # (whose name gains a '/' only after th_set_type), symlink, # hardlink, character device, and both long-name forms. # h1-block is the WHOLE 512-byte block in hex, so one case # covers every byte including the padding the field cases do # not reach. # det-safer-* CVE-2013-4420. th_get_pathname() runs safer_name_suffix() # over the archived name, so "../../etc/passwd" must extract # as "etc/passwd". This is a SECURITY property of the port, # it is pure string arithmetic, and it is asserted here on # the target rather than assumed from the patch being listed # in SRC_URI. # det-mem-* a whole archive built and re-read through a tartype_t whose # four function pointers work on a RAM buffer. That is # libtar's indirect dispatch driven end to end with ZERO # syscalls, so the result is a pure function of the library # and is compared against native byte for byte. # det-churn-* 2000 open/close cycles, 1000 gnu_longname replacements # (each one a strdup the previous call must have freed), and # 5000 header encodes asserted byte-stable. A leak, a # double-free or a stale read shows up here as a wrong answer # or a trap rather than as a message. # # THE DETERMINISTIC/ENVIRONMENT SPLIT IS DELIBERATE. The probe prints "LT ..." # for values that are a pure function of the library, and "LTX ..." for the ones # that need a real filesystem. Only the first set is byte-compared against # native; the second is asserted case by case, because libtar restores owner, # mtime and mode on EVERY extracted member with no way to opt out -- so a target # whose attribute syscalls differ from a Linux host is a legitimate difference to # report, not a diff to fail. # # EVERY EXPECTED VALUE IS THE VALUE A NATIVE x86_64 BUILD OF THE IDENTICAL # PATCHED SOURCE PRODUCES, and native-byte-identical below makes that comparison # ON TARGET with cmp(1) -- deliberate, and the accumulated lesson of two sibling # tickets: the console dump truncates long lines (BRINTOS-260) and the # expect-backed host SSH is pty-based and rewrites LF to CRLF (BRINTOS-263). # # THE SUITE FORKS AS LITTLE AS IT CAN. On this runtime a multi-MB wasm module's # start-up dominates, and the cjson suite next door was killed by the runner's # per-suite bound at 156 cases written as one fork per case. The probe runs ONCE # and its output is read into a variable; every case below is a shell builtin # `case` match against it. require libtar libtar/present # has -- builtin substring match, no fork has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "missing: $2" ;; esac } hdr=/usr/include/libtar.h lhh=/usr/include/libtar_listhash.h arc=/usr/lib/libtar.a # --- payload ----------------------------------------------------------------- check libtar/payload-header test -s "$hdr" check libtar/payload-listhash-h test -s "$lhh" check libtar/payload-archive test -s "$arc" check libtar/payload-cli test -x /usr/bin/libtar # brintOS is static-only by policy (conf/distro/brintos.conf: EXTRA_OECONF += # --disable-shared, and wasmld's coro arm has no side-module ABI). Unlike # minini, http-parser and libstemmer, libtar HAS a real configure script, so the # distro-wide knob reaches it and no bbappend was needed -- a .so here would mean # --disable-shared silently stopped reaching libtool. if [ -e /usr/lib/libtar.so ] || [ -e /usr/lib/libtar.so.0 ] || \ [ -e /usr/lib/libtar.so.1 ]; then bst_notok libtar/no-shared-object "a libtar .so is present on a static-only image" else bst_ok libtar/no-shared-object fi # --- header ABI -------------------------------------------------------------- # Every value below depends on these, so a header change fails loudly here # rather than shifting every result underneath it. hdrtext=$(cat "$hdr" 2>/dev/null) has libtar/hdr-open "int tar_open(TAR **t, const char *pathname" "$hdrtext" has libtar/hdr-fdopen "int tar_fdopen(TAR **t, int fd" "$hdrtext" has libtar/hdr-close "int tar_close(TAR *t);" "$hdrtext" has libtar/hdr-append-file "int tar_append_file(TAR *t" "$hdrtext" has libtar/hdr-append-tree "int tar_append_tree(TAR *t" "$hdrtext" has libtar/hdr-extract-all "int tar_extract_all(TAR *t" "$hdrtext" has libtar/hdr-th-read "int th_read(TAR *t);" "$hdrtext" has libtar/hdr-th-write "int th_write(TAR *t);" "$hdrtext" has libtar/hdr-tartype "tartype_t;" "$hdrtext" has libtar/hdr-openfunc "typedef int (*openfunc_t)(const char *, int, ...);" "$hdrtext" has libtar/hdr-cplusplus "extern \"C\"" "$hdrtext" has libtar/hdr-blocksize "#define T_BLOCKSIZE 512" "$hdrtext" has libtar/hdr-namelen "#define T_NAMELEN 100" "$hdrtext" has libtar/hdr-prefixlen "#define T_PREFIXLEN 155" "$hdrtext" has libtar/hdr-gnu-opt "#define TAR_GNU 1" "$hdrtext" # The 0008 patch moved th_get_pathname off a static buffer and onto a per-handle # malloc; the extra TAR member is the ABI half of that change and tar_close() # frees it. If this field ever went away the probe's every-header-frees-cleanly # assumption would go with it. has libtar/hdr-th-pathname "char *th_pathname;" "$hdrtext" # --- symbol completeness ----------------------------------------------------- # Derived ON TARGET from the installed header rather than written into the test, # so it survives a version bump; the count is pinned and each name is also # spot-checked, so an empty derivation cannot make the loop vacuously green. # #define lines are dropped first: libtar.h declares eleven of its accessors as # MACROS (th_get_size, th_crc_ok, tar_block_read, …), which correctly have no # symbol in the archive and would otherwise read as eleven missing ones. # Whole identifiers are matched and then filtered in the shell rather than with # a word-boundary escape: busybox grep's \b/\< support is not something to rely # on, and an unanchored "th_" would find one inside pa-th_hashfunc. allsyms=$(grep -v '^#define' "$hdr" 2>/dev/null \ | grep -o '[A-Za-z_][A-Za-z_0-9]*(' | sed 's/(//' | sort -u) hdrsyms= for s in $allsyms; do case "$s" in tar_*|th_*) hdrsyms="$hdrsyms $s" ;; esac done arcsyms=$(grep -ao 'tar_[a-z_]*\|th_[a-z_]*' "$arc" 2>/dev/null | sort -u) nsym=0; nmiss=0; missing= for s in $hdrsyms; do nsym=$((nsym + 1)) case "$arcsyms" in *"$s"*) ;; *) nmiss=$((nmiss + 1)); missing="$missing $s" ;; esac done if [ "$nmiss" -eq 0 ] && [ "$nsym" -gt 0 ]; then bst_ok libtar/symbols-complete else bst_notok libtar/symbols-complete "$nmiss missing:$missing" fi check_eq libtar/symbols-count 39 "$nsym" has libtar/sym-open "tar_open" "$arcsyms" has libtar/sym-close "tar_close" "$arcsyms" has libtar/sym-append-tree "tar_append_tree" "$arcsyms" has libtar/sym-extract-all "tar_extract_all" "$arcsyms" has libtar/sym-th-read "th_read" "$arcsyms" has libtar/sym-th-write "th_write" "$arcsyms" # --- libtar(1): the library reached through the image's OWN binary ----------- # A real target executable linked against the shipped archive, driven across a # process boundary. Note it creates the archive with open(O_CREAT), not creat(2) # -- which is why this works where GNU tar's named-file -cf does not # (brintos/linux#13, recorded in 58-tar.test). wd=${TMPDIR:-/tmp}/bst-libtar.$$ mkdir -p "$wd/src/nested" 2>/dev/null printf 'libtar cli payload\n' > "$wd/src/a.txt" 2>/dev/null printf 'nested\n' > "$wd/src/nested/b.txt" 2>/dev/null cliv=$(libtar -V 2>&1) has libtar/cli-version "libtar" "$cliv" ( cd "$wd" && libtar -c out.tar src >/dev/null 2>&1 ) check libtar/cli-create test -s "$wd/out.tar" lst=$( cd "$wd" && libtar -t out.tar 2>&1 ) has libtar/cli-list-a "src/a.txt" "$lst" has libtar/cli-list-nested "src/nested/b.txt" "$lst" # a block-multiple archive is the format's own invariant, asserted without a # second fork of the CLI sz=$(wc -c < "$wd/out.tar" 2>/dev/null) if [ -n "$sz" ] && [ "$((sz % 512))" -eq 0 ] && [ "$sz" -gt 0 ]; then bst_ok libtar/cli-block-aligned else bst_notok libtar/cli-block-aligned "archive size $sz is not a positive multiple of 512" fi mkdir -p "$wd/out" 2>/dev/null ( cd "$wd/out" && libtar -x ../out.tar >/dev/null 2>&1 ) if [ -f "$wd/out/src/a.txt" ]; then got=$(cat "$wd/out/src/a.txt" 2>&1) check_eq libtar/cli-extract-content "libtar cli payload" "$got" else bst_notok libtar/cli-extract-content "src/a.txt not extracted" fi check libtar/cli-extract-nested test -f "$wd/out/src/nested/b.txt" # -g selects the GNU long-name/long-link extensions; a path past 100 characters # is the only way to tell the two encodings apart from the outside. deep=$wd/src/nested/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa mkdir -p "$deep/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 2>/dev/null printf 'deep\n' > "$deep/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/c.txt" 2>/dev/null ( cd "$wd" && libtar -g -c gnu.tar src >/dev/null 2>&1 ) gl=$( cd "$wd" && libtar -g -t gnu.tar 2>&1 ) has libtar/cli-gnu-longname "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/c.txt" "$gl" # an unreadable archive must fail rather than report an empty listing libtar -t "$wd/no-such-archive.tar" >/dev/null 2>&1 if [ $? -ne 0 ]; then bst_ok libtar/cli-missing-archive else bst_notok libtar/cli-missing-archive "listing a nonexistent archive succeeded" fi # --- the library RUNNING, via the cross-linked probe ------------------------- # data/libtar-probe is libtar-probe.c cross-linked with wasmcc-glibc against THE # VERY ARCHIVE THAT SHIPS (/usr/lib/libtar.a), staged to $TMPDIR and run here. It # runs ONCE and its exit status is kept, rather than re-run for the rc case: on # this runtime a multi-MB wasm module's start-up dominates. probe=$wd/libtar-probe if cp "$BST_DATA/libtar-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then out=$("$probe" "$wd/probe-wd" 2>&1); prc=$? # the arm banner, held OUT of the byte comparison below has libtar/det-probe-banner "LT-PROBE libtar-probe ptr=4 long=4" "$out" # --- the deterministic section, case by case ------------------------------ has libtar/det-header-onblock "LT chk-header-onblock ok" "$out" # the 0011 static-analysis patch: a failed sscanf must yield 0, not an # uninitialised int that happens to look plausible has libtar/det-oct2int-empty "LT chk-oct2int-empty ok" "$out" has libtar/det-oct2int-nonoctal "LT chk-oct2int-nonoctal ok" "$out" has libtar/det-oct2int-stops-at-8 "LT chk-oct2int-stops-at-8 ok" "$out" # every numeric field on disk, encoded and read back i=0 while [ $i -lt 18 ]; do n=$(printf '%02d' $i) has "libtar/det-oct-roundtrip-$n" "LT chk-oct-roundtrip-$n ok" "$out" i=$((i + 1)) done has libtar/det-oct-roundtrip-intmax "LT chk-oct-roundtrip-intmax ok" "$out" # h1: a plain ustar regular-file header, every field set explicitly has libtar/det-h1-crcok "LT chk-h1-crcok ok" "$out" has libtar/det-h1-isreg "LT chk-h1-isreg ok" "$out" has libtar/det-h1-notdir "LT chk-h1-notdir ok" "$out" has libtar/det-h1-notsym "LT chk-h1-notsym ok" "$out" has libtar/det-h1-getsize "LT chk-h1-getsize ok" "$out" has libtar/det-h1-getmtime "LT chk-h1-getmtime ok" "$out" has libtar/det-h1-getmode "LT chk-h1-getmode ok" "$out" has libtar/det-h1-getuid "LT chk-h1-getuid ok" "$out" has libtar/det-h1-getgid "LT chk-h1-getgid ok" "$out" # h2/h3: a 200-character path, split into the POSIX prefix+name pair and then # carried whole by the GNU long-name extension. Two different encodings that # must both read back as the identical path. has libtar/det-h2-prefix-used "LT chk-h2-prefix-used ok" "$out" has libtar/det-h2-getpath "LT chk-h2-getpath ok" "$out" has libtar/det-h3-longname-set "LT chk-h3-longname-set ok" "$out" has libtar/det-h3-prefix-unused "LT chk-h3-prefix-unused ok" "$out" has libtar/det-h3-getpath "LT chk-h3-getpath ok" "$out" # h4..h8: the remaining typeflags, and the uid path that must NOT consult the # passwd database has libtar/det-h4-isdir "LT chk-h4-isdir ok" "$out" has libtar/det-h4-trailing-slash "LT chk-h4-trailing-slash ok" "$out" has libtar/det-h5-issym "LT chk-h5-issym ok" "$out" has libtar/det-h6-ischr "LT chk-h6-ischr ok" "$out" has libtar/det-h6-devmajor "LT chk-h6-devmajor ok" "$out" has libtar/det-h6-devminor "LT chk-h6-devminor ok" "$out" has libtar/det-h7-longlink-sentinel "LT chk-h7-longlink-sentinel ok" "$out" has libtar/det-h7-longlink-set "LT chk-h7-longlink-set ok" "$out" has libtar/det-h8-uname-empty "LT chk-h8-uname-empty ok" "$out" has libtar/det-h8-getuid "LT chk-h8-getuid ok" "$out" # CVE-2013-4420: a hostile archived name must be neutralised before it is ever # handed to open(2). Asserted on target, not assumed from SRC_URI. has libtar/det-safer-absolute "LT chk-safer-absolute ok" "$out" has libtar/det-safer-traversal "LT chk-safer-traversal ok" "$out" has libtar/det-safer-root "LT chk-safer-root ok" "$out" has libtar/det-safer-prefix-join "LT chk-safer-prefix-join ok" "$out" # the list and hash the library uses to track hardlinks and devices has libtar/det-list-new "LT chk-list-new ok" "$out" has libtar/det-list-nents "LT chk-list-nents ok" "$out" has libtar/det-hash-new "LT chk-hash-new ok" "$out" has libtar/det-hash-added "LT chk-hash-added ok" "$out" has libtar/det-hash-nents "LT chk-hash-nents ok" "$out" has libtar/det-hash-getkey "LT chk-hash-getkey ok" "$out" has libtar/det-hash-getkey-absent "LT chk-hash-getkey-absent ok" "$out" # THE END-TO-END GROUP: a five-member archive written and re-read through a # tartype_t of four function pointers over a RAM buffer -- libtar's indirect # dispatch, with no filesystem underneath it. has libtar/det-mem-build "LT chk-mem-build ok" "$out" has libtar/det-mem-block-aligned "LT chk-mem-block-aligned ok" "$out" has libtar/det-mem-reopen "LT chk-mem-reopen ok" "$out" has libtar/det-mem-e0-content "LT chk-mem-e0-content ok" "$out" has libtar/det-mem-entries "LT chk-mem-entries ok" "$out" has libtar/det-mem-eof "LT chk-mem-eof ok" "$out" has libtar/det-mem-close "LT chk-mem-close ok" "$out" # allocation churn: a leak or a double-free surfaces as a wrong answer or a # trap, never as a message has libtar/det-churn-open-close "LT chk-churn-open-close-2000 ok" "$out" has libtar/det-churn-longname "LT chk-churn-longname-1000 ok" "$out" has libtar/det-churn-encode-stable "LT chk-churn-encode-5000-stable ok" "$out" # THE LOAD-BEARING ONE. data/libtar-native.txt is the verbatim "LT " section of # this same probe source built NATIVELY against a host build of the identical # PATCHED release. The LT-PROBE banner (pointer width) and the whole "LTX " # section (real syscalls) are excluded by construction, not by a filter written # twice. ctl=$BST_DATA/libtar-native.txt gotf=$wd/got.txt printf '%s\n' "$out" | grep '^LT ' > "$gotf" 2>/dev/null if [ ! -s "$ctl" ]; then bst_notok libtar/native-byte-identical "control absent or empty: $ctl" elif cmp -s "$ctl" "$gotf"; then bst_ok libtar/native-byte-identical else bst_notok libtar/native-byte-identical \ "differs from the native control: $(_bst_1line "$(cmp "$ctl" "$gotf" 2>&1)")" fi has libtar/det-summary "LT-SUMMARY 68/68" "$out" # --- the environment section ---------------------------------------------- # Real syscalls against a real directory. libtar restores owner, mtime and # mode on every extracted member with NO way to opt out (tar_extract_file # always calls tar_set_file_perms), so if any of those three is unavailable # here the extract cases below go red together and the three syscall-* cases # say which one did it. has libtar/env-workdir "LTX chk-workdir ok" "$out" has libtar/env-write-a "LTX chk-write-a ok" "$out" has libtar/env-write-b "LTX chk-write-b ok" "$out" has libtar/env-open-write "LTX chk-open-write ok" "$out" has libtar/env-append-tree "LTX chk-append-tree ok" "$out" has libtar/env-append-eof "LTX chk-append-eof ok" "$out" has libtar/env-close-write "LTX chk-close-write ok" "$out" has libtar/env-archive-nonempty "LTX chk-archive-nonempty ok" "$out" has libtar/env-archive-block-aligned "LTX chk-archive-block-aligned ok" "$out" has libtar/env-open-read "LTX chk-open-read ok" "$out" has libtar/env-list-has-a "LTX chk-list-has-a ok" "$out" has libtar/env-list-has-b "LTX chk-list-has-b ok" "$out" has libtar/env-extract-all "LTX chk-extract-all ok" "$out" has libtar/env-extract-a-content "LTX chk-extract-a-content ok" "$out" has libtar/env-extract-b-content "LTX chk-extract-b-content ok" "$out" # asked directly, so a red extract above is attributable to one syscall rather # than to libtar has libtar/env-syscall-chmod "LTX chk-syscall-chmod ok" "$out" has libtar/env-syscall-chown "LTX chk-syscall-chown ok" "$out" has libtar/env-syscall-utime "LTX chk-syscall-utime ok" "$out" has libtar/env-summary "LTX-SUMMARY 18/18" "$out" check_eq libtar/probe-rc 0 "$prc" else bst_skip libtar/det-probe "data/libtar-probe absent from the image" fi rm -rf "$wd"