diff --git a/build/guest-tests/camera-check.sh b/build/guest-tests/camera-check.sh index 3b1969bd..b001ec36 100755 --- a/build/guest-tests/camera-check.sh +++ b/build/guest-tests/camera-check.sh @@ -3,7 +3,13 @@ # guest by tools/image/zones-test.sh. QEMU has no camera, so vivid, which only this loads, stands # in for one. # -# camera-check.sh USER USER is the session's user, who grants as the chrome does +# camera-check.sh USER USER is the session's user, who grants as the chrome does +# camera-check.sh USER --hardware the machine's own USB camera instead, vivid never loaded +# +# --hardware runs as root at a console (Ctrl+Alt+F2) of the installed laptop, with USER logged in, +# the screen unlocked and nothing using the camera since boot, and within the ten minutes before +# the idle lock, which takes the camera back. It checks what QEMU cannot: uvcvideo's capture, a +# program that keeps the camera past its grant holding the driver, and the camera taken away. # # Output: "CT PASS|FAIL name - detail"; a check that cannot run fails. set -u @@ -13,6 +19,7 @@ KD=/usr/bin/kryptikd LOG=/var/log/kryptik/camera-check mkdir -p "$LOG" USER_NAME="${1:-tester}" +HW=0; [[ "${2:-}" == --hardware ]] && HW=1 pass() { echo "CT PASS $1${2:+ - $2}"; } fail() { echo "CT FAIL $1${2:+ - $2}"; } oneline() { tr '\n' ' ' | cut -c1-300; } @@ -34,10 +41,11 @@ wait_for() { # wait_for FILE REGEX SECONDS done return 1 } -# A program in a zone: opens /dev/video0, asks the driver's name, reads a frame. With an -# argument it keeps reading every half second until a read fails; with WAIT it first waits for -# the camera to be given. -PROBE='import fcntl, os, sys, time +# A program in a zone: opens /dev/video0, asks the driver's name and the formats it offers, and +# takes a frame by mmap streaming, as a browser does (uvcvideo has no read()). With HOLD it takes +# one every half second until that fails, and with KEEP then holds the descriptor two minutes +# more; with WAIT it first waits for the camera to be given. +PROBE='import fcntl, mmap, os, select, struct, sys, time wait = "wait" in sys.argv for _ in range(60 if wait else 1): try: @@ -52,17 +60,59 @@ else: cap = bytearray(104) fcntl.ioctl(fd, 0x80685600, cap) print("DRIVER", bytes(cap[:16]).rstrip(b"\0").decode(), flush=True) -print("FRAME", len(os.read(fd, 8 << 20)), flush=True) +fmts = [] +for i in range(64): + d = bytearray(64) + struct.pack_into("=II", d, 0, i, 1) + try: + fcntl.ioctl(fd, 0xc0405602, d) + except OSError: + break + fmts.append(bytes(d[44:48]).decode("ascii", "replace")) +print("FORMATS", " ".join(fmts), flush=True) +def buf(i=0): + b = bytearray(88) + struct.pack_into("=II", b, 0, i, 1) + struct.pack_into("=I", b, 60, 1) + return b +req = bytearray(20) +struct.pack_into("=III", req, 0, 4, 1, 1) +fcntl.ioctl(fd, 0xc0145608, req) +maps = [] +for i in range(struct.unpack_from("=I", req, 0)[0]): + b = buf(i) + fcntl.ioctl(fd, 0xc0585609, b) + off, size = struct.unpack_from("=I", b, 64)[0], struct.unpack_from("=I", b, 72)[0] + maps.append(mmap.mmap(fd, size, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE, offset=off)) + fcntl.ioctl(fd, 0xc058560f, b) +fcntl.ioctl(fd, 0x40045612, struct.pack("=i", 1)) +def frame(secs): + end = time.monotonic() + secs + while time.monotonic() < end: + if not select.select([fd], [], [], max(0.0, end - time.monotonic()))[0]: + break + b = buf() + fcntl.ioctl(fd, 0xc0585611, b) + used, flags = struct.unpack_from("=II", b, 8) + fcntl.ioctl(fd, 0xc058560f, b) + if used and not flags & 0x40: + return used + return 0 +print("FRAME", frame(5), flush=True) if "hold" in sys.argv: print("HELD", flush=True) for _ in range(240): time.sleep(0.5) try: - os.read(fd, 8 << 20) + frame(0.5) except OSError as e: - print("READ-FAILED", e.errno, flush=True) + print("DQBUF-FAILED", e.errno, flush=True) + if "keep" in sys.argv: + time.sleep(120) sys.exit(0) - print("STILL-READS", flush=True)' + print("STILL-STREAMS", flush=True)' +# A video-class USB interface: a camera of the machine's own. +usb_camera() { grep -lsx 0e /sys/bus/usb/devices/*/bInterfaceClass | head -n 1; } [[ "$(id -u)" = 0 ]] || { fail "root" "this must run as root"; exit 1; } untrusted_uid="$(sed -n 's/^uid_base *= *\([0-9]*\).*/\1/p' "$Z/untrusted.toml")" node=/run/kryptik-camera/untrusted/video0 @@ -78,7 +128,21 @@ else fail "camera-not-by-alias" "uvcvideo's video-class aliases: ${claims}; loading by alias: $(oneline <<<"$kept"); loaded: $(loaded uvcvideo && echo yes || echo no)" fi -modprobe vivid n_devs=1 node_types=0x1 2> "$LOG/vivid.err" || { fail "camera-stand-in" "$(oneline < "$LOG/vivid.err")"; exit 0; } +# The camera: the machine's own, met before any grant this boot, or vivid where there is none. +usb="$(usb_camera)" +if [[ "$HW" -eq 1 ]]; then + if [[ -n "$usb" ]] && ! loaded uvcvideo && [[ "$(holder)" == none ]]; then + pass "camera-hardware" "the USB camera at ${usb%/*}, untouched since boot" + else + fail "camera-hardware" "USB camera: ${usb:-none}; uvcvideo $(loaded uvcvideo && echo loaded || echo absent); $(holder)" + exit 0 + fi + DRV=uvcvideo +else + [[ -z "$usb" ]] || { fail "camera-stand-in" "a USB camera is here (${usb%/*}): run with --hardware"; exit 0; } + modprobe vivid n_devs=1 node_types=0x1 2> "$LOG/vivid.err" || { fail "camera-stand-in" "$(oneline < "$LOG/vivid.err")"; exit 0; } + DRV=vivid +fi zrun untrusted -- python3 -c "$PROBE" if grep -q '^OPEN-FAILED 2$' <<<"$ZOUT"; then pass "camera-not-granted" "untrusted finds no camera before a grant"; else fail "camera-not-granted" "rc ${ZRC}: $(oneline <<<"$ZOUT")"; fi @@ -91,40 +155,60 @@ for z in vault net; do fi done -# Granted: the driver loads, and the node, the zone's root's alone, is in the zone's directory. +# Granted: the driver loads, and the node, the zone's root's alone, is in the zone's directory, +# for the camera's own device. said="$(as_user "kryptik-launch --camera-grant untrusted" 2>&1)"; rc=$? st="$(stat -c '%F %u %a' "$node" 2>&1)" -if [[ "$rc" -eq 0 ]] && loaded uvcvideo && [[ "$st" == "character special file ${untrusted_uid} 600" && "$(holder)" == "zone untrusted holds "* ]]; then - pass "camera-granted" "${said}; ${st}" +hn="$(holder | sed -n 's/.*(\(video[0-9]*\))$/\1/p')" +hd="$(basename "$(readlink -f "/sys/class/video4linux/${hn:-none}/device/driver")")" +if [[ "$rc" -eq 0 ]] && loaded uvcvideo && [[ "$st" == "character special file ${untrusted_uid} 600" && "$(holder)" == "zone untrusted holds "* && "$hd" == "$DRV" ]]; then + pass "camera-granted" "${said}; ${st}; ${hn}, driven by ${hd}" else - fail "camera-granted" "rc ${rc}: ${said}; node: ${st}; uvcvideo $(loaded uvcvideo && echo loaded || echo absent); $(holder)" + fail "camera-granted" "rc ${rc}: ${said}; node: ${st}; uvcvideo $(loaded uvcvideo && echo loaded || echo absent); $(holder); ${hn:-no node}'s driver ${hd}" fi +# A picture a browser shows: MJPG or YUYV among the formats, not only an infrared camera's grey. zrun untrusted -- python3 -c "$PROBE" -if grep -q '^DRIVER vivid$' <<<"$ZOUT" && grep -qE '^FRAME [1-9][0-9]*$' <<<"$ZOUT"; then pass "camera-zone-captures" "$(oneline <<<"$ZOUT")"; else fail "camera-zone-captures" "rc ${ZRC}: $(oneline <<<"$ZOUT")"; fi +if grep -q "^DRIVER ${DRV}$" <<<"$ZOUT" && grep -qE '^FORMATS (.* )?(MJPG|YUYV)( |$)' <<<"$ZOUT" && grep -qE '^FRAME [1-9][0-9]*$' <<<"$ZOUT"; then pass "camera-zone-captures" "$(oneline <<<"$ZOUT")"; else fail "camera-zone-captures" "rc ${ZRC}: $(oneline <<<"$ZOUT")"; fi if [[ "$(holder)" == none* && ! -e "$node" ]]; then pass "camera-ends-with-zone" "untrusted stopped, so its grant ended"; else fail "camera-ends-with-zone" "$(holder); node $(ls -l "$node" 2>&1)"; fi -# Another zone finds none while untrusted holds it. +# Another zone finds none while untrusted holds it. personal's passphrase is a file only the +# QEMU run's earlier checks make. as_user "kryptik-launch --camera-grant untrusted" > /dev/null 2>&1 -zrun personal --passphrase-file /root/zt/personal.pass -- python3 -c "$PROBE" -if grep -q '^OPEN-FAILED 2$' <<<"$ZOUT"; then pass "camera-other-zone-none" "personal finds no camera"; else fail "camera-other-zone-none" "rc ${ZRC}: $(oneline <<<"$ZOUT")"; fi +if [[ "$HW" -eq 0 ]]; then + zrun personal --passphrase-file /root/zt/personal.pass -- python3 -c "$PROBE" + if grep -q '^OPEN-FAILED 2$' <<<"$ZOUT"; then pass "camera-other-zone-none" "personal finds no camera"; else fail "camera-other-zone-none" "rc ${ZRC}: $(oneline <<<"$ZOUT")"; fi +fi # k alone: the descriptor a program kept fails, the node goes, and the driver lets go of the -# stand-in and unloads. -"$KD" run untrusted --zones "$Z" --rootfs "$R" -- python3 -c "$PROBE" hold > "$LOG/holder.out" 2>&1 & +# camera. vivid's node pins vivid, so uvcvideo unloads; a USB camera's pins uvcvideo, which stays +# loaded, and no grant is given until that program lets go, as the camera would come back with +# no capture node. +hold=(hold); [[ "$HW" -eq 0 ]] || hold+=(keep) +"$KD" run untrusted --zones "$Z" --rootfs "$R" -- python3 -c "$PROBE" "${hold[@]}" > "$LOG/holder.out" 2>&1 & hp=$! if wait_for "$LOG/holder.out" '^HELD' 60; then said="$(as_user "kryptik-launch --camera-revoke" 2>&1)"; rc=$? - if [[ "$rc" -eq 0 && "$said" == untrusted* && ! -e "$node" ]] && ! loaded uvcvideo && [[ ! -e /sys/bus/platform/drivers/vivid/vivid.0 ]]; then + if [[ "$HW" -eq 0 && "$rc" -eq 0 && "$said" == untrusted* && ! -e "$node" ]] && ! loaded uvcvideo && [[ ! -e /sys/bus/platform/drivers/vivid/vivid.0 ]]; then pass "camera-revoked" "${said}: the node is gone, vivid.0 let go of and uvcvideo unloaded" + elif [[ "$HW" -eq 1 && "$rc" -eq 0 && "$said" == "untrusted stayed" && ! -e "$node" ]] && loaded uvcvideo && [[ "$(holder)" == "none; its driver stayed loaded" ]]; then + pass "camera-revoked" "${said}: the node is gone, and uvcvideo stays, held by the program" else - fail "camera-revoked" "rc ${rc}: ${said}; node $(ls -l "$node" 2>&1); uvcvideo $(loaded uvcvideo && echo loaded || echo absent)" + fail "camera-revoked" "rc ${rc}: ${said}; node $(ls -l "$node" 2>&1); uvcvideo $(loaded uvcvideo && echo loaded || echo absent); $(holder)" + fi + if wait_for "$LOG/holder.out" '^DQBUF-FAILED [0-9]+$' 30; then pass "camera-revoked-kept-descriptor" "$(grep '^DQBUF-FAILED' "$LOG/holder.out")"; else fail "camera-revoked-kept-descriptor" "$(oneline < "$LOG/holder.out")"; fi + if [[ "$HW" -eq 1 ]]; then + said="$(as_user "kryptik-launch --camera-grant untrusted" 2>&1)"; rc=$? + if [[ "$rc" -ne 0 && "$said" == *"still holds the camera"* && ! -e "$node" ]]; then + pass "camera-kept-refuses-grant" "$said" + else + fail "camera-kept-refuses-grant" "rc ${rc}: ${said}; $(holder)" + fi fi - if wait_for "$LOG/holder.out" '^READ-FAILED [0-9]+$' 30; then pass "camera-revoked-kept-descriptor" "$(grep '^READ-FAILED' "$LOG/holder.out")"; else fail "camera-revoked-kept-descriptor" "$(oneline < "$LOG/holder.out")"; fi else fail "camera-revoked" "the program never held the camera: $(oneline < "$LOG/holder.out")" fail "camera-revoked-kept-descriptor" "not run" fi -for _ in $(seq 1 60); do kill -0 "$hp" 2>/dev/null || break; sleep 1; done +[[ "$HW" -eq 1 ]] || for _ in $(seq 1 60); do kill -0 "$hp" 2>/dev/null || break; sleep 1; done kill "$hp" 2>/dev/null; wait "$hp" 2>/dev/null # Granted again, to a zone already running, as to a browser waiting for it: the stand-in is @@ -140,24 +224,50 @@ else fi for _ in $(seq 1 60); do kill -0 "$wp" 2>/dev/null || break; sleep 1; done kill "$wp" 2>/dev/null; wait "$wp" 2>/dev/null -if [[ "$(holder)" == none* ]]; then pass "camera-ends-with-zone-again" "$(holder)"; else fail "camera-ends-with-zone-again" "$(holder)"; fi +# With nothing holding it this time, the driver unloads, and the mark that it stayed is gone. +h="$(holder)" +if { [[ "$HW" -eq 0 && "$h" == none* ]] || [[ "$HW" -eq 1 && "$h" == none ]]; } && ! loaded uvcvideo; then + pass "camera-ends-with-zone-again" "$(holder)" +else + fail "camera-ends-with-zone-again" "$(holder); uvcvideo $(loaded uvcvideo && echo loaded || echo absent)" +fi # Taken away while granted, as a USB camera pulled out: udev's removal of its node ends the grant -# at once, before another camera plugged in could take the node's number. +# at once, before another camera plugged in could take the node's number. A built-in camera is +# taken away by deauthorizing its USB device; authorized again, it comes back loading nothing. said="$(as_user "kryptik-launch --camera-grant untrusted" 2>&1)"; rc=$? ended() { [[ "$(holder)" == none* && ! -e "$node" ]]; } -if [[ "$rc" -eq 0 && -e "$node" ]]; then - modprobe -r vivid > "$LOG/vivid-gone.out" 2>&1 +# The granted node's USB device: its interface's parent. +hn="$(holder | sed -n 's/.*(\(video[0-9]*\))$/\1/p')" +dev="$(readlink -f "/sys/class/video4linux/${hn:-none}/device/..")" +if [[ "$HW" -eq 1 && ! ( -n "$hn" && -w "$dev/authorized" ) ]]; then + fail "camera-gone-ends-grant" "no USB device for ${hn:-the granted node}: $(holder)" + as_user "kryptik-launch --camera-revoke" > /dev/null 2>&1 +elif [[ "$rc" -eq 0 && -e "$node" ]]; then + if [[ "$HW" -eq 1 ]]; then echo 0 > "$dev/authorized"; else modprobe -r vivid; fi > "$LOG/gone.out" 2>&1 ok=no for _ in $(seq 1 15); do ended && { ok=yes; break; }; sleep 1; done + [[ "$HW" -eq 0 ]] || echo 1 > "$dev/authorized" if [[ "$ok" == yes ]]; then - pass "camera-gone-ends-grant" "vivid taken away while untrusted held it: the grant ended and the node went" + pass "camera-gone-ends-grant" "the camera taken away while untrusted held it: the grant ended and the node went" else - fail "camera-gone-ends-grant" "status: $(holder); node $(ls -l "$node" 2>&1); $(oneline < "$LOG/vivid-gone.out")" + fail "camera-gone-ends-grant" "status: $(holder); node $(ls -l "$node" 2>&1); $(oneline < "$LOG/gone.out")" fi else fail "camera-gone-ends-grant" "the grant: rc ${rc}: ${said}" fi +if [[ "$HW" -eq 1 ]]; then + for _ in $(seq 1 10); do [[ -n "$(usb_camera)" ]] && break; sleep 1; done + sleep 3 + if [[ -n "$(usb_camera)" ]] && ! loaded uvcvideo && [[ "$(holder)" == none* ]]; then + pass "camera-back-loads-nothing" "the camera is back at ${dev}, and uvcvideo is not loaded" + else + fail "camera-back-loads-nothing" "USB camera: $(usb_camera); uvcvideo $(loaded uvcvideo && echo loaded || echo absent); $(holder)" + fi + modprobe -r uvcvideo 2>/dev/null + echo "CT END" + exit 0 +fi # Of two cameras, the first that offers a picture a browser reads: vivid.0's node offers only # multi-planar formats, which Firefox does not read, so the grant passes it for vivid.1's, as it diff --git a/build/patches/firefox-153.6.0/0005-v4l2-camera-changes-without-inotify.patch b/build/patches/firefox-153.6.0/0005-v4l2-camera-changes-without-inotify.patch new file mode 100644 index 00000000..f2fcce67 --- /dev/null +++ b/build/patches/firefox-153.6.0/0005-v4l2-camera-changes-without-inotify.patch @@ -0,0 +1,48 @@ +--- a/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.cc ++++ b/third_party/libwebrtc/modules/video_capture/linux/device_info_v4l2.cc +@@ -170,6 +170,33 @@ + + close(_fd_v4l); + close(_fd_dev); ++ } else { ++ // No inotify, as in a sandbox that refuses it: look for /dev/video once a second ++ // instead, and say the devices changed whenever the set of them does. ++ if (_fd_v4l >= 0) { ++ close(_fd_v4l); ++ } ++ if (_fd_dev >= 0) { ++ close(_fd_dev); ++ } ++ uint64_t seen = 0; ++ for (int tick = 0; !_isShutdown; tick++) { ++ if (tick % 10 == 0) { ++ uint64_t now = 0; ++ for (int n = 0; n < 64; n++) { ++ char path[32]; ++ snprintf(path, sizeof(path), "/dev/video%d", n); ++ if (access(path, F_OK) == 0) { ++ now |= uint64_t(1) << n; ++ } ++ } ++ if (tick > 0 && now != seen) { ++ DeviceChange(); ++ } ++ seen = now; ++ } ++ usleep(100 * 1000); ++ } + } + } + #endif +--- a/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.cc ++++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_v4l2.cc +@@ -451,6 +451,9 @@ + } else if (retVal == 0) { + // poll timed out + return true; ++ } else if (rSet.revents & (POLLERR | POLLHUP | POLLNVAL)) { ++ // the device is gone, or its queue failed: polling again would spin ++ return false; + } else if (!(rSet.revents & POLLIN)) { + // not event on camera handle + return true; diff --git a/build/patches/firefox-153.6.0/README.md b/build/patches/firefox-153.6.0/README.md index 900d4361..d068a6d0 100644 --- a/build/patches/firefox-153.6.0/README.md +++ b/build/patches/firefox-153.6.0/README.md @@ -5,7 +5,8 @@ Applied by `s_browser_firefox` in the browser image through `apply_repo_patches` Mozilla builds Firefox with clang; Kryptik builds it with stage 04's GCC 14, as every other program is built. The first three are the changes Gentoo's Firefox -153 patch set carries for a GCC build, each a few lines; the fourth is Kryptik's: +153 patch set carries for a GCC build, each a few lines; the fourth and fifth +are Kryptik's: - `0001`: `features.h` leaves the list of system headers Firefox wraps. Wrapped, glibc's `_FORTIFY_SOURCE` checks do not take effect, which stage 06's audit @@ -21,6 +22,17 @@ every other program is built. The first three are the changes Gentoo's Firefox build with neither gets no remote at all, as on a platform without one: a second `firefox` with the same profile is not handed to the first, and says that Firefox is already running. +- `0005`: the camera in a zone, which refuses inotify (a watch on the `/usr` + it shares with zone 0 would see every program started). WebRTC's V4L2 code + learned of cameras only by inotify, so a camera granted after a page loaded + was never listed and pages got no `devicechange`. Without inotify it now + looks for `/dev/video` once a second and reports a change, and the + zone's `/dev/video0` link, dangling with no grant, reads as absent. Its + capture thread also polled a camera that was taken away in a busy loop, + since the node answers `POLLERR` and never `POLLIN`; it now stops there. + Told the camera is gone, Firefox can end the track and close the node, + which the next grant waits for (`camera.rs`); whether it does is for the + tests on physical hardware. The flag stage 04 adds that this build cannot take, `-fno-delete-null-pointer-checks`, is dropped for Firefox alone in `build/config/hardening-exceptions.txt`. diff --git a/build/patches/firefox-153.6.0/SHA256SUMS b/build/patches/firefox-153.6.0/SHA256SUMS index 915dce98..e863c245 100644 --- a/build/patches/firefox-153.6.0/SHA256SUMS +++ b/build/patches/firefox-153.6.0/SHA256SUMS @@ -2,3 +2,4 @@ b9bc94714ef801b3450dddfba1a35a7139ed48c9a958378dbda9016a4403ee14 0002-ffvpx-gcc-incompatible-pointer-types.patch 692345659a387cda546bf9aa2a8b19cefce60f3545634607e6a35d526145fd90 0003-cstdint-includes.patch d68bb973e17677582d8988053518549d8261fe20934b732c12a4138bf9ec8dfb 0004-remote-needs-dbus-or-x11.patch +a844c745485cbe462bfeab8dbce837594a8f883ee4de5164ed1e5a872aca52db 0005-v4l2-camera-changes-without-inotify.patch diff --git a/docs/design/camera.md b/docs/design/camera.md index efea27ae..31a4c86a 100644 --- a/docs/design/camera.md +++ b/docs/design/camera.md @@ -101,14 +101,16 @@ loaded only for a grant. QEMU has no USB camera. The kernel's `vivid`, a test driver that makes V4L2 devices with no hardware (as `test_power` makes a battery), stands in, built -as a module that the zones and desktop suites alone load; `uvcvideo` -itself waits for the tests on physical hardware. +as a module that the zones and desktop suites alone load. The probe takes +its frames by mmap streaming, as a browser does, since `uvcvideo` has no +`read()`. - with no grant, no zone's `/dev/video0` opens, and loading by a camera's alias, as eudev does when one is plugged in, loads nothing (`modprobe -b` with a video-class alias); -- granted to `personal`, a probe there captures a frame from the stand-in; - `work` finds no camera; the chrome's window names `personal`; +- granted to `personal`, a probe there captures a frame from the stand-in, + which offers MJPEG or YUYV; `work` finds no camera; the chrome's window + names `personal`; - taken away while granted (`vivid` unloaded), the grant ends and the node goes; - of two stand-ins, the grant passes over the lower numbered, whose node @@ -118,6 +120,17 @@ itself waits for the tests on physical hardware. descriptor fails at its next read, and the node is gone; - a zone whose file refuses the camera is never offered it. +On a laptop, `camera-check.sh USER --hardware` +(`/usr/lib/kryptik/guest-tests`), run as root at a console with the user +logged in and nothing using the camera since boot, checks the same against +the laptop's own USB camera, with `vivid` never loaded, and what only +`uvcvideo` shows: a program that keeps its descriptor past `k` holds the +driver, which stays loaded, and no grant is given until it lets go; the +next grant after that captures again and unloads the driver at its end; and +the camera taken away (its USB device deauthorized, which takes an infrared +camera in the same module with it for those seconds) ends the grant, and +loads nothing when it comes back. + ## Not covered - MIPI cameras behind Intel's IPU6 and AMD's image processor (below), and diff --git a/docs/hardware.md b/docs/hardware.md index dbba3d8f..36db6540 100644 --- a/docs/hardware.md +++ b/docs/hardware.md @@ -106,6 +106,7 @@ let the row's note name what did not work: | closed on its dock, then the dock unplugged | it sleeps within seconds, as a lid closed undocked does | | the state passphrase at boot, typed on the built-in keyboard | it unlocks, whether the keyboard is on PS/2 or on I2C behind Intel's LPSS or AMD's DesignWare controller; the row's note names which (`/proc/bus/input/devices`). One that types nothing there is among those status.md lists, and a plain USB keyboard is the way through | | the listing's `camera usb` line for the built-in camera, taken before any `k` | it reads `port fixed control auto runtime suspended`; a port that reads `unknown` keeps the camera awake, and the row's note gives the line | +| at a console (Ctrl+Alt+F2) as root, before any `k`: `bash /usr/lib/kryptik/guest-tests/camera-check.sh USER --hardware`, USER the one logged in | every line reads `CT PASS`; a `CT FAIL` line goes in the row's note whole | | `k` and a zone's number in the menu, then a program there that records video | a USB camera records; one the listing's `camera` line puts behind an IPU does not, as 1.0 drives none | | a USB camera given to a zone with `k` and its number, then pulled out | the grant ends at once, and the menu's Camera line says no zone has it; plugged back in, it waits for `k` again | | Firefox in `untrusted`: Alt+p, its number and `b` | a window opens; where the listing's `shstk` line says yes, this is the only test of Firefox starting with shadow stacks on, and a failure names SHSTK in what it prints | diff --git a/docs/user-guide.md b/docs/user-guide.md index 3aad200b..63122da5 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -471,14 +471,16 @@ processing in userspace (MIPI cameras behind the IPU6), those behind the image processor of AMD's Ryzen AI Max (HP's ZBook Ultra G1a), and infrared face-login cameras do not. `vault` and the net zone never get the camera. -Firefox in a zone is not told when the camera is given or taken back. -- Give the camera before opening the call. A page that looked for a camera - before goes on hearing there is none, even after a reload. Turning the - camera on in the call finds it, and so does starting Firefox again. +Firefox in a zone is not told when the camera is given or taken back, so it +looks once a second. +- Give the camera before opening the call where you can. A page that looked + for a camera before hears of it within a second if it listens for that; + otherwise turning the camera on in the call finds it, and so does a + reload. - Turn the camera off in the call, or leave the call, before `k` takes the - camera back or the screen locks, or as soon as you can after. A call still - showing a camera that was taken back keeps a processor core busy and - keeps hold of the camera, and until then `k` cannot give it to any zone. + camera back or the screen locks, or as soon as you can after. Until the + call lets go of a camera that was taken back, `k` cannot give it to any + zone. For administration, Ctrl+Alt+F2 gives a text login: log in as the user, and `su` becomes root, where passwords change: `passwd NAME` for the user,