# libcap 2.69 -- the POSIX.1e capability tools capsh, getcap, setcap and # getpcaps (package libcap-bin, all four in /usr/sbin). # # This port splits THREE ways on this kernel, and the suite is organised around # that split rather than around the four binaries: # # * pure userspace -- cap_from_text/cap_to_text, the capability name table and # capsh's built-in per-capability documentation. Correct regardless of the # kernel, so green here is a claim about the port and nothing else. # * capget(2) -- WIRED on this arm (the brintos/linux#10 fix carried on our # kernel fork, BRINTOS-81), so the PROCESS-credential half genuinely works: # capsh --print's Current: line (capget with pid=0, i.e. self) and # getpcaps (capget through find_task_by_vpid). # * prctl(2) and the *xattr syscalls -- NOT wired. prctl has no entry in the # wasm32 syscall table at all, so the bounding set, the ambient set, # securebits and keepcaps are unreadable; the xattr family answers ENOSYS # (brintos/linux#27), so FILE capabilities can be neither set nor read. # # The reds are left FAILING rather than skipped, so each flips green by itself # the day its syscall lands. # # TRAP, measured against a native libcap 2.69 build before this file was # written: getcap exits 0 on EVERY path -- a missing file, an unreadable store, # a directory, anything. `check getcap ...` is therefore a false-green # generator, and so is any needle that getcap's own FAILURE line also prints # (it repeats the filename back). Every getcap assertion below reads STDOUT # ONLY -- the diagnostics go to stderr -- or compares the entire output. . "$BST_LIB" require capsh libcap/present # ok iff exits NON-zero AND its output carries . Several of these # tools signal a usage/parse error through both a status and a message; a # status-only check would go green on a kernel that failed the same call for an # entirely different reason. check_err() { _id=$1; _needle=$2; shift 2 _out=$("$@" 2>&1); _rc=$? case "$_out" in *"$_needle"*) _hit=1 ;; *) _hit=0 ;; esac if [ "$_rc" -ne 0 ] && [ "$_hit" -eq 1 ]; then bst_ok "$_id" else bst_notok "$_id" "rc=$_rc want:'$_needle' got:'$(_bst_1line "$_out")'"; fi } T=${TMPDIR:-/tmp}/bst-libcap.$$ mkdir -p "$T/sub" || exit 0 trap 'rm -rf "$T"' EXIT printf '#!/bin/sh\nexit 0\n' > "$T/f"; chmod 755 "$T/f" cp "$T/f" "$T/sub/g" # ---------------------------------------------------------------- packaging -- # R2 is about all four tools SHIPPING, not just the one `require` guarded on. for b in getcap setcap getpcaps; do check "libcap/$b-present" command -v "$b" done # ------------------------------------------------ pure userspace: text + docs -- # cap_to_text over a raw mask -- no syscall on this path at all. check_out libcap/decode-low "0x0000000000000003=cap_chown,cap_dac_override" \ capsh --decode=0x3 check_out libcap/decode-high "0x0000000000002000=cap_net_raw" capsh --decode=0x2000 # cap_from_text rejects an unknown name in the library, before capsh consults # the kernel at all -- so this half of --supports is userspace. The other half # (a name the library KNOWS) is not: it goes on to CAP_IS_SUPPORTED, i.e. # cap_get_bound, i.e. prctl -- so that case lives in the prctl block below. check_err libcap/supports-unknown "not recognized by library" capsh --supports=cap_bogus # capshdoc -- the compiled-in explanation table. check_out libcap/explain-header "cap_net_raw (13)" capsh --explain=cap_net_raw check_out libcap/explain-body "RAW sockets" capsh --explain=cap_net_raw # --------------------------------------------------- option grammar + status -- # Three binaries, three different conventions -- pinned deliberately, because a # port that silently changed one of them would still look like it "runs". check libcap/capsh-bare capsh # bare capsh exits 0 check_err libcap/capsh-bad-option "usage:" capsh --nonsense check_rc libcap/getcap-usage-rc 1 getcap # bare getcap exits 1 check_out libcap/getcap-help "displays the capabilities on the queried file(s)" getcap -h check_rc libcap/setcap-usage-rc 1 setcap check_out libcap/setcap-help "must be a regular (non-symlink) file" setcap -h check_rc libcap/getpcaps-usage-rc 1 getpcaps check_err libcap/getpcaps-badpid "Cannot parse pid" getpcaps notanumber # cap_from_text rejects the text before setcap reaches any syscall, so this is a # parser assertion and stays green on a kernel with no capability store at all. check_err libcap/setcap-bad-text "fatal error" setcap bogus_cap+ep "$T/f" # getcap's own path diagnostics come from lstat(), not from the capability # store, so they survive the missing xattr syscalls. check_out libcap/getcap-missing-file "No such file or directory" \ getcap /nonexistent-libcap-probe check_out libcap/getcap-not-regular "(Not a regular file)" getcap -v "$T/sub" # --------------------------------------------- capget(2): process credentials -- # capsh --print is captured ONCE and every derived assertion reads this one # capture, so the cases cannot disagree about which run they describe. PR=$(capsh --print 2>&1); PR_RC=$? check_eq libcap/print-rc 0 "$PR_RC" case "$PR" in *"Current: "*) bst_ok libcap/print-current ;; *) bst_notok libcap/print-current "$(_bst_1line "$PR")" ;; esac case "$PR" in *"uid=0"*) bst_ok libcap/print-uid ;; *) bst_notok libcap/print-uid "$(_bst_1line "$PR")" ;; esac # The content of the permitted set, not merely the shape of the line: the # browser runtime runs as root, so cap_chown must be permitted. This is the one # assertion that proves capget returned real data rather than an empty set. check libcap/has-p-chown capsh --has-p=cap_chown # getpcaps takes the OTHER capget path -- an explicit pid through # find_task_by_vpid, rather than pid=0/current. check_out libcap/getpcaps-pid1 "1: " getpcaps 1 check_out libcap/getpcaps-verbose "Capabilities for '1':" getpcaps --verbose 1 check_out libcap/getpcaps-self "$$: " getpcaps $$ # ------------------------------------------------------------------ prctl(2) -- # prctl has no entry in the wasm32 syscall table, so everything capsh reads # through it is unavailable. # # These assert the CONTENT of capsh --print's lines rather than its exit status, # because when prctl alone is missing capsh degrades GRACEFULLY -- it prints # "Bounding set = " and still exits 0, so a status check would # report green on exactly the kernel the case exists to probe. # # But capsh --print calls cap_get_proc() FIRST and exits(1) if that fails, so # while capget is also broken these three lines are never printed at all. A # plain substring test cannot tell "prctl said unsupported" from "capsh died # before reaching the line", and the two clear on DIFFERENT fixes -- so the # helper reports a third value, `absent`, and the failure detail names which # gap is actually in play instead of silently blaming prctl. prline() { printf '%s\n' "$PR" | awk -v p="$1" \ 'index($0,p)==1 { print ($0 ~ /unsupported/) ? "unsupported" : "readable"; f=1; exit } END { if (!f) print "absent" }' } check_eq libcap/bounding-set readable "$(prline 'Bounding set')" check_eq libcap/ambient-set readable "$(prline 'Ambient set')" # capsh prints "[Securebits ABI not supported]" -- no colon -- when # prctl(PR_GET_SECUREBITS) fails, so the colon is the discriminator. check_eq libcap/securebits readable "$(prline 'Securebits')" # The two cases below reach prctl WITHOUT going through capsh --print's # cap_get_proc, so they isolate the prctl gap on its own: # --keep is a bare prctl(PR_SET_KEEPCAPS), and PR_SET_KEEPCAPS is # unprivileged, so nothing about the caller's credentials is involved; check libcap/keepcaps capsh --keep=1 # --supports= on a name the library knows goes to CAP_IS_SUPPORTED, which is # cap_get_bound, which is prctl(PR_CAPBSET_READ). check libcap/supports-known capsh --supports=cap_chown # PR_CAPBSET_DROP, on top of a capset() that IS in the syscall table. check libcap/drop-bset capsh --drop=cap_chown --print # -------------------------------------------------- *xattr: file capabilities -- # The store half -- the security.capability attribute setcap writes and getcap # reads. # # The two tools are NOT equally direct probes of it, and the difference decides # how a red here should be read. setcap calls cap_get_proc() to raise # CAP_SETFCAP before it ever touches the store, so while capget is broken setcap # fails at the PRIVILEGE step ("unable to get process capabilities") and never # reaches setxattr -- its reds are capget's, not the store's. getcap goes # straight to cap_get_file(), so it is the only capget-independent probe of the # xattr syscalls in this suite and its reds are genuinely #27's. check libcap/setcap-set setcap cap_net_raw+ep "$T/f" # Read back through STDOUT ONLY: getcap's failure line repeats the filename and # would satisfy a filename needle on precisely the broken kernel this probes. gc=$(getcap "$T/f" 2>/dev/null) case "$gc" in *cap_net_raw*) bst_ok libcap/getcap-readback ;; *) bst_notok libcap/getcap-readback "stdout:'$(_bst_1line "$gc")'" ;; esac gr=$(getcap -r "$T" 2>/dev/null) case "$gr" in *cap_net_raw*) bst_ok libcap/getcap-recursive ;; *) bst_notok libcap/getcap-recursive "stdout:'$(_bst_1line "$gr")'" ;; esac check libcap/setcap-verify setcap -v cap_net_raw+ep "$T/f" check libcap/setcap-remove setcap -r "$T/f" # A file with no capabilities: getcap -v prints the bare filename on stdout and # nothing else. Where the store is unreadable stdout is EMPTY and the message # goes to stderr, so comparing the whole of stdout separates the two -- an # exit-status or substring check cannot. gv=$(getcap -v "$T/sub/g" 2>/dev/null) check_eq libcap/getcap-verbose-clean "$T/sub/g" "$gv"