. "$BST_LIB" # shadow 4.14.2 -- the account tools (useradd/usermod/userdel, groupadd/groupmod/ # groupdel, chage, passwd, chpasswd, su, login, newgrp). Two things are worth # knowing before reading the cases. # # THE DATABASE THE TOOLS EDIT IS A PRIVATE COPY UNDER /tmp, reached with shadow's # own -P/--prefix rather than /etc. Three reasons, in increasing order of force. # The image's rootfs is read-only on an anonymous browser session, so a suite # that wrote /etc/passwd would be reporting which session it was run in rather # than anything about the port. A suite that DID succeed would leave the image's # account database permanently altered for every suite that runs after it -- # 06-base-passwd asserts the exact content of /etc/passwd and /etc/group, and it # runs two files later. And --prefix is the flag upstream provides for operating # on a database that is not the running system's, so using it exercises a # supported path instead of a contrivance. # # It is not the weaker test it might look like. useradd, chpasswd and passwd # reach the prefixed files through the same commonio lock-copy-rename code that # serves /etc, and crypt(3) -- the one thing that kept this package from building # here at all -- runs identically either way. The shadow/chpasswd-sha512 case # below is what proves it: a $6$ hash in the database can only have come from a # crypt() that linked and ran. # # WHAT THIS SUITE DELIBERATELY DOES NOT COVER, AND WHY THAT IS NOT A HOLE. su, # login and newgrp exist to CHANGE credentials, and this kernel wires none of the # setters (brintos/linux#19: setuid/setgid/setgroups/setres*/setfs* are NULL rows # in the wasm32 syscall table and return ENOSYS while the getters answer). No # argument to those three can work around that. The last group asserts the gap # directly and INVERTED -- green while it exists, RED the day #19 lands -- which # is the signal to come back and write the coverage that is unreachable today. require useradd shadow/present # ------------------------------------------------------ what got installed ---- # useradd/groupadd have no busybox applet, so their bare presence is this port. # The four below DO have one, and shadow's update-alternatives priority (200, # against busybox's 50 and coreutils' 100) is what puts the real tool on PATH -- # which is the stated point of the ticket, so it is asserted rather than assumed. # update-alternatives installs the real binary as .shadow and points the # PATH name at it, so the link's target names the winner unambiguously; comparing # --help text would be guessing at two tools' phrasing instead. for t in useradd usermod userdel groupadd groupmod groupdel chage gpasswd chpasswd; do if command -v "$t" >/dev/null 2>&1; then bst_ok "shadow/have-$t" else bst_notok "shadow/have-$t" "$t is not on PATH; shadow's main package should install it"; fi done for a in su login newgrp groups; do p=$(command -v "$a" 2>/dev/null) if [ -z "$p" ]; then bst_notok "shadow/alt-$a" "$a is not on PATH at all" else tgt=$(readlink "$p" 2>/dev/null) case "$tgt" in *shadow*) bst_ok "shadow/alt-$a" ;; "") bst_notok "shadow/alt-$a" "$p is not a symlink, so update-alternatives did not place it" ;; *) bst_notok "shadow/alt-$a" "$p -> $tgt; shadow's priority 200 should have won this name" ;; esac fi done # login.defs is shadow-base's, and it is not decoration: ENCRYPT_METHOD in it is # what selects SHA-512 for the chpasswd case further down. check shadow/login-defs test -f /etc/login.defs check_out shadow/encrypt-method "SHA512" sh -c 'grep -E "^ENCRYPT_METHOD" /etc/login.defs' # ------------------------------------------------- the prefixed database ------ D=${TMPDIR:-/tmp}/bst-shadow.$$ rm -rf "$D" if ! mkdir -p "$D/etc" 2>/dev/null; then bst_notok shadow/prefix-stage "cannot create $D/etc -- no writable tmp, so no case below could run" exit 0 fi # Seeded by hand rather than copied from /etc, so every assertion below is about # what the tools did and not about what this image happens to ship. login.defs IS # copied, because it carries the UID_MIN/GID_MIN ranges and the hashing method the # tools are supposed to honour, and a suite that supplied its own would be # grading its own answer key. printf 'root:x:0:0:root:/root:/bin/sh\n' > "$D/etc/passwd" printf 'root:x:0:\n' > "$D/etc/group" printf 'root:*:19000:0:99999:7:::\n' > "$D/etc/shadow" printf 'root:*::\n' > "$D/etc/gshadow" : > "$D/etc/subuid" : > "$D/etc/subgid" cp /etc/login.defs "$D/etc/login.defs" 2>/dev/null mkdir -p "$D/etc/default" 2>/dev/null cp /etc/default/useradd "$D/etc/default/useradd" 2>/dev/null mkdir -p "$D/home" 2>/dev/null P="-P $D" # groups first: useradd -g needs one to exist. check shadow/groupadd groupadd $P bstgrp check_out shadow/groupadd-entry "bstgrp:" sh -c "grep '^bstgrp:' $D/etc/group" check shadow/groupadd-gid groupadd $P -g 4242 bstgid check_out shadow/groupadd-gid-val ":4242:" sh -c "grep '^bstgid:' $D/etc/group" # gshadow is a second file groupadd has to keep in step with /etc/group; a port # that wrote only one of them would still pass the case above. check_out shadow/groupadd-gshadow "bstgrp:" sh -c "grep '^bstgrp:' $D/etc/gshadow" check shadow/useradd useradd $P -M -u 4242 -g bstgrp -c "BST test user" bstuser check_out shadow/useradd-passwd "bstuser:" sh -c "grep '^bstuser:' $D/etc/passwd" check_out shadow/useradd-uid ":4242:" sh -c "grep '^bstuser:' $D/etc/passwd" check_out shadow/useradd-gecos "BST test user" sh -c "grep '^bstuser:' $D/etc/passwd" # The point of shadow over a plain passwd file: the hash lives in a second, # root-only database, and useradd has to create that row too. check_out shadow/useradd-shadow "bstuser:" sh -c "grep '^bstuser:' $D/etc/shadow" check shadow/usermod-comment usermod $P -c "BST renamed" bstuser check_out shadow/usermod-gecos "BST renamed" sh -c "grep '^bstuser:' $D/etc/passwd" check shadow/usermod-shell usermod $P -s /bin/sh bstuser check_out shadow/usermod-shell-val "/bin/sh" sh -c "grep '^bstuser:' $D/etc/passwd" check shadow/groupmod-rename groupmod $P -n bstgrp2 bstgrp check_out shadow/groupmod-newname "bstgrp2:" sh -c "grep '^bstgrp2:' $D/etc/group" # chage reads the aging fields useradd wrote into the shadow row. Run with -l it # is also the only read-only tool here, so a failure isolates parsing from # writing. check_out shadow/chage-list "Last password change" chage $P -l bstuser # --------------------------------------------------------------- crypt(3) ---- # THE CASE THIS PORT EXISTS FOR. shadow's configure dies "crypt() not found" on # this arm unless libxcrypt is supplied by name and its archive named by absolute # path (the driver drops -lcrypt as "in the glibc pool" -- brintos/hwjs-cc#33). # Both of those are build-time claims. This is the runtime one: chpasswd hashes # through crypt(), and login.defs selects SHA-512, so a $6$ field in the database # is proof the symbol linked AND executed. A stub or a phantom import could not # produce it. if printf 'bstuser:BstPw0rd!\n' | chpasswd $P 2>/dev/null; then hash=$(sed -n 's/^bstuser:\([^:]*\):.*/\1/p' "$D/etc/shadow") case "$hash" in '$6$'*) bst_ok shadow/chpasswd-sha512 ;; '$'*) bst_notok shadow/chpasswd-sha512 "hashed, but not with the SHA-512 method login.defs selects: $(_bst_1line "$hash")" ;; '') bst_notok shadow/chpasswd-sha512 "chpasswd exited 0 but left the password field empty" ;; *) bst_notok shadow/chpasswd-sha512 "password field is not a crypt(3) hash: $(_bst_1line "$hash")" ;; esac else bst_notok shadow/chpasswd-sha512 "chpasswd failed; crypt(3) is the thing to look at first" fi # passwd -S reports the status letter the hash implies, so it is a second reader # of what chpasswd just wrote rather than a repeat of the same assertion. check_out shadow/passwd-status-usable " P " passwd $P -S bstuser # passwd -l/-u are INVERTED, for the same reason su and newgrp are, and it is a # different reason from every other case above. Locking rewrites the shadow file # through a path that calls setuid(0) FOR REAL (src/passwd.c) -- not the no-op # drop the --prefix patch tolerates, which passes the ids the process already # has. So this is brintos/linux#19 proper. Asserted green while the gap is open # and RED the day it lands, which is the signal to delete this case and write # the real lock/unlock coverage -- including the two readers it costs us, the # " L " status letter and the "bstuser:!" mark in the database, which cannot be # asserted while no lock can be taken. out=$(passwd $P -l bstuser 2>&1 &1 &1); rc=$? if [ "$rc" -eq 0 ]; then bst_notok shadow/newgrp-enosys-inverted "newgrp succeeded -- brintos/linux#19 has landed; delete this case and write real newgrp coverage" elif bst_execfail "$out"; then bst_unrun shadow/newgrp-enosys-inverted "$out" else case "$out" in *"Function not implemented"*) bst_ok shadow/newgrp-enosys-inverted ;; *) bst_notok shadow/newgrp-enosys-inverted "newgrp failed for some reason OTHER than the credential-setter gap: $(_bst_1line "$out")" ;; esac fi # login is the third, and it is SKIPPED rather than inverted. It needs a # controlling terminal before it reaches any credential call, so on this suite's # stdin it would fail for a reason that has nothing to do with #19 -- and a case # that can go red for two unrelated causes attributes neither. 88-mingetty.test # drives the real login flow over a pty and is where that coverage belongs. bst_skip shadow/login-flow "login needs a controlling terminal before it reaches a credential call, so this suite cannot separate that from brintos/linux#19; the pty login flow is 88-mingetty.test's"