diff --git a/test/Makefile.am b/test/Makefile.am index 15a4bd7b..1bd5cd6d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -83,6 +83,7 @@ EXTRA_DIST += dbus-service.sh EXTRA_DIST += dbus-cond.sh EXTRA_DIST += dbus-initctl.sh EXTRA_DIST += dbus-introspect.sh +EXTRA_DIST += dbus-device.sh AM_TESTS_ENVIRONMENT = SYSROOT='$(abs_builddir)/sysroot/'; AM_TESTS_ENVIRONMENT += export SYSROOT; @@ -150,6 +151,9 @@ TESTS += dbus-service.sh TESTS += dbus-cond.sh TESTS += dbus-initctl.sh TESTS += dbus-introspect.sh +if KEVENTD +TESTS += dbus-device.sh +endif TESTS += fuzz-msg-parse.sh # Needs the plugin to bring up the bus, not just the built-in one if BUILD_DBUS_PLUGIN diff --git a/test/dbus-device.sh b/test/dbus-device.sh new file mode 100755 index 00000000..2342dee3 --- /dev/null +++ b/test/dbus-device.sh @@ -0,0 +1,135 @@ +#!/bin/sh +# keventd: org.finit.Device1 on /run/keventd/bus. +# +# Covers the device manager's own bus: introspection, queue-state +# properties, Settle, Info by devpath, RulesReload, Trigger, and the +# DeviceProcessed signal. Network interfaces are the one device class +# the unprivileged sandbox can hotplug, see keventd.sh. + +set -eu + +TEST_DIR=$(dirname "$0") + +test_teardown() +{ + say "Running test teardown." + + run "ip link del dummy0 2>/dev/null || true" + run "rm -f $FINIT_CONF" +} + +# shellcheck source=/dev/null +. "$TEST_DIR/lib/setup.sh" +# shellcheck source=/dev/null +. "$TEST_DIR/lib/dbus-setup.sh" + +DEVBUS=/run/keventd/bus + +if ! texec initctl -p status keventd >/dev/null 2>&1; then + skip "keventd not enabled in this build" +fi +if ! run "ip link add probe0 type dummy 2>/dev/null"; then + skip "cannot create dummy interfaces in test namespace" +fi +run "ip link del probe0" + +say "keventd readiness" +retry 'assert_ready "keventd"' 50 0.2 +retry "texec test -S $DEVBUS" + +say "Device1 introspects with methods, properties, and signal" +xml=$(texec "$CLIENT" introspect "$DEVBUS" /org/finit/device) +for m in Settle Trigger Info RulesReload; do + case "$xml" in + *""*) assert "$m declared" 0 -eq 0 ;; + *) fail "$m missing from Device1 XML" ;; + esac +done +case "$xml" in + *''*) + assert "QueueEmpty property and DeviceProcessed signal declared" 0 -eq 0 ;; + *) fail "property/signal declarations missing: $xml" ;; +esac + +say "Queue-state properties are readable" +seq1=$(texec "$CLIENT" getprop "$DEVBUS" /org/finit/device \ + org.finit.Device1 SeqnumProcessed) +assert "SeqnumProcessed is numeric (got: $seq1)" "$seq1" -ge 0 +qe=$(texec "$CLIENT" getprop "$DEVBUS" /org/finit/device \ + org.finit.Device1 QueueEmpty) +case "$qe" in + true|false) assert "QueueEmpty reads $qe" 0 -eq 0 ;; + *) fail "Unexpected QueueEmpty value: $qe" ;; +esac + +say "Device1.Settle answers within its timeout" +texec "$CLIENT" call-u "$DEVBUS" /org/finit/device \ + org.finit.Device1 Settle 15 >/dev/null \ + || fail "Settle returned non-zero" +assert "Settle completed" 0 -eq 0 + +say "Interface add advances SeqnumProcessed and Info answers" +run "ip link add dummy0 type dummy" +retry 'assert_cond "class/net/dummy0"' +seq2=$(texec "$CLIENT" getprop "$DEVBUS" /org/finit/device \ + org.finit.Device1 SeqnumProcessed) +assert "SeqnumProcessed advanced ($seq1 -> $seq2)" "$seq2" -gt "$seq1" + +info=$(texec "$CLIENT" call-s "$DEVBUS" /org/finit/device \ + org.finit.Device1 Info /devices/virtual/net/dummy0 2>&1) \ + || fail "Info failed: $info" +assert "Info answered for dummy0" 0 -eq 0 + +say "keventd -S settles via the bus" +run "/libexec/finit/keventd -S -t 15" || fail "keventd -S failed" +assert "bus-first settle ok" 0 -eq 0 + +say "Trigger(add, net) re-emits events, DeviceProcessed observed" +rm -f /tmp/dbus-dev-sig.out +( texec "$CLIENT" monitor-signal "$DEVBUS" \ + "type='signal',interface='org.finit.Device1',member='DeviceProcessed'" \ + 5000 > /tmp/dbus-dev-sig.out 2>&1 ) & +mon_pid=$! +sleep 0.5 +texec "$CLIENT" call-ss "$DEVBUS" /org/finit/device \ + org.finit.Device1 Trigger add net >/dev/null 2>&1 \ + || fail "Trigger returned non-zero" +set +e +wait "$mon_pid" +mon_rc=$? +set -e +assert "monitor saw DeviceProcessed (rc=$mon_rc)" "$mon_rc" -eq 0 +case "$(cat /tmp/dbus-dev-sig.out)" in + *"DeviceProcessed"*net*) assert "Signal payload names a net device" 0 -eq 0 ;; + *) fail "Unexpected signal output: $(cat /tmp/dbus-dev-sig.out)" ;; +esac + +say "RulesReload succeeds and Trigger rejects a bogus action" +texec "$CLIENT" call-void "$DEVBUS" /org/finit/device \ + org.finit.Device1 RulesReload >/dev/null \ + || fail "RulesReload failed" +assert "RulesReload ok" 0 -eq 0 + +set +e +texec "$CLIENT" call-ss "$DEVBUS" /org/finit/device \ + org.finit.Device1 Trigger frobnicate "" >/tmp/dbus-trg.out 2>&1 +trg_rc=$? +set -e +assert "Bogus action rejected (rc=$trg_rc)" "$trg_rc" -eq 1 +case "$(cat /tmp/dbus-trg.out)" in + *InvalidArgs*) assert "Error is InvalidArgs" 0 -eq 0 ;; + *) fail "Unexpected reply: $(cat /tmp/dbus-trg.out)" ;; +esac + +say "Device1.RulesReload from non-root is rejected with AccessDenied" +texec chmod 0666 "$DEVBUS" +set +e +texec "$CLIENT" call-void-as-uid 1 "$DEVBUS" /org/finit/device \ + org.finit.Device1 RulesReload >/tmp/dbus-devauthz.out 2>&1 +dauthz_rc=$? +set -e +assert "Non-root RulesReload rejected (rc=$dauthz_rc)" "$dauthz_rc" -eq 1 +case "$(cat /tmp/dbus-devauthz.out)" in + *AccessDenied*) assert "Device1 authz fires" 0 -eq 0 ;; + *) fail "Unexpected reply: $(cat /tmp/dbus-devauthz.out)" ;; +esac diff --git a/test/src/dbus-auth-client.c b/test/src/dbus-auth-client.c index 2e7e6a2b..a24bf223 100644 --- a/test/src/dbus-auth-client.c +++ b/test/src/dbus-auth-client.c @@ -342,6 +342,24 @@ static int do_call_u(const char *sock, const char *obj, const char *iface, return rc; } +static int mode_call_ss(int argc, char *argv[]) +{ + link_client_t *c; + int rc; + + if (argc != 8) return 2; + c = link_client_open(argv[2]); + if (!c) return 2; + + rc = link_client_call_v(c, argv[3], argv[4], argv[5], + "ss", argv[6], argv[7]); + rc = report_rc(c, rc); + if (rc == 0) + printf("OK\n"); + link_client_close(c); + return rc; +} + static int mode_call_u(int argc, char *argv[]) { if (argc != 7) return 2; @@ -409,7 +427,7 @@ static int mode_get_service(int argc, char *argv[]) /* * getprop BUS PATH IFACE PROP -- Properties.Get, prints the variant - * value; dispatches on the wire signature, "s" and "u" supported. + * value; dispatches on the wire signature: s, u, b, and t. */ static int mode_getprop(int argc, char *argv[]) { @@ -439,6 +457,21 @@ static int mode_getprop(int argc, char *argv[]) printf("%s\n", s); else if (type == 'u' && link_r_u32(&reader, &u) == 0) printf("%u\n", u); + else if (type == 'b') { + int b; + + if (link_r_bool(&reader, &b) == 0) + printf("%s\n", b ? "true" : "false"); + else + rc = 2; + } else if (type == 't') { + uint64_t t; + + if (link_r_u64(&reader, &t) == 0) + printf("%llu\n", (unsigned long long)t); + else + rc = 2; + } else { fprintf(stderr, "unsupported variant type '%c'\n", type); rc = 2; @@ -547,6 +580,7 @@ int main(int argc, char *argv[]) if (!strcmp(argv[1], "introspect")) return mode_introspect (argc, argv); if (!strcmp(argv[1], "liststrings")) return mode_liststrings (argc, argv); if (!strcmp(argv[1], "call-s")) return mode_call_s (argc, argv); + if (!strcmp(argv[1], "call-ss")) return mode_call_ss (argc, argv); if (!strcmp(argv[1], "call-u")) return mode_call_u (argc, argv); if (!strcmp(argv[1], "call-su")) return mode_call_su (argc, argv); if (!strcmp(argv[1], "call-void")) return mode_call_void (argc, argv);