. "$BST_LIB"
# vlock 2.2.3 (meta-oe recipes-extended) -- the console locker: /usr/bin/vlock,
# a POSIX-sh wrapper that parses the CLI and execs the setuid /usr/sbin/vlock-main,
# which takes the terminal and holds it until a password verifies. Four facts
# shape every case below.
#
# 1. IT MUST NEVER BE RUN ON THE CONSOLE THIS SUITE REPORTS ON. vlock-main
# disables ECHO and ISIG and loops until authentication succeeds; INT, QUIT
# and TSTP are ignored by design. Started here with an inherited stdin it
# would swallow the runner. So every case either redirects stdin from
# /dev/null (which is also what makes the diagnostic paths reachable) or runs
# on a pty of expect's, and none of them touches the real console.
#
# 2. THE GUARD IS vlock-main, NOT vlock. busybox has a vlock applet of its own
# and owns /usr/bin/vlock until this package's update-alternatives entry
# (priority 60 against busybox's 50) takes the name, so `command -v vlock`
# succeeds on an image where this port never installed. /usr/sbin/vlock-main
# exists only if it did.
#
# 3. IT IS BUILT WITHOUT PLUGINS, because the five upstream modules are
# dlopen()ed shared objects and this arm is static-only. That is observable
# from outside: the version string says so, --help stops advertising the
# plugin flags, no module directory is installed, and asking for a plugin is
# a clean error. `all` is the exception -- vlock-main emulates that one
# natively -- which is why -a fails on the VT_GETMODE ioctl rather than on
# plugin support.
#
# 4. THIS IMAGE SHIPS NO /etc/shadow, so out of the box every password is
# rejected and the lock cannot be lifted (base-passwd gives root the
# disabled-password field `*`). The pty cases below therefore write a shadow
# entry of their own, exercise the real lock/unlock cycle against it, and put
# the image back as they found it. The hash is sha512-crypt, computed off
# target, which is also what proves libxcrypt is doing the work: glibc 2.43
# has no crypt() at all.
require vlock-main vlock/present
D=/tmp/.bst-vlock
rm -rf "$D"; mkdir -p "$D"
# --- identity and inventory ---------------------------------------------------
# Fact 2: the alternatives link must have moved off busybox. update-alternatives
# renames this package's own file to ., so that is what the link
# points at once priority 60 wins.
vl=$(readlink /usr/bin/vlock 2>/dev/null)
case "$vl" in
*vlock.vlock) bst_ok vlock/resolves-to-port ;;
*busybox*) bst_notok vlock/resolves-to-port "/usr/bin/vlock -> $vl (busybox still owns the name; the priority-60 alternative did not take)" ;;
"") bst_notok vlock/resolves-to-port "/usr/bin/vlock is not a symlink -- no alternatives entry at all" ;;
*) bst_notok vlock/resolves-to-port "/usr/bin/vlock -> $vl (expected .../vlock.vlock)" ;;
esac
# vlock-main is installed setuid root (mode 4711) because upstream expects an
# unprivileged caller reading a shadow file only root can read. Whether that bit
# survives as far as the target is not this port's to decide: stage-machine.sh
# unpacks the rootfs tarball with a plain unprivileged 'tar -xf', and GNU tar
# restores setuid/setgid only when it is root, so the whole image arrives with
# them cleared. /bin/busybox.suid is the control -- it is 4755 in the tarball
# too -- so the two cases are told apart rather than guessed at, and this case
# becomes a real assertion again the day staging preserves the bits.
if [ -u /usr/sbin/vlock-main ]; then
bst_ok vlock/main-setuid
elif [ -e /bin/busybox.suid ] && [ ! -u /bin/busybox.suid ]; then
bst_skip vlock/main-setuid "image-wide, not a vlock packaging fault: the .deb ships /usr/sbin/vlock-main 4711 and the rootfs tarball still carries it, but stage-machine.sh unpacks that tarball with an unprivileged 'tar -xf', which does not restore setuid bits; /bin/busybox.suid has lost its own identically. Nothing depends on it here -- this kernel wires no setuid/setgid and root is the only user"
else
bst_notok vlock/main-setuid "/usr/sbin/vlock-main is not setuid while /bin/busybox.suid kept its bit, so this is a vlock packaging fault rather than the image-wide staging strip"
fi
# Fact 3, at install time: --disable-plugins drops install-plugins from the
# install target, so neither the module nor the script directory is created.
check vlock/no-module-dir test ! -d /usr/lib/vlock
# --- the CLI, all of it off the wrapper, none of it needing a terminal --------
# The wrapper writes help and version to stderr and exits 0.
check_out vlock/version-string "vlock version 2.2.3" vlock -v
check_out vlock/version-no-plugins "(no plugin support)" vlock -v
# --help lists what this build can actually do. -c and -a survive; -n, -s and -t
# are printed only by a plugin build, so their absence is the assertion.
help=$(vlock --help 2>&1)
case "$help" in *"-c or --current"*) hc=1 ;; *) hc=0 ;; esac
case "$help" in *"--disable-sysrq"*) hs=1 ;; *) hs=0 ;; esac
case "$help" in *"--timeout"*) ht=1 ;; *) ht=0 ;; esac
check_eq vlock/help-current "1" "$hc"
if [ "$hs" -eq 0 ] && [ "$ht" -eq 0 ]; then bst_ok vlock/help-no-plugin-flags
else bst_notok vlock/help-no-plugin-flags "plugin-only flags advertised by a no-plugin build (sysrq=$hs timeout=$ht)"; fi
# Asking for a plugin anyway. The wrapper still translates -n into the plugin
# name and execs vlock-main with it; vlock-main is what refuses, and it refuses
# before it ever looks at the terminal.
out=$(vlock -n &1); rc=$?
case "$out" in *"plugin support disabled"*) hit=1 ;; *) hit=0 ;; esac
if [ "$rc" -eq 1 ] && [ "$hit" -eq 1 ]; then bst_ok vlock/plugin-refused
else bst_notok vlock/plugin-refused "rc=$rc (want 1) msg=$hit: $(_bst_1line "$out")"; fi
# -a is the one plugin name a no-plugin build accepts (fact 3). It gets as far
# as the VT_GETMODE ioctl and fails there because /dev/null is not a virtual
# console -- which is upstream's own diagnostic for this, not a wasm32 effect.
out=$(vlock -a &1); rc=$?
case "$out" in *"not a virtual console"*) hit=1 ;; *) hit=0 ;; esac
if [ "$rc" -eq 1 ] && [ "$hit" -eq 1 ]; then bst_ok vlock/all-needs-a-vc
else bst_notok vlock/all-needs-a-vc "rc=$rc (want 1) msg=$hit: $(_bst_1line "$out")"; fi
# No plugin at all, so the isatty(0) guard in main() is the first thing to fire.
out=$(vlock &1); rc=$?
case "$out" in *"stdin is not a terminal"*) hit=1 ;; *) hit=0 ;; esac
if [ "$rc" -eq 1 ] && [ "$hit" -eq 1 ]; then bst_ok vlock/needs-a-terminal
else bst_notok vlock/needs-a-terminal "rc=$rc (want 1) msg=$hit: $(_bst_1line "$out")"; fi
# --- the lock itself, on a pty, against a shadow entry of our own -------------
# Fact 4. sha512-crypt hash of "bstlock", salt "bstvlock01". Written, used, and
# removed again inside this block, so the image is left exactly as found -- an
# image that ships its own /etc/shadow keeps it (saved and restored), and one
# that does not (the stock case) ends with no such file.
PW=bstlock
HASH='$6$bstvlock01$820uxuaDGFPlQc3jGMF1BiMaXY.Jhuuu2xI0GhUIDEUdtTLfgnLBvcb4OMzjlHU0PnPOCrD6Dw0N6JdHHFStz/'
had=0
if [ -e /etc/shadow ]; then had=1; cp -p /etc/shadow "$D/shadow.orig" 2>/dev/null || had=2; fi
pty_skip() {
for c in pty-banner pty-enter-hint pty-password-prompt pty-reject pty-relock \
pty-unlock-eof pty-unlock-status; do bst_skip "vlock/$c" "$1"; done
}
if ! command -v expect >/dev/null 2>&1; then
pty_skip "expect not installed; vlock refuses any stdin that is not a terminal"
elif [ "$had" -eq 2 ]; then
pty_skip "this image ships an /etc/shadow that could not be backed up; refusing to overwrite it"
elif ! printf 'root:%s:19000:0:99999:7:::\n' "$HASH" > /etc/shadow 2>/dev/null; then
pty_skip "/etc/shadow is not writable, so there is no password for vlock to verify against"
else
chmod 0600 /etc/shadow 2>/dev/null
# USER is what get_username() reads first when euid is 0, and the runner's
# environment does not necessarily carry it. Pinning it makes the prompt and
# the shadow lookup agree with the entry just written, rather than depending
# on how this suite was started.
OUT=$(USER=root expect -f "$BST_DATA/selftest_vlock.exp" "$PW" 2>&1)
pty_case() {
_v=$(printf '%s\n' "$OUT" | awk -v k="$2" '$1==k{print $2}')
if [ "$_v" = "1" ]; then bst_ok "$1"
elif [ -z "$_v" ]; then bst_notok "$1" "payload printed no $2 line: $(_bst_1line "$OUT")"
else bst_notok "$1" "$2=0: $(_bst_1line "$OUT")"; fi
}
pty_case vlock/pty-banner R1-banner
pty_case vlock/pty-enter-hint R1-enter-hint
pty_case vlock/pty-password-prompt R2-password-prompt
pty_case vlock/pty-reject R3-reject
pty_case vlock/pty-relock R4-relock
pty_case vlock/pty-unlock-eof R5-unlock-eof
pty_case vlock/pty-unlock-status R5-unlock-status
if [ "$had" -eq 1 ]; then cp -p "$D/shadow.orig" /etc/shadow; else rm -f /etc/shadow; fi
fi
# --- what this image does not carry -------------------------------------------
bst_skip vlock/man-pages "vlock.1, vlock-main.8 and vlock-plugins.5 are packaged into vlock-doc, and this image installs no -doc packages"
rm -rf "$D"