. "$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"