diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /tools/testing/selftests/kdbus |
Initial import
Diffstat (limited to 'tools/testing/selftests/kdbus')
27 files changed, 10413 insertions, 0 deletions
diff --git a/tools/testing/selftests/kdbus/.gitignore b/tools/testing/selftests/kdbus/.gitignore new file mode 100644 index 000000000..d3ef42f6a --- /dev/null +++ b/tools/testing/selftests/kdbus/.gitignore @@ -0,0 +1 @@ +kdbus-test diff --git a/tools/testing/selftests/kdbus/Makefile b/tools/testing/selftests/kdbus/Makefile new file mode 100644 index 000000000..8f36cb566 --- /dev/null +++ b/tools/testing/selftests/kdbus/Makefile @@ -0,0 +1,49 @@ +CFLAGS += -I../../../../usr/include/ +CFLAGS += -I../../../../samples/kdbus/ +CFLAGS += -I../../../../include/uapi/ +CFLAGS += -std=gnu99 +CFLAGS += -DKBUILD_MODNAME=\"kdbus\" -D_GNU_SOURCE +LDLIBS = -pthread -lcap -lm + +OBJS= \ + kdbus-enum.o \ + kdbus-util.o \ + kdbus-test.o \ + kdbus-test.o \ + test-activator.o \ + test-benchmark.o \ + test-bus.o \ + test-chat.o \ + test-connection.o \ + test-daemon.o \ + test-endpoint.o \ + test-fd.o \ + test-free.o \ + test-match.o \ + test-message.o \ + test-metadata-ns.o \ + test-monitor.o \ + test-names.o \ + test-policy.o \ + test-policy-ns.o \ + test-policy-priv.o \ + test-sync.o \ + test-timeout.o + +all: kdbus-test + +include ../lib.mk + +%.o: %.c kdbus-enum.h kdbus-test.h kdbus-util.h + $(CC) $(CFLAGS) -c $< -o $@ + +kdbus-test: $(OBJS) + $(CC) $(CFLAGS) $^ $(LDLIBS) -o $@ + +TEST_PROGS := kdbus-test + +run_tests: + ./kdbus-test --tap + +clean: + rm -f *.o kdbus-test diff --git a/tools/testing/selftests/kdbus/kdbus-enum.c b/tools/testing/selftests/kdbus/kdbus-enum.c new file mode 100644 index 000000000..4f1e57978 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-enum.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> + +#include "kdbus-util.h" +#include "kdbus-enum.h" + +struct kdbus_enum_table { + long long id; + const char *name; +}; + +#define TABLE(what) static struct kdbus_enum_table kdbus_table_##what[] +#define ENUM(_id) { .id = _id, .name = STRINGIFY(_id) } +#define LOOKUP(what) \ + const char *enum_##what(long long id) \ + { \ + for (size_t i = 0; i < ELEMENTSOF(kdbus_table_##what); i++) \ + if (id == kdbus_table_##what[i].id) \ + return kdbus_table_##what[i].name; \ + return "UNKNOWN"; \ + } + +TABLE(CMD) = { + ENUM(KDBUS_CMD_BUS_MAKE), + ENUM(KDBUS_CMD_ENDPOINT_MAKE), + ENUM(KDBUS_CMD_HELLO), + ENUM(KDBUS_CMD_SEND), + ENUM(KDBUS_CMD_RECV), + ENUM(KDBUS_CMD_LIST), + ENUM(KDBUS_CMD_NAME_RELEASE), + ENUM(KDBUS_CMD_CONN_INFO), + ENUM(KDBUS_CMD_MATCH_ADD), + ENUM(KDBUS_CMD_MATCH_REMOVE), +}; +LOOKUP(CMD); + +TABLE(MSG) = { + ENUM(_KDBUS_ITEM_NULL), + ENUM(KDBUS_ITEM_PAYLOAD_VEC), + ENUM(KDBUS_ITEM_PAYLOAD_OFF), + ENUM(KDBUS_ITEM_PAYLOAD_MEMFD), + ENUM(KDBUS_ITEM_FDS), + ENUM(KDBUS_ITEM_BLOOM_PARAMETER), + ENUM(KDBUS_ITEM_BLOOM_FILTER), + ENUM(KDBUS_ITEM_DST_NAME), + ENUM(KDBUS_ITEM_MAKE_NAME), + ENUM(KDBUS_ITEM_ATTACH_FLAGS_SEND), + ENUM(KDBUS_ITEM_ATTACH_FLAGS_RECV), + ENUM(KDBUS_ITEM_ID), + ENUM(KDBUS_ITEM_NAME), + ENUM(KDBUS_ITEM_TIMESTAMP), + ENUM(KDBUS_ITEM_CREDS), + ENUM(KDBUS_ITEM_PIDS), + ENUM(KDBUS_ITEM_AUXGROUPS), + ENUM(KDBUS_ITEM_OWNED_NAME), + ENUM(KDBUS_ITEM_TID_COMM), + ENUM(KDBUS_ITEM_PID_COMM), + ENUM(KDBUS_ITEM_EXE), + ENUM(KDBUS_ITEM_CMDLINE), + ENUM(KDBUS_ITEM_CGROUP), + ENUM(KDBUS_ITEM_CAPS), + ENUM(KDBUS_ITEM_SECLABEL), + ENUM(KDBUS_ITEM_AUDIT), + ENUM(KDBUS_ITEM_CONN_DESCRIPTION), + ENUM(KDBUS_ITEM_NAME_ADD), + ENUM(KDBUS_ITEM_NAME_REMOVE), + ENUM(KDBUS_ITEM_NAME_CHANGE), + ENUM(KDBUS_ITEM_ID_ADD), + ENUM(KDBUS_ITEM_ID_REMOVE), + ENUM(KDBUS_ITEM_REPLY_TIMEOUT), + ENUM(KDBUS_ITEM_REPLY_DEAD), +}; +LOOKUP(MSG); + +TABLE(PAYLOAD) = { + ENUM(KDBUS_PAYLOAD_KERNEL), + ENUM(KDBUS_PAYLOAD_DBUS), +}; +LOOKUP(PAYLOAD); diff --git a/tools/testing/selftests/kdbus/kdbus-enum.h b/tools/testing/selftests/kdbus/kdbus-enum.h new file mode 100644 index 000000000..ed28cca26 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-enum.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#pragma once + +const char *enum_CMD(long long id); +const char *enum_MSG(long long id); +const char *enum_MATCH(long long id); +const char *enum_PAYLOAD(long long id); diff --git a/tools/testing/selftests/kdbus/kdbus-test.c b/tools/testing/selftests/kdbus/kdbus-test.c new file mode 100644 index 000000000..db732e596 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-test.c @@ -0,0 +1,899 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <assert.h> +#include <getopt.h> +#include <stdbool.h> +#include <signal.h> +#include <sys/mount.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <sys/eventfd.h> +#include <linux/sched.h> + +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +enum { + TEST_CREATE_BUS = 1 << 0, + TEST_CREATE_CONN = 1 << 1, +}; + +struct kdbus_test { + const char *name; + const char *desc; + int (*func)(struct kdbus_test_env *env); + unsigned int flags; +}; + +struct kdbus_test_args { + bool mntns; + bool pidns; + bool userns; + char *uid_map; + char *gid_map; + int loop; + int wait; + int fork; + int tap_output; + char *module; + char *root; + char *test; + char *busname; +}; + +static const struct kdbus_test tests[] = { + { + .name = "bus-make", + .desc = "bus make functions", + .func = kdbus_test_bus_make, + .flags = 0, + }, + { + .name = "hello", + .desc = "the HELLO command", + .func = kdbus_test_hello, + .flags = TEST_CREATE_BUS, + }, + { + .name = "byebye", + .desc = "the BYEBYE command", + .func = kdbus_test_byebye, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "chat", + .desc = "a chat pattern", + .func = kdbus_test_chat, + .flags = TEST_CREATE_BUS, + }, + { + .name = "daemon", + .desc = "a simple daemon", + .func = kdbus_test_daemon, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "fd-passing", + .desc = "file descriptor passing", + .func = kdbus_test_fd_passing, + .flags = TEST_CREATE_BUS, + }, + { + .name = "endpoint", + .desc = "custom endpoint", + .func = kdbus_test_custom_endpoint, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "monitor", + .desc = "monitor functionality", + .func = kdbus_test_monitor, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "name-basics", + .desc = "basic name registry functions", + .func = kdbus_test_name_basic, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "name-conflict", + .desc = "name registry conflict details", + .func = kdbus_test_name_conflict, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "name-queue", + .desc = "queuing of names", + .func = kdbus_test_name_queue, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "message-basic", + .desc = "basic message handling", + .func = kdbus_test_message_basic, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "message-prio", + .desc = "handling of messages with priority", + .func = kdbus_test_message_prio, + .flags = TEST_CREATE_BUS, + }, + { + .name = "message-quota", + .desc = "message quotas are enforced", + .func = kdbus_test_message_quota, + .flags = TEST_CREATE_BUS, + }, + { + .name = "memory-access", + .desc = "memory access", + .func = kdbus_test_memory_access, + .flags = TEST_CREATE_BUS, + }, + { + .name = "timeout", + .desc = "timeout", + .func = kdbus_test_timeout, + .flags = TEST_CREATE_BUS, + }, + { + .name = "sync-byebye", + .desc = "synchronous replies vs. BYEBYE", + .func = kdbus_test_sync_byebye, + .flags = TEST_CREATE_BUS, + }, + { + .name = "sync-reply", + .desc = "synchronous replies", + .func = kdbus_test_sync_reply, + .flags = TEST_CREATE_BUS, + }, + { + .name = "message-free", + .desc = "freeing of memory", + .func = kdbus_test_free, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "connection-info", + .desc = "retrieving connection information", + .func = kdbus_test_conn_info, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "connection-update", + .desc = "updating connection information", + .func = kdbus_test_conn_update, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "writable-pool", + .desc = "verifying pools are never writable", + .func = kdbus_test_writable_pool, + .flags = TEST_CREATE_BUS, + }, + { + .name = "policy", + .desc = "policy", + .func = kdbus_test_policy, + .flags = TEST_CREATE_BUS, + }, + { + .name = "policy-priv", + .desc = "unprivileged bus access", + .func = kdbus_test_policy_priv, + .flags = TEST_CREATE_BUS, + }, + { + .name = "policy-ns", + .desc = "policy in user namespaces", + .func = kdbus_test_policy_ns, + .flags = TEST_CREATE_BUS, + }, + { + .name = "metadata-ns", + .desc = "metadata in different namespaces", + .func = kdbus_test_metadata_ns, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-id-add", + .desc = "adding of matches by id", + .func = kdbus_test_match_id_add, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-id-remove", + .desc = "removing of matches by id", + .func = kdbus_test_match_id_remove, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-replace", + .desc = "replace of matches with the same cookie", + .func = kdbus_test_match_replace, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-name-add", + .desc = "adding of matches by name", + .func = kdbus_test_match_name_add, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-name-remove", + .desc = "removing of matches by name", + .func = kdbus_test_match_name_remove, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-name-change", + .desc = "matching for name changes", + .func = kdbus_test_match_name_change, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-bloom", + .desc = "matching with bloom filters", + .func = kdbus_test_match_bloom, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "activator", + .desc = "activator connections", + .func = kdbus_test_activator, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "benchmark", + .desc = "benchmark", + .func = kdbus_test_benchmark, + .flags = TEST_CREATE_BUS, + }, + { + .name = "benchmark-nomemfds", + .desc = "benchmark without using memfds", + .func = kdbus_test_benchmark_nomemfds, + .flags = TEST_CREATE_BUS, + }, + { + .name = "benchmark-uds", + .desc = "benchmark comparison to UDS", + .func = kdbus_test_benchmark_uds, + .flags = TEST_CREATE_BUS, + }, +}; + +#define N_TESTS ((int) (sizeof(tests) / sizeof(tests[0]))) + +static int test_prepare_env(const struct kdbus_test *t, + const struct kdbus_test_args *args, + struct kdbus_test_env *env) +{ + if (t->flags & TEST_CREATE_BUS) { + char *s; + char *n = NULL; + int ret; + + asprintf(&s, "%s/control", args->root); + + env->control_fd = open(s, O_RDWR); + free(s); + ASSERT_RETURN(env->control_fd >= 0); + + if (!args->busname) { + n = unique_name("test-bus"); + ASSERT_RETURN(n); + } + + ret = kdbus_create_bus(env->control_fd, + args->busname ?: n, + _KDBUS_ATTACH_ALL, &s); + free(n); + ASSERT_RETURN(ret == 0); + + asprintf(&env->buspath, "%s/%s/bus", args->root, s); + free(s); + } + + if (t->flags & TEST_CREATE_CONN) { + env->conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(env->conn); + } + + env->root = args->root; + env->module = args->module; + + return 0; +} + +void test_unprepare_env(const struct kdbus_test *t, struct kdbus_test_env *env) +{ + if (env->conn) { + kdbus_conn_free(env->conn); + env->conn = NULL; + } + + if (env->control_fd >= 0) { + close(env->control_fd); + env->control_fd = -1; + } + + if (env->buspath) { + free(env->buspath); + env->buspath = NULL; + } +} + +static int test_run(const struct kdbus_test *t, + const struct kdbus_test_args *kdbus_args, + int wait) +{ + int ret; + struct kdbus_test_env env = {}; + + ret = test_prepare_env(t, kdbus_args, &env); + if (ret != TEST_OK) + return ret; + + if (wait > 0) { + printf("Sleeping %d seconds before running test ...\n", wait); + sleep(wait); + } + + ret = t->func(&env); + test_unprepare_env(t, &env); + return ret; +} + +static int test_run_forked(const struct kdbus_test *t, + const struct kdbus_test_args *kdbus_args, + int wait) +{ + int ret; + pid_t pid; + + pid = fork(); + if (pid < 0) { + return TEST_ERR; + } else if (pid == 0) { + ret = test_run(t, kdbus_args, wait); + _exit(ret); + } + + pid = waitpid(pid, &ret, 0); + if (pid <= 0) + return TEST_ERR; + else if (!WIFEXITED(ret)) + return TEST_ERR; + else + return WEXITSTATUS(ret); +} + +static void print_test_result(int ret) +{ + switch (ret) { + case TEST_OK: + printf("OK"); + break; + case TEST_SKIP: + printf("SKIPPED"); + break; + case TEST_ERR: + printf("ERROR"); + break; + } +} + +static int start_all_tests(struct kdbus_test_args *kdbus_args) +{ + int ret; + unsigned int fail_cnt = 0; + unsigned int skip_cnt = 0; + unsigned int ok_cnt = 0; + unsigned int i; + + if (kdbus_args->tap_output) { + printf("1..%d\n", N_TESTS); + fflush(stdout); + } + + kdbus_util_verbose = false; + + for (i = 0; i < N_TESTS; i++) { + const struct kdbus_test *t = tests + i; + + if (!kdbus_args->tap_output) { + unsigned int n; + + printf("Testing %s (%s) ", t->desc, t->name); + for (n = 0; n < 60 - strlen(t->desc) - strlen(t->name); n++) + printf("."); + printf(" "); + } + + ret = test_run_forked(t, kdbus_args, 0); + switch (ret) { + case TEST_OK: + ok_cnt++; + break; + case TEST_SKIP: + skip_cnt++; + break; + case TEST_ERR: + fail_cnt++; + break; + } + + if (kdbus_args->tap_output) { + printf("%sok %d - %s%s (%s)\n", + (ret == TEST_ERR) ? "not " : "", i + 1, + (ret == TEST_SKIP) ? "# SKIP " : "", + t->desc, t->name); + fflush(stdout); + } else { + print_test_result(ret); + printf("\n"); + } + } + + if (kdbus_args->tap_output) + printf("Failed %d/%d tests, %.2f%% okay\n", fail_cnt, N_TESTS, + 100.0 - (fail_cnt * 100.0) / ((float) N_TESTS)); + else + printf("\nSUMMARY: %u tests passed, %u skipped, %u failed\n", + ok_cnt, skip_cnt, fail_cnt); + + return fail_cnt > 0 ? TEST_ERR : TEST_OK; +} + +static int start_one_test(struct kdbus_test_args *kdbus_args) +{ + int i, ret; + bool test_found = false; + + for (i = 0; i < N_TESTS; i++) { + const struct kdbus_test *t = tests + i; + + if (strcmp(t->name, kdbus_args->test)) + continue; + + do { + test_found = true; + if (kdbus_args->fork) + ret = test_run_forked(t, kdbus_args, + kdbus_args->wait); + else + ret = test_run(t, kdbus_args, + kdbus_args->wait); + + printf("Testing %s: ", t->desc); + print_test_result(ret); + printf("\n"); + + if (ret != TEST_OK) + break; + } while (kdbus_args->loop); + + return ret; + } + + if (!test_found) { + printf("Unknown test-id '%s'\n", kdbus_args->test); + return TEST_ERR; + } + + return TEST_OK; +} + +static void usage(const char *argv0) +{ + unsigned int i, j; + + printf("Usage: %s [options]\n" + "Options:\n" + "\t-a, --tap Output test results in TAP format\n" + "\t-m, --module <module> Kdbus module name\n" + "\t-x, --loop Run in a loop\n" + "\t-f, --fork Fork before running a test\n" + "\t-h, --help Print this help\n" + "\t-r, --root <root> Toplevel of the kdbus hierarchy\n" + "\t-t, --test <test-id> Run one specific test only, in verbose mode\n" + "\t-b, --bus <busname> Instead of generating a random bus name, take <busname>.\n" + "\t-w, --wait <secs> Wait <secs> before actually starting test\n" + "\t --mntns New mount namespace\n" + "\t --pidns New PID namespace\n" + "\t --userns New user namespace\n" + "\t --uidmap uid_map UID map for user namespace\n" + "\t --gidmap gid_map GID map for user namespace\n" + "\n", argv0); + + printf("By default, all test are run once, and a summary is printed.\n" + "Available tests for --test:\n\n"); + + for (i = 0; i < N_TESTS; i++) { + const struct kdbus_test *t = tests + i; + + printf("\t%s", t->name); + + for (j = 0; j < 24 - strlen(t->name); j++) + printf(" "); + + printf("Test %s\n", t->desc); + } + + printf("\n"); + printf("Note that some tests may, if run specifically by --test, " + "behave differently, and not terminate by themselves.\n"); + + exit(EXIT_FAILURE); +} + +void print_kdbus_test_args(struct kdbus_test_args *args) +{ + if (args->userns || args->pidns || args->mntns) + printf("# Starting tests in new %s%s%s namespaces%s\n", + args->mntns ? "MOUNT " : "", + args->pidns ? "PID " : "", + args->userns ? "USER " : "", + args->mntns ? ", kdbusfs will be remounted" : ""); + else + printf("# Starting tests in the same namespaces\n"); +} + +void print_metadata_support(void) +{ + bool no_meta_audit, no_meta_cgroups, no_meta_seclabel; + + /* + * KDBUS_ATTACH_CGROUP, KDBUS_ATTACH_AUDIT and + * KDBUS_ATTACH_SECLABEL + */ + no_meta_audit = !config_auditsyscall_is_enabled(); + no_meta_cgroups = !config_cgroups_is_enabled(); + no_meta_seclabel = !config_security_is_enabled(); + + if (no_meta_audit | no_meta_cgroups | no_meta_seclabel) + printf("# Starting tests without %s%s%s metadata support\n", + no_meta_audit ? "AUDIT " : "", + no_meta_cgroups ? "CGROUP " : "", + no_meta_seclabel ? "SECLABEL " : ""); + else + printf("# Starting tests with full metadata support\n"); +} + +int run_tests(struct kdbus_test_args *kdbus_args) +{ + int ret; + static char control[4096]; + + snprintf(control, sizeof(control), "%s/control", kdbus_args->root); + + if (access(control, W_OK) < 0) { + printf("Unable to locate control node at '%s'.\n", + control); + return TEST_ERR; + } + + if (kdbus_args->test) { + ret = start_one_test(kdbus_args); + } else { + do { + ret = start_all_tests(kdbus_args); + if (ret != TEST_OK) + break; + } while (kdbus_args->loop); + } + + return ret; +} + +static void nop_handler(int sig) {} + +static int test_prepare_mounts(struct kdbus_test_args *kdbus_args) +{ + int ret; + char kdbusfs[64] = {'\0'}; + + snprintf(kdbusfs, sizeof(kdbusfs), "%sfs", kdbus_args->module); + + /* make current mount slave */ + ret = mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL); + if (ret < 0) { + ret = -errno; + printf("error mount() root: %d (%m)\n", ret); + return ret; + } + + /* Remount procfs since we need it in our tests */ + if (kdbus_args->pidns) { + ret = mount("proc", "/proc", "proc", + MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (ret < 0) { + ret = -errno; + printf("error mount() /proc : %d (%m)\n", ret); + return ret; + } + } + + /* Remount kdbusfs */ + ret = mount(kdbusfs, kdbus_args->root, kdbusfs, + MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (ret < 0) { + ret = -errno; + printf("error mount() %s :%d (%m)\n", kdbusfs, ret); + return ret; + } + + return 0; +} + +int run_tests_in_namespaces(struct kdbus_test_args *kdbus_args) +{ + int ret; + int efd = -1; + int status; + pid_t pid, rpid; + struct sigaction oldsa; + struct sigaction sa = { + .sa_handler = nop_handler, + .sa_flags = SA_NOCLDSTOP, + }; + + efd = eventfd(0, EFD_CLOEXEC); + if (efd < 0) { + ret = -errno; + printf("eventfd() failed: %d (%m)\n", ret); + return TEST_ERR; + } + + ret = sigaction(SIGCHLD, &sa, &oldsa); + if (ret < 0) { + ret = -errno; + printf("sigaction() failed: %d (%m)\n", ret); + return TEST_ERR; + } + + /* setup namespaces */ + pid = syscall(__NR_clone, SIGCHLD| + (kdbus_args->userns ? CLONE_NEWUSER : 0) | + (kdbus_args->mntns ? CLONE_NEWNS : 0) | + (kdbus_args->pidns ? CLONE_NEWPID : 0), NULL); + if (pid < 0) { + printf("clone() failed: %d (%m)\n", -errno); + return TEST_ERR; + } + + if (pid == 0) { + eventfd_t event_status = 0; + + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + if (ret < 0) { + ret = -errno; + printf("error prctl(): %d (%m)\n", ret); + _exit(TEST_ERR); + } + + /* reset sighandlers of childs */ + ret = sigaction(SIGCHLD, &oldsa, NULL); + if (ret < 0) { + ret = -errno; + printf("sigaction() failed: %d (%m)\n", ret); + _exit(TEST_ERR); + } + + ret = eventfd_read(efd, &event_status); + if (ret < 0 || event_status != 1) { + printf("error eventfd_read()\n"); + _exit(TEST_ERR); + } + + if (kdbus_args->mntns) { + ret = test_prepare_mounts(kdbus_args); + if (ret < 0) { + printf("error preparing mounts\n"); + _exit(TEST_ERR); + } + } + + ret = run_tests(kdbus_args); + _exit(ret); + } + + /* Setup userns mapping */ + if (kdbus_args->userns) { + ret = userns_map_uid_gid(pid, kdbus_args->uid_map, + kdbus_args->gid_map); + if (ret < 0) { + printf("error mapping uid and gid in userns\n"); + eventfd_write(efd, 2); + return TEST_ERR; + } + } + + ret = eventfd_write(efd, 1); + if (ret < 0) { + ret = -errno; + printf("error eventfd_write(): %d (%m)\n", ret); + return TEST_ERR; + } + + rpid = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(rpid == pid, TEST_ERR); + + close(efd); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return TEST_ERR; + + return TEST_OK; +} + +int start_tests(struct kdbus_test_args *kdbus_args) +{ + int ret; + bool namespaces; + static char fspath[4096]; + + namespaces = (kdbus_args->mntns || kdbus_args->pidns || + kdbus_args->userns); + + /* for pidns we need mntns set */ + if (kdbus_args->pidns && !kdbus_args->mntns) { + printf("Failed: please set both pid and mnt namesapces\n"); + return TEST_ERR; + } + + if (kdbus_args->userns) { + if (!config_user_ns_is_enabled()) { + printf("User namespace not supported\n"); + return TEST_ERR; + } + + if (!kdbus_args->uid_map || !kdbus_args->gid_map) { + printf("Failed: please specify uid or gid mapping\n"); + return TEST_ERR; + } + } + + print_kdbus_test_args(kdbus_args); + print_metadata_support(); + + /* setup kdbus paths */ + if (!kdbus_args->module) + kdbus_args->module = "kdbus"; + + if (!kdbus_args->root) { + snprintf(fspath, sizeof(fspath), "/sys/fs/%s", + kdbus_args->module); + kdbus_args->root = fspath; + } + + /* Start tests */ + if (namespaces) + ret = run_tests_in_namespaces(kdbus_args); + else + ret = run_tests(kdbus_args); + + return ret; +} + +int main(int argc, char *argv[]) +{ + int t, ret = 0; + struct kdbus_test_args *kdbus_args; + enum { + ARG_MNTNS = 0x100, + ARG_PIDNS, + ARG_USERNS, + ARG_UIDMAP, + ARG_GIDMAP, + }; + + kdbus_args = malloc(sizeof(*kdbus_args)); + if (!kdbus_args) { + printf("unable to malloc() kdbus_args\n"); + return EXIT_FAILURE; + } + + memset(kdbus_args, 0, sizeof(*kdbus_args)); + + static const struct option options[] = { + { "loop", no_argument, NULL, 'x' }, + { "help", no_argument, NULL, 'h' }, + { "root", required_argument, NULL, 'r' }, + { "test", required_argument, NULL, 't' }, + { "bus", required_argument, NULL, 'b' }, + { "wait", required_argument, NULL, 'w' }, + { "fork", no_argument, NULL, 'f' }, + { "module", required_argument, NULL, 'm' }, + { "tap", no_argument, NULL, 'a' }, + { "mntns", no_argument, NULL, ARG_MNTNS }, + { "pidns", no_argument, NULL, ARG_PIDNS }, + { "userns", no_argument, NULL, ARG_USERNS }, + { "uidmap", required_argument, NULL, ARG_UIDMAP }, + { "gidmap", required_argument, NULL, ARG_GIDMAP }, + {} + }; + + srand(time(NULL)); + + while ((t = getopt_long(argc, argv, "hxfm:r:t:b:w:a", options, NULL)) >= 0) { + switch (t) { + case 'x': + kdbus_args->loop = 1; + break; + + case 'm': + kdbus_args->module = optarg; + break; + + case 'r': + kdbus_args->root = optarg; + break; + + case 't': + kdbus_args->test = optarg; + break; + + case 'b': + kdbus_args->busname = optarg; + break; + + case 'w': + kdbus_args->wait = strtol(optarg, NULL, 10); + break; + + case 'f': + kdbus_args->fork = 1; + break; + + case 'a': + kdbus_args->tap_output = 1; + break; + + case ARG_MNTNS: + kdbus_args->mntns = true; + break; + + case ARG_PIDNS: + kdbus_args->pidns = true; + break; + + case ARG_USERNS: + kdbus_args->userns = true; + break; + + case ARG_UIDMAP: + kdbus_args->uid_map = optarg; + break; + + case ARG_GIDMAP: + kdbus_args->gid_map = optarg; + break; + + default: + case 'h': + usage(argv[0]); + } + } + + ret = start_tests(kdbus_args); + if (ret == TEST_ERR) + return EXIT_FAILURE; + + free(kdbus_args); + + return 0; +} diff --git a/tools/testing/selftests/kdbus/kdbus-test.h b/tools/testing/selftests/kdbus/kdbus-test.h new file mode 100644 index 000000000..a5c6ae81b --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-test.h @@ -0,0 +1,83 @@ +#ifndef _TEST_KDBUS_H_ +#define _TEST_KDBUS_H_ + +struct kdbus_test_env { + char *buspath; + const char *root; + const char *module; + int control_fd; + struct kdbus_conn *conn; +}; + +enum { + TEST_OK, + TEST_SKIP, + TEST_ERR, +}; + +#define ASSERT_RETURN_VAL(cond, val) \ + if (!(cond)) { \ + fprintf(stderr, "Assertion '%s' failed in %s(), %s:%d\n", \ + #cond, __func__, __FILE__, __LINE__); \ + return val; \ + } + +#define ASSERT_EXIT_VAL(cond, val) \ + if (!(cond)) { \ + fprintf(stderr, "Assertion '%s' failed in %s(), %s:%d\n", \ + #cond, __func__, __FILE__, __LINE__); \ + _exit(val); \ + } + +#define ASSERT_BREAK(cond) \ + if (!(cond)) { \ + fprintf(stderr, "Assertion '%s' failed in %s(), %s:%d\n", \ + #cond, __func__, __FILE__, __LINE__); \ + break; \ + } + +#define ASSERT_RETURN(cond) \ + ASSERT_RETURN_VAL(cond, TEST_ERR) + +#define ASSERT_EXIT(cond) \ + ASSERT_EXIT_VAL(cond, EXIT_FAILURE) + +int kdbus_test_activator(struct kdbus_test_env *env); +int kdbus_test_benchmark(struct kdbus_test_env *env); +int kdbus_test_benchmark_nomemfds(struct kdbus_test_env *env); +int kdbus_test_benchmark_uds(struct kdbus_test_env *env); +int kdbus_test_bus_make(struct kdbus_test_env *env); +int kdbus_test_byebye(struct kdbus_test_env *env); +int kdbus_test_chat(struct kdbus_test_env *env); +int kdbus_test_conn_info(struct kdbus_test_env *env); +int kdbus_test_conn_update(struct kdbus_test_env *env); +int kdbus_test_daemon(struct kdbus_test_env *env); +int kdbus_test_custom_endpoint(struct kdbus_test_env *env); +int kdbus_test_fd_passing(struct kdbus_test_env *env); +int kdbus_test_free(struct kdbus_test_env *env); +int kdbus_test_hello(struct kdbus_test_env *env); +int kdbus_test_match_bloom(struct kdbus_test_env *env); +int kdbus_test_match_id_add(struct kdbus_test_env *env); +int kdbus_test_match_id_remove(struct kdbus_test_env *env); +int kdbus_test_match_replace(struct kdbus_test_env *env); +int kdbus_test_match_name_add(struct kdbus_test_env *env); +int kdbus_test_match_name_change(struct kdbus_test_env *env); +int kdbus_test_match_name_remove(struct kdbus_test_env *env); +int kdbus_test_message_basic(struct kdbus_test_env *env); +int kdbus_test_message_prio(struct kdbus_test_env *env); +int kdbus_test_message_quota(struct kdbus_test_env *env); +int kdbus_test_memory_access(struct kdbus_test_env *env); +int kdbus_test_metadata_ns(struct kdbus_test_env *env); +int kdbus_test_monitor(struct kdbus_test_env *env); +int kdbus_test_name_basic(struct kdbus_test_env *env); +int kdbus_test_name_conflict(struct kdbus_test_env *env); +int kdbus_test_name_queue(struct kdbus_test_env *env); +int kdbus_test_policy(struct kdbus_test_env *env); +int kdbus_test_policy_ns(struct kdbus_test_env *env); +int kdbus_test_policy_priv(struct kdbus_test_env *env); +int kdbus_test_sync_byebye(struct kdbus_test_env *env); +int kdbus_test_sync_reply(struct kdbus_test_env *env); +int kdbus_test_timeout(struct kdbus_test_env *env); +int kdbus_test_writable_pool(struct kdbus_test_env *env); + +#endif /* _TEST_KDBUS_H_ */ diff --git a/tools/testing/selftests/kdbus/kdbus-util.c b/tools/testing/selftests/kdbus/kdbus-util.c new file mode 100644 index 000000000..a5e54ca3a --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-util.c @@ -0,0 +1,1611 @@ +/* + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <inttypes.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <grp.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <linux/unistd.h> +#include <linux/memfd.h> + +#ifndef __NR_memfd_create + #ifdef __x86_64__ + #define __NR_memfd_create 319 + #elif defined __arm__ + #define __NR_memfd_create 385 + #else + #define __NR_memfd_create 356 + #endif +#endif + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#ifndef F_ADD_SEALS +#define F_LINUX_SPECIFIC_BASE 1024 +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +int kdbus_util_verbose = true; + +int kdbus_sysfs_get_parameter_mask(const char *path, uint64_t *mask) +{ + int ret; + FILE *file; + unsigned long long value; + + file = fopen(path, "r"); + if (!file) { + ret = -errno; + kdbus_printf("--- error fopen(): %d (%m)\n", ret); + return ret; + } + + ret = fscanf(file, "%llu", &value); + if (ret != 1) { + if (ferror(file)) + ret = -errno; + else + ret = -EIO; + + kdbus_printf("--- error fscanf(): %d\n", ret); + fclose(file); + return ret; + } + + *mask = (uint64_t)value; + + fclose(file); + + return 0; +} + +int kdbus_sysfs_set_parameter_mask(const char *path, uint64_t mask) +{ + int ret; + FILE *file; + + file = fopen(path, "w"); + if (!file) { + ret = -errno; + kdbus_printf("--- error open(): %d (%m)\n", ret); + return ret; + } + + ret = fprintf(file, "%llu", (unsigned long long)mask); + if (ret <= 0) { + ret = -EIO; + kdbus_printf("--- error fprintf(): %d\n", ret); + } + + fclose(file); + + return ret > 0 ? 0 : ret; +} + +int kdbus_create_bus(int control_fd, const char *name, + uint64_t owner_meta, char **path) +{ + struct { + struct kdbus_cmd cmd; + + /* bloom size item */ + struct { + uint64_t size; + uint64_t type; + struct kdbus_bloom_parameter bloom; + } bp; + + /* owner metadata items */ + struct { + uint64_t size; + uint64_t type; + uint64_t flags; + } attach; + + /* name item */ + struct { + uint64_t size; + uint64_t type; + char str[64]; + } name; + } bus_make; + int ret; + + memset(&bus_make, 0, sizeof(bus_make)); + bus_make.bp.size = sizeof(bus_make.bp); + bus_make.bp.type = KDBUS_ITEM_BLOOM_PARAMETER; + bus_make.bp.bloom.size = 64; + bus_make.bp.bloom.n_hash = 1; + + snprintf(bus_make.name.str, sizeof(bus_make.name.str), + "%u-%s", getuid(), name); + + bus_make.attach.type = KDBUS_ITEM_ATTACH_FLAGS_SEND; + bus_make.attach.size = sizeof(bus_make.attach); + bus_make.attach.flags = owner_meta; + + bus_make.name.type = KDBUS_ITEM_MAKE_NAME; + bus_make.name.size = KDBUS_ITEM_HEADER_SIZE + + strlen(bus_make.name.str) + 1; + + bus_make.cmd.flags = KDBUS_MAKE_ACCESS_WORLD; + bus_make.cmd.size = sizeof(bus_make.cmd) + + bus_make.bp.size + + bus_make.attach.size + + bus_make.name.size; + + kdbus_printf("Creating bus with name >%s< on control fd %d ...\n", + name, control_fd); + + ret = kdbus_cmd_bus_make(control_fd, &bus_make.cmd); + if (ret < 0) { + kdbus_printf("--- error when making bus: %d (%m)\n", ret); + return ret; + } + + if (ret == 0 && path) + *path = strdup(bus_make.name.str); + + return ret; +} + +struct kdbus_conn * +kdbus_hello(const char *path, uint64_t flags, + const struct kdbus_item *item, size_t item_size) +{ + struct kdbus_cmd_free cmd_free = {}; + int fd, ret; + struct { + struct kdbus_cmd_hello hello; + + struct { + uint64_t size; + uint64_t type; + char str[16]; + } conn_name; + + uint8_t extra_items[item_size]; + } h; + struct kdbus_conn *conn; + + memset(&h, 0, sizeof(h)); + + if (item_size > 0) + memcpy(h.extra_items, item, item_size); + + kdbus_printf("-- opening bus connection %s\n", path); + fd = open(path, O_RDWR|O_CLOEXEC); + if (fd < 0) { + kdbus_printf("--- error %d (%m)\n", fd); + return NULL; + } + + h.hello.flags = flags | KDBUS_HELLO_ACCEPT_FD; + h.hello.attach_flags_send = _KDBUS_ATTACH_ALL; + h.hello.attach_flags_recv = _KDBUS_ATTACH_ALL; + h.conn_name.type = KDBUS_ITEM_CONN_DESCRIPTION; + strcpy(h.conn_name.str, "this-is-my-name"); + h.conn_name.size = KDBUS_ITEM_HEADER_SIZE + strlen(h.conn_name.str) + 1; + + h.hello.size = sizeof(h); + h.hello.pool_size = POOL_SIZE; + + ret = kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) &h.hello); + if (ret < 0) { + kdbus_printf("--- error when saying hello: %d (%m)\n", ret); + return NULL; + } + kdbus_printf("-- Our peer ID for %s: %llu -- bus uuid: '%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x'\n", + path, (unsigned long long)h.hello.id, + h.hello.id128[0], h.hello.id128[1], h.hello.id128[2], + h.hello.id128[3], h.hello.id128[4], h.hello.id128[5], + h.hello.id128[6], h.hello.id128[7], h.hello.id128[8], + h.hello.id128[9], h.hello.id128[10], h.hello.id128[11], + h.hello.id128[12], h.hello.id128[13], h.hello.id128[14], + h.hello.id128[15]); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = h.hello.offset; + kdbus_cmd_free(fd, &cmd_free); + + conn = malloc(sizeof(*conn)); + if (!conn) { + kdbus_printf("unable to malloc()!?\n"); + return NULL; + } + + conn->buf = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0); + if (conn->buf == MAP_FAILED) { + free(conn); + close(fd); + kdbus_printf("--- error mmap (%m)\n"); + return NULL; + } + + conn->fd = fd; + conn->id = h.hello.id; + return conn; +} + +struct kdbus_conn * +kdbus_hello_registrar(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access, uint64_t flags) +{ + struct kdbus_item *item, *items; + size_t i, size; + + size = KDBUS_ITEM_SIZE(strlen(name) + 1) + + num_access * KDBUS_ITEM_SIZE(sizeof(*access)); + + items = alloca(size); + + item = items; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + item->type = KDBUS_ITEM_NAME; + strcpy(item->str, name); + item = KDBUS_ITEM_NEXT(item); + + for (i = 0; i < num_access; i++) { + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_policy_access); + item->type = KDBUS_ITEM_POLICY_ACCESS; + + item->policy_access.type = access[i].type; + item->policy_access.access = access[i].access; + item->policy_access.id = access[i].id; + + item = KDBUS_ITEM_NEXT(item); + } + + return kdbus_hello(path, flags, items, size); +} + +struct kdbus_conn *kdbus_hello_activator(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access) +{ + return kdbus_hello_registrar(path, name, access, num_access, + KDBUS_HELLO_ACTIVATOR); +} + +bool kdbus_item_in_message(struct kdbus_msg *msg, uint64_t type) +{ + const struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, msg, items) + if (item->type == type) + return true; + + return false; +} + +int kdbus_bus_creator_info(struct kdbus_conn *conn, + uint64_t flags, + uint64_t *offset) +{ + struct kdbus_cmd_info *cmd; + size_t size = sizeof(*cmd); + int ret; + + cmd = alloca(size); + memset(cmd, 0, size); + cmd->size = size; + cmd->attach_flags = flags; + + ret = kdbus_cmd_bus_creator_info(conn->fd, cmd); + if (ret < 0) { + kdbus_printf("--- error when requesting info: %d (%m)\n", ret); + return ret; + } + + if (offset) + *offset = cmd->offset; + else + kdbus_free(conn, cmd->offset); + + return 0; +} + +int kdbus_conn_info(struct kdbus_conn *conn, uint64_t id, + const char *name, uint64_t flags, + uint64_t *offset) +{ + struct kdbus_cmd_info *cmd; + size_t size = sizeof(*cmd); + struct kdbus_info *info; + int ret; + + if (name) + size += KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + + cmd = alloca(size); + memset(cmd, 0, size); + cmd->size = size; + cmd->attach_flags = flags; + + if (name) { + cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + cmd->items[0].type = KDBUS_ITEM_NAME; + strcpy(cmd->items[0].str, name); + } else { + cmd->id = id; + } + + ret = kdbus_cmd_conn_info(conn->fd, cmd); + if (ret < 0) { + kdbus_printf("--- error when requesting info: %d (%m)\n", ret); + return ret; + } + + info = (struct kdbus_info *) (conn->buf + cmd->offset); + if (info->size != cmd->info_size) { + kdbus_printf("%s(): size mismatch: %d != %d\n", __func__, + (int) info->size, (int) cmd->info_size); + return -EIO; + } + + if (offset) + *offset = cmd->offset; + else + kdbus_free(conn, cmd->offset); + + return 0; +} + +void kdbus_conn_free(struct kdbus_conn *conn) +{ + if (!conn) + return; + + if (conn->buf) + munmap(conn->buf, POOL_SIZE); + + if (conn->fd >= 0) + close(conn->fd); + + free(conn); +} + +int sys_memfd_create(const char *name, __u64 size) +{ + int ret, fd; + + fd = syscall(__NR_memfd_create, name, MFD_ALLOW_SEALING); + if (fd < 0) + return fd; + + ret = ftruncate(fd, size); + if (ret < 0) { + close(fd); + return ret; + } + + return fd; +} + +int sys_memfd_seal_set(int fd) +{ + return fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | + F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); +} + +off_t sys_memfd_get_size(int fd, off_t *size) +{ + struct stat stat; + int ret; + + ret = fstat(fd, &stat); + if (ret < 0) { + kdbus_printf("stat() failed: %m\n"); + return ret; + } + + *size = stat.st_size; + return 0; +} + +static int __kdbus_msg_send(const struct kdbus_conn *conn, + const char *name, + uint64_t cookie, + uint64_t flags, + uint64_t timeout, + int64_t priority, + uint64_t dst_id, + uint64_t cmd_flags, + int cancel_fd) +{ + struct kdbus_cmd_send *cmd = NULL; + struct kdbus_msg *msg = NULL; + const char ref1[1024 * 128 + 3] = "0123456789_0"; + const char ref2[] = "0123456789_1"; + struct kdbus_item *item; + struct timespec now; + uint64_t size; + int memfd = -1; + int ret; + + size = sizeof(*msg) + 3 * KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + if (dst_id == KDBUS_DST_ID_BROADCAST) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + else { + memfd = sys_memfd_create("my-name-is-nice", 1024 * 1024); + if (memfd < 0) { + kdbus_printf("failed to create memfd: %m\n"); + return memfd; + } + + if (write(memfd, "kdbus memfd 1234567", 19) != 19) { + ret = -errno; + kdbus_printf("writing to memfd failed: %m\n"); + goto out; + } + + ret = sys_memfd_seal_set(memfd); + if (ret < 0) { + ret = -errno; + kdbus_printf("memfd sealing failed: %m\n"); + goto out; + } + + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + } + + if (name) + size += KDBUS_ITEM_SIZE(strlen(name) + 1); + + msg = malloc(size); + if (!msg) { + ret = -errno; + kdbus_printf("unable to malloc()!?\n"); + goto out; + } + + if (dst_id == KDBUS_DST_ID_BROADCAST) + flags |= KDBUS_MSG_SIGNAL; + + memset(msg, 0, size); + msg->flags = flags; + msg->priority = priority; + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = name ? 0 : dst_id; + msg->cookie = cookie; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + if (timeout) { + ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &now); + if (ret < 0) + goto out; + + msg->timeout_ns = now.tv_sec * 1000000000ULL + + now.tv_nsec + timeout; + } + + item = msg->items; + + if (name) { + item->type = KDBUS_ITEM_DST_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + strcpy(item->str, name); + item = KDBUS_ITEM_NEXT(item); + } + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)&ref1; + item->vec.size = sizeof(ref1); + item = KDBUS_ITEM_NEXT(item); + + /* data padding for ref1 */ + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)NULL; + item->vec.size = KDBUS_ALIGN8(sizeof(ref1)) - sizeof(ref1); + item = KDBUS_ITEM_NEXT(item); + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)&ref2; + item->vec.size = sizeof(ref2); + item = KDBUS_ITEM_NEXT(item); + + if (dst_id == KDBUS_DST_ID_BROADCAST) { + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + item->bloom_filter.generation = 0; + } else { + item->type = KDBUS_ITEM_PAYLOAD_MEMFD; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_memfd); + item->memfd.size = 16; + item->memfd.fd = memfd; + } + item = KDBUS_ITEM_NEXT(item); + + size = sizeof(*cmd); + if (cancel_fd != -1) + size += KDBUS_ITEM_SIZE(sizeof(cancel_fd)); + + cmd = malloc(size); + if (!cmd) { + ret = -errno; + kdbus_printf("unable to malloc()!?\n"); + goto out; + } + + cmd->size = size; + cmd->flags = cmd_flags; + cmd->msg_address = (uintptr_t)msg; + + item = cmd->items; + + if (cancel_fd != -1) { + item->type = KDBUS_ITEM_CANCEL_FD; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(cancel_fd); + item->fds[0] = cancel_fd; + item = KDBUS_ITEM_NEXT(item); + } + + ret = kdbus_cmd_send(conn->fd, cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + goto out; + } + + if (cmd_flags & KDBUS_SEND_SYNC_REPLY) { + struct kdbus_msg *reply; + + kdbus_printf("SYNC REPLY @offset %llu:\n", cmd->reply.offset); + reply = (struct kdbus_msg *)(conn->buf + cmd->reply.offset); + kdbus_msg_dump(conn, reply); + + kdbus_msg_free(reply); + + ret = kdbus_free(conn, cmd->reply.offset); + if (ret < 0) + goto out; + } + +out: + free(msg); + free(cmd); + + if (memfd >= 0) + close(memfd); + + return ret < 0 ? ret : 0; +} + +int kdbus_msg_send(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id) +{ + return __kdbus_msg_send(conn, name, cookie, flags, timeout, priority, + dst_id, 0, -1); +} + +int kdbus_msg_send_sync(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id, int cancel_fd) +{ + return __kdbus_msg_send(conn, name, cookie, flags, timeout, priority, + dst_id, KDBUS_SEND_SYNC_REPLY, cancel_fd); +} + +int kdbus_msg_send_reply(const struct kdbus_conn *conn, + uint64_t reply_cookie, + uint64_t dst_id) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_msg *msg; + const char ref1[1024 * 128 + 3] = "0123456789_0"; + struct kdbus_item *item; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = malloc(size); + if (!msg) { + kdbus_printf("unable to malloc()!?\n"); + return -ENOMEM; + } + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = dst_id; + msg->cookie_reply = reply_cookie; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)&ref1; + item->vec.size = sizeof(ref1); + item = KDBUS_ITEM_NEXT(item); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) + kdbus_printf("error sending message: %d (%m)\n", ret); + + free(msg); + + return ret; +} + +static char *msg_id(uint64_t id, char *buf) +{ + if (id == 0) + return "KERNEL"; + if (id == ~0ULL) + return "BROADCAST"; + sprintf(buf, "%llu", (unsigned long long)id); + return buf; +} + +int kdbus_msg_dump(const struct kdbus_conn *conn, const struct kdbus_msg *msg) +{ + const struct kdbus_item *item = msg->items; + char buf_src[32]; + char buf_dst[32]; + uint64_t timeout = 0; + uint64_t cookie_reply = 0; + int ret = 0; + + if (msg->flags & KDBUS_MSG_EXPECT_REPLY) + timeout = msg->timeout_ns; + else + cookie_reply = msg->cookie_reply; + + kdbus_printf("MESSAGE: %s (%llu bytes) flags=0x%08llx, %s → %s, " + "cookie=%llu, timeout=%llu cookie_reply=%llu priority=%lli\n", + enum_PAYLOAD(msg->payload_type), (unsigned long long)msg->size, + (unsigned long long)msg->flags, + msg_id(msg->src_id, buf_src), msg_id(msg->dst_id, buf_dst), + (unsigned long long)msg->cookie, (unsigned long long)timeout, + (unsigned long long)cookie_reply, (long long)msg->priority); + + KDBUS_ITEM_FOREACH(item, msg, items) { + if (item->size < KDBUS_ITEM_HEADER_SIZE) { + kdbus_printf(" +%s (%llu bytes) invalid data record\n", + enum_MSG(item->type), item->size); + ret = -EINVAL; + break; + } + + switch (item->type) { + case KDBUS_ITEM_PAYLOAD_OFF: { + char *s; + + if (item->vec.offset == ~0ULL) + s = "[\\0-bytes]"; + else + s = (char *)msg + item->vec.offset; + + kdbus_printf(" +%s (%llu bytes) off=%llu size=%llu '%s'\n", + enum_MSG(item->type), item->size, + (unsigned long long)item->vec.offset, + (unsigned long long)item->vec.size, s); + break; + } + + case KDBUS_ITEM_FDS: { + int i, n = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + + kdbus_printf(" +%s (%llu bytes, %d fds)\n", + enum_MSG(item->type), item->size, n); + + for (i = 0; i < n; i++) + kdbus_printf(" fd[%d] = %d\n", + i, item->fds[i]); + + break; + } + + case KDBUS_ITEM_PAYLOAD_MEMFD: { + char *buf; + off_t size; + + buf = mmap(NULL, item->memfd.size, PROT_READ, + MAP_PRIVATE, item->memfd.fd, 0); + if (buf == MAP_FAILED) { + kdbus_printf("mmap() fd=%i size=%llu failed: %m\n", + item->memfd.fd, item->memfd.size); + break; + } + + if (sys_memfd_get_size(item->memfd.fd, &size) < 0) { + kdbus_printf("KDBUS_CMD_MEMFD_SIZE_GET failed: %m\n"); + break; + } + + kdbus_printf(" +%s (%llu bytes) fd=%i size=%llu filesize=%llu '%s'\n", + enum_MSG(item->type), item->size, item->memfd.fd, + (unsigned long long)item->memfd.size, + (unsigned long long)size, buf); + munmap(buf, item->memfd.size); + break; + } + + case KDBUS_ITEM_CREDS: + kdbus_printf(" +%s (%llu bytes) uid=%lld, euid=%lld, suid=%lld, fsuid=%lld, " + "gid=%lld, egid=%lld, sgid=%lld, fsgid=%lld\n", + enum_MSG(item->type), item->size, + item->creds.uid, item->creds.euid, + item->creds.suid, item->creds.fsuid, + item->creds.gid, item->creds.egid, + item->creds.sgid, item->creds.fsgid); + break; + + case KDBUS_ITEM_PIDS: + kdbus_printf(" +%s (%llu bytes) pid=%lld, tid=%lld, ppid=%lld\n", + enum_MSG(item->type), item->size, + item->pids.pid, item->pids.tid, + item->pids.ppid); + break; + + case KDBUS_ITEM_AUXGROUPS: { + int i, n; + + kdbus_printf(" +%s (%llu bytes)\n", + enum_MSG(item->type), item->size); + n = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(uint64_t); + + for (i = 0; i < n; i++) + kdbus_printf(" gid[%d] = %lld\n", + i, item->data64[i]); + break; + } + + case KDBUS_ITEM_NAME: + case KDBUS_ITEM_PID_COMM: + case KDBUS_ITEM_TID_COMM: + case KDBUS_ITEM_EXE: + case KDBUS_ITEM_CGROUP: + case KDBUS_ITEM_SECLABEL: + case KDBUS_ITEM_DST_NAME: + case KDBUS_ITEM_CONN_DESCRIPTION: + kdbus_printf(" +%s (%llu bytes) '%s' (%zu)\n", + enum_MSG(item->type), item->size, + item->str, strlen(item->str)); + break; + + case KDBUS_ITEM_OWNED_NAME: { + kdbus_printf(" +%s (%llu bytes) '%s' (%zu) flags=0x%08llx\n", + enum_MSG(item->type), item->size, + item->name.name, strlen(item->name.name), + item->name.flags); + break; + } + + case KDBUS_ITEM_CMDLINE: { + size_t size = item->size - KDBUS_ITEM_HEADER_SIZE; + const char *str = item->str; + int count = 0; + + kdbus_printf(" +%s (%llu bytes) ", + enum_MSG(item->type), item->size); + while (size) { + kdbus_printf("'%s' ", str); + size -= strlen(str) + 1; + str += strlen(str) + 1; + count++; + } + + kdbus_printf("(%d string%s)\n", + count, (count == 1) ? "" : "s"); + break; + } + + case KDBUS_ITEM_AUDIT: + kdbus_printf(" +%s (%llu bytes) loginuid=%u sessionid=%u\n", + enum_MSG(item->type), item->size, + item->audit.loginuid, item->audit.sessionid); + break; + + case KDBUS_ITEM_CAPS: { + const uint32_t *cap; + int n, i; + + kdbus_printf(" +%s (%llu bytes) len=%llu bytes, last_cap %d\n", + enum_MSG(item->type), item->size, + (unsigned long long)item->size - + KDBUS_ITEM_HEADER_SIZE, + (int) item->caps.last_cap); + + cap = item->caps.caps; + n = (item->size - offsetof(struct kdbus_item, caps.caps)) + / 4 / sizeof(uint32_t); + + kdbus_printf(" CapInh="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(0 * n) + (n - i - 1)]); + + kdbus_printf(" CapPrm="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(1 * n) + (n - i - 1)]); + + kdbus_printf(" CapEff="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(2 * n) + (n - i - 1)]); + + kdbus_printf(" CapBnd="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(3 * n) + (n - i - 1)]); + kdbus_printf("\n"); + break; + } + + case KDBUS_ITEM_TIMESTAMP: + kdbus_printf(" +%s (%llu bytes) seq=%llu realtime=%lluns monotonic=%lluns\n", + enum_MSG(item->type), item->size, + (unsigned long long)item->timestamp.seqnum, + (unsigned long long)item->timestamp.realtime_ns, + (unsigned long long)item->timestamp.monotonic_ns); + break; + + case KDBUS_ITEM_REPLY_TIMEOUT: + kdbus_printf(" +%s (%llu bytes) cookie=%llu\n", + enum_MSG(item->type), item->size, + msg->cookie_reply); + break; + + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + kdbus_printf(" +%s (%llu bytes) '%s', old id=%lld, now id=%lld, old_flags=0x%llx new_flags=0x%llx\n", + enum_MSG(item->type), + (unsigned long long) item->size, + item->name_change.name, + item->name_change.old_id.id, + item->name_change.new_id.id, + item->name_change.old_id.flags, + item->name_change.new_id.flags); + break; + + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + kdbus_printf(" +%s (%llu bytes) id=%llu flags=%llu\n", + enum_MSG(item->type), + (unsigned long long) item->size, + (unsigned long long) item->id_change.id, + (unsigned long long) item->id_change.flags); + break; + + default: + kdbus_printf(" +%s (%llu bytes)\n", + enum_MSG(item->type), item->size); + break; + } + } + + if ((char *)item - ((char *)msg + msg->size) >= 8) { + kdbus_printf("invalid padding at end of message\n"); + ret = -EINVAL; + } + + kdbus_printf("\n"); + + return ret; +} + +void kdbus_msg_free(struct kdbus_msg *msg) +{ + const struct kdbus_item *item; + int nfds, i; + + if (!msg) + return; + + KDBUS_ITEM_FOREACH(item, msg, items) { + switch (item->type) { + /* close all memfds */ + case KDBUS_ITEM_PAYLOAD_MEMFD: + close(item->memfd.fd); + break; + case KDBUS_ITEM_FDS: + nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + + for (i = 0; i < nfds; i++) + close(item->fds[i]); + + break; + } + } +} + +int kdbus_msg_recv(struct kdbus_conn *conn, + struct kdbus_msg **msg_out, + uint64_t *offset) +{ + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *msg; + int ret; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret < 0) + return ret; + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + ret = kdbus_msg_dump(conn, msg); + if (ret < 0) { + kdbus_msg_free(msg); + return ret; + } + + if (msg_out) { + *msg_out = msg; + + if (offset) + *offset = recv.msg.offset; + } else { + kdbus_msg_free(msg); + + ret = kdbus_free(conn, recv.msg.offset); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Returns: 0 on success, negative errno on failure. + * + * We must return -ETIMEDOUT, -ECONNREST, -EAGAIN and other errors. + * We must return the result of kdbus_msg_recv() + */ +int kdbus_msg_recv_poll(struct kdbus_conn *conn, + int timeout_ms, + struct kdbus_msg **msg_out, + uint64_t *offset) +{ + int ret; + + do { + struct timeval before, after, diff; + struct pollfd fd; + + fd.fd = conn->fd; + fd.events = POLLIN | POLLPRI | POLLHUP; + fd.revents = 0; + + gettimeofday(&before, NULL); + ret = poll(&fd, 1, timeout_ms); + gettimeofday(&after, NULL); + + if (ret == 0) { + ret = -ETIMEDOUT; + break; + } + + if (ret > 0) { + if (fd.revents & POLLIN) + ret = kdbus_msg_recv(conn, msg_out, offset); + + if (fd.revents & (POLLHUP | POLLERR)) + ret = -ECONNRESET; + } + + if (ret == 0 || ret != -EAGAIN) + break; + + timersub(&after, &before, &diff); + timeout_ms -= diff.tv_sec * 1000UL + + diff.tv_usec / 1000UL; + } while (timeout_ms > 0); + + return ret; +} + +int kdbus_free(const struct kdbus_conn *conn, uint64_t offset) +{ + struct kdbus_cmd_free cmd_free = {}; + int ret; + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = offset; + cmd_free.flags = 0; + + ret = kdbus_cmd_free(conn->fd, &cmd_free); + if (ret < 0) { + kdbus_printf("KDBUS_CMD_FREE failed: %d (%m)\n", ret); + return ret; + } + + return 0; +} + +int kdbus_name_acquire(struct kdbus_conn *conn, + const char *name, uint64_t *flags) +{ + struct kdbus_cmd *cmd_name; + size_t name_len = strlen(name) + 1; + uint64_t size = sizeof(*cmd_name) + KDBUS_ITEM_SIZE(name_len); + struct kdbus_item *item; + int ret; + + cmd_name = alloca(size); + + memset(cmd_name, 0, size); + + item = cmd_name->items; + item->size = KDBUS_ITEM_HEADER_SIZE + name_len; + item->type = KDBUS_ITEM_NAME; + strcpy(item->str, name); + + cmd_name->size = size; + if (flags) + cmd_name->flags = *flags; + + ret = kdbus_cmd_name_acquire(conn->fd, cmd_name); + if (ret < 0) { + kdbus_printf("error aquiring name: %s\n", strerror(-ret)); + return ret; + } + + kdbus_printf("%s(): flags after call: 0x%llx\n", __func__, + cmd_name->return_flags); + + if (flags) + *flags = cmd_name->return_flags; + + return 0; +} + +int kdbus_name_release(struct kdbus_conn *conn, const char *name) +{ + struct kdbus_cmd *cmd_name; + size_t name_len = strlen(name) + 1; + uint64_t size = sizeof(*cmd_name) + KDBUS_ITEM_SIZE(name_len); + struct kdbus_item *item; + int ret; + + cmd_name = alloca(size); + + memset(cmd_name, 0, size); + + item = cmd_name->items; + item->size = KDBUS_ITEM_HEADER_SIZE + name_len; + item->type = KDBUS_ITEM_NAME; + strcpy(item->str, name); + + cmd_name->size = size; + + kdbus_printf("conn %lld giving up name '%s'\n", + (unsigned long long) conn->id, name); + + ret = kdbus_cmd_name_release(conn->fd, cmd_name); + if (ret < 0) { + kdbus_printf("error releasing name: %s\n", strerror(-ret)); + return ret; + } + + return 0; +} + +int kdbus_list(struct kdbus_conn *conn, uint64_t flags) +{ + struct kdbus_cmd_list cmd_list = {}; + struct kdbus_info *list, *name; + int ret; + + cmd_list.size = sizeof(cmd_list); + cmd_list.flags = flags; + + ret = kdbus_cmd_list(conn->fd, &cmd_list); + if (ret < 0) { + kdbus_printf("error listing names: %d (%m)\n", ret); + return ret; + } + + kdbus_printf("REGISTRY:\n"); + list = (struct kdbus_info *)(conn->buf + cmd_list.offset); + + KDBUS_FOREACH(name, list, cmd_list.list_size) { + uint64_t flags = 0; + struct kdbus_item *item; + const char *n = "MISSING-NAME"; + + if (name->size == sizeof(struct kdbus_cmd)) + continue; + + KDBUS_ITEM_FOREACH(item, name, items) + if (item->type == KDBUS_ITEM_OWNED_NAME) { + n = item->name.name; + flags = item->name.flags; + } + + kdbus_printf("%8llu flags=0x%08llx conn=0x%08llx '%s'\n", + name->id, (unsigned long long) flags, + name->flags, n); + } + kdbus_printf("\n"); + + ret = kdbus_free(conn, cmd_list.offset); + + return ret; +} + +int kdbus_conn_update_attach_flags(struct kdbus_conn *conn, + uint64_t attach_flags_send, + uint64_t attach_flags_recv) +{ + int ret; + size_t size; + struct kdbus_cmd *update; + struct kdbus_item *item; + + size = sizeof(struct kdbus_cmd); + size += KDBUS_ITEM_SIZE(sizeof(uint64_t)) * 2; + + update = malloc(size); + if (!update) { + kdbus_printf("error malloc: %m\n"); + return -ENOMEM; + } + + memset(update, 0, size); + update->size = size; + + item = update->items; + + item->type = KDBUS_ITEM_ATTACH_FLAGS_SEND; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t); + item->data64[0] = attach_flags_send; + item = KDBUS_ITEM_NEXT(item); + + item->type = KDBUS_ITEM_ATTACH_FLAGS_RECV; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t); + item->data64[0] = attach_flags_recv; + item = KDBUS_ITEM_NEXT(item); + + ret = kdbus_cmd_update(conn->fd, update); + if (ret < 0) + kdbus_printf("error conn update: %d (%m)\n", ret); + + free(update); + + return ret; +} + +int kdbus_conn_update_policy(struct kdbus_conn *conn, const char *name, + const struct kdbus_policy_access *access, + size_t num_access) +{ + struct kdbus_cmd *update; + struct kdbus_item *item; + size_t i, size; + int ret; + + size = sizeof(struct kdbus_cmd); + size += KDBUS_ITEM_SIZE(strlen(name) + 1); + size += num_access * KDBUS_ITEM_SIZE(sizeof(struct kdbus_policy_access)); + + update = malloc(size); + if (!update) { + kdbus_printf("error malloc: %m\n"); + return -ENOMEM; + } + + memset(update, 0, size); + update->size = size; + + item = update->items; + + item->type = KDBUS_ITEM_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + strcpy(item->str, name); + item = KDBUS_ITEM_NEXT(item); + + for (i = 0; i < num_access; i++) { + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_policy_access); + item->type = KDBUS_ITEM_POLICY_ACCESS; + + item->policy_access.type = access[i].type; + item->policy_access.access = access[i].access; + item->policy_access.id = access[i].id; + + item = KDBUS_ITEM_NEXT(item); + } + + ret = kdbus_cmd_update(conn->fd, update); + if (ret < 0) + kdbus_printf("error conn update: %d (%m)\n", ret); + + free(update); + + return ret; +} + +int kdbus_add_match_id(struct kdbus_conn *conn, uint64_t cookie, + uint64_t type, uint64_t id) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + int ret; + + memset(&buf, 0, sizeof(buf)); + + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = cookie; + buf.item.size = sizeof(buf.item); + buf.item.type = type; + buf.item.chg.id = id; + + ret = kdbus_cmd_match_add(conn->fd, &buf.cmd); + if (ret < 0) + kdbus_printf("--- error adding conn match: %d (%m)\n", ret); + + return ret; +} + +int kdbus_add_match_empty(struct kdbus_conn *conn) +{ + struct { + struct kdbus_cmd_match cmd; + struct kdbus_item item; + } buf; + int ret; + + memset(&buf, 0, sizeof(buf)); + + buf.item.size = sizeof(uint64_t) * 3; + buf.item.type = KDBUS_ITEM_ID; + buf.item.id = KDBUS_MATCH_ID_ANY; + + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(conn->fd, &buf.cmd); + if (ret < 0) + kdbus_printf("--- error adding conn match: %d (%m)\n", ret); + + return ret; +} + +static int all_ids_are_mapped(const char *path) +{ + int ret; + FILE *file; + uint32_t inside_id, length; + + file = fopen(path, "r"); + if (!file) { + ret = -errno; + kdbus_printf("error fopen() %s: %d (%m)\n", + path, ret); + return ret; + } + + ret = fscanf(file, "%u\t%*u\t%u", &inside_id, &length); + if (ret != 2) { + if (ferror(file)) + ret = -errno; + else + ret = -EIO; + + kdbus_printf("--- error fscanf(): %d\n", ret); + fclose(file); + return ret; + } + + fclose(file); + + /* + * If length is 4294967295 which means the invalid uid + * (uid_t) -1 then we are able to map all uid/gids + */ + if (inside_id == 0 && length == (uid_t) -1) + return 1; + + return 0; +} + +int all_uids_gids_are_mapped(void) +{ + int ret; + + ret = all_ids_are_mapped("/proc/self/uid_map"); + if (ret <= 0) { + kdbus_printf("--- error not all uids are mapped\n"); + return 0; + } + + ret = all_ids_are_mapped("/proc/self/gid_map"); + if (ret <= 0) { + kdbus_printf("--- error not all gids are mapped\n"); + return 0; + } + + return 1; +} + +int drop_privileges(uid_t uid, gid_t gid) +{ + int ret; + + ret = setgroups(0, NULL); + if (ret < 0) { + ret = -errno; + kdbus_printf("error setgroups: %d (%m)\n", ret); + return ret; + } + + ret = setresgid(gid, gid, gid); + if (ret < 0) { + ret = -errno; + kdbus_printf("error setresgid: %d (%m)\n", ret); + return ret; + } + + ret = setresuid(uid, uid, uid); + if (ret < 0) { + ret = -errno; + kdbus_printf("error setresuid: %d (%m)\n", ret); + return ret; + } + + return ret; +} + +uint64_t now(clockid_t clock) +{ + struct timespec spec; + + clock_gettime(clock, &spec); + return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec; +} + +char *unique_name(const char *prefix) +{ + unsigned int i; + uint64_t u_now; + char n[17]; + char *str; + int r; + + /* + * This returns a random string which is guaranteed to be + * globally unique across all calls to unique_name(). We + * compose the string as: + * <prefix>-<random>-<time> + * With: + * <prefix>: string provided by the caller + * <random>: a random alpha string of 16 characters + * <time>: the current time in micro-seconds since last boot + * + * The <random> part makes the string always look vastly different, + * the <time> part makes sure no two calls return the same string. + */ + + u_now = now(CLOCK_MONOTONIC); + + for (i = 0; i < sizeof(n) - 1; ++i) + n[i] = 'a' + (rand() % ('z' - 'a')); + n[sizeof(n) - 1] = 0; + + r = asprintf(&str, "%s-%s-%" PRIu64, prefix, n, u_now); + if (r < 0) + return NULL; + + return str; +} + +static int do_userns_map_id(pid_t pid, + const char *map_file, + const char *map_id) +{ + int ret; + int fd; + char *map; + unsigned int i; + + map = strndupa(map_id, strlen(map_id)); + if (!map) { + ret = -errno; + kdbus_printf("error strndupa %s: %d (%m)\n", + map_file, ret); + return ret; + } + + for (i = 0; i < strlen(map); i++) + if (map[i] == ',') + map[i] = '\n'; + + fd = open(map_file, O_RDWR); + if (fd < 0) { + ret = -errno; + kdbus_printf("error open %s: %d (%m)\n", + map_file, ret); + return ret; + } + + ret = write(fd, map, strlen(map)); + if (ret < 0) { + ret = -errno; + kdbus_printf("error write to %s: %d (%m)\n", + map_file, ret); + goto out; + } + + ret = 0; + +out: + close(fd); + return ret; +} + +int userns_map_uid_gid(pid_t pid, + const char *map_uid, + const char *map_gid) +{ + int fd, ret; + char file_id[128] = {'\0'}; + + snprintf(file_id, sizeof(file_id), "/proc/%ld/uid_map", + (long) pid); + + ret = do_userns_map_id(pid, file_id, map_uid); + if (ret < 0) + return ret; + + snprintf(file_id, sizeof(file_id), "/proc/%ld/setgroups", + (long) pid); + + fd = open(file_id, O_WRONLY); + if (fd >= 0) { + write(fd, "deny\n", 5); + close(fd); + } + + snprintf(file_id, sizeof(file_id), "/proc/%ld/gid_map", + (long) pid); + + return do_userns_map_id(pid, file_id, map_gid); +} + +static int do_cap_get_flag(cap_t caps, cap_value_t cap) +{ + int ret; + cap_flag_value_t flag_set; + + ret = cap_get_flag(caps, cap, CAP_EFFECTIVE, &flag_set); + if (ret < 0) { + ret = -errno; + kdbus_printf("error cap_get_flag(): %d (%m)\n", ret); + return ret; + } + + return (flag_set == CAP_SET); +} + +/* + * Returns: + * 1 in case all the requested effective capabilities are set. + * 0 in case we do not have the requested capabilities. This value + * will be used to abort tests with TEST_SKIP + * Negative errno on failure. + * + * Terminate args with a negative value. + */ +int test_is_capable(int cap, ...) +{ + int ret; + va_list ap; + cap_t caps; + + caps = cap_get_proc(); + if (!caps) { + ret = -errno; + kdbus_printf("error cap_get_proc(): %d (%m)\n", ret); + return ret; + } + + ret = do_cap_get_flag(caps, (cap_value_t)cap); + if (ret <= 0) + goto out; + + va_start(ap, cap); + while ((cap = va_arg(ap, int)) > 0) { + ret = do_cap_get_flag(caps, (cap_value_t)cap); + if (ret <= 0) + break; + } + va_end(ap); + +out: + cap_free(caps); + return ret; +} + +int config_user_ns_is_enabled(void) +{ + return (access("/proc/self/uid_map", F_OK) == 0); +} + +int config_auditsyscall_is_enabled(void) +{ + return (access("/proc/self/loginuid", F_OK) == 0); +} + +int config_cgroups_is_enabled(void) +{ + return (access("/proc/self/cgroup", F_OK) == 0); +} + +int config_security_is_enabled(void) +{ + int fd; + int ret; + char buf[128]; + + /* CONFIG_SECURITY is disabled */ + if (access("/proc/self/attr/current", F_OK) != 0) + return 0; + + /* + * Now only if read() fails with -EINVAL then we assume + * that SECLABEL and LSM are disabled + */ + fd = open("/proc/self/attr/current", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return 1; + + ret = read(fd, buf, sizeof(buf)); + if (ret == -1 && errno == EINVAL) + ret = 0; + else + ret = 1; + + close(fd); + + return ret; +} diff --git a/tools/testing/selftests/kdbus/kdbus-util.h b/tools/testing/selftests/kdbus/kdbus-util.h new file mode 100644 index 000000000..e1e18b92f --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-util.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Daniel Mack + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#pragma once + +#define BIT(X) (1 << (X)) + +#include <time.h> +#include <stdbool.h> +#include <linux/kdbus.h> + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#define KDBUS_PTR(addr) ((void *)(uintptr_t)(addr)) + +#define KDBUS_ALIGN8(l) (((l) + 7) & ~7) +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) KDBUS_ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) + +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))((uint8_t *)(item) + KDBUS_ALIGN8((item)->size)) +#define KDBUS_ITEM_FOREACH(item, head, first) \ + for ((item) = (head)->first; \ + ((uint8_t *)(item) < (uint8_t *)(head) + (head)->size) && \ + ((uint8_t *)(item) >= (uint8_t *)(head)); \ + (item) = KDBUS_ITEM_NEXT(item)) +#define KDBUS_FOREACH(iter, first, _size) \ + for ((iter) = (first); \ + ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ + ((uint8_t *)(iter) >= (uint8_t *)(first)); \ + (iter) = (void *)((uint8_t *)(iter) + KDBUS_ALIGN8((iter)->size))) + +#define _KDBUS_ATTACH_BITS_SET_NR (__builtin_popcountll(_KDBUS_ATTACH_ALL)) + +/* Sum of KDBUS_ITEM_* that reflects _KDBUS_ATTACH_ALL */ +#define KDBUS_ATTACH_ITEMS_TYPE_SUM \ + ((((_KDBUS_ATTACH_BITS_SET_NR - 1) * \ + ((_KDBUS_ATTACH_BITS_SET_NR - 1) + 1)) / 2) + \ + (_KDBUS_ITEM_ATTACH_BASE * _KDBUS_ATTACH_BITS_SET_NR)) + +#define POOL_SIZE (16 * 1024LU * 1024LU) + +#define UNPRIV_UID 65534 +#define UNPRIV_GID 65534 + +/* Dump as user of process, useful for user namespace testing */ +#define SUID_DUMP_USER 1 + +extern int kdbus_util_verbose; + +#define kdbus_printf(X...) \ + if (kdbus_util_verbose) \ + printf(X) + +#define RUN_UNPRIVILEGED(child_uid, child_gid, _child_, _parent_) ({ \ + pid_t pid, rpid; \ + int ret; \ + \ + pid = fork(); \ + if (pid == 0) { \ + ret = drop_privileges(child_uid, child_gid); \ + ASSERT_EXIT_VAL(ret == 0, ret); \ + \ + _child_; \ + _exit(0); \ + } else if (pid > 0) { \ + _parent_; \ + rpid = waitpid(pid, &ret, 0); \ + ASSERT_RETURN(rpid == pid); \ + ASSERT_RETURN(WIFEXITED(ret)); \ + ASSERT_RETURN(WEXITSTATUS(ret) == 0); \ + ret = TEST_OK; \ + } else { \ + ret = pid; \ + } \ + \ + ret; \ + }) + +#define RUN_UNPRIVILEGED_CONN(_var_, _bus_, _code_) \ + RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ \ + struct kdbus_conn *_var_; \ + _var_ = kdbus_hello(_bus_, 0, NULL, 0); \ + ASSERT_EXIT(_var_); \ + _code_; \ + kdbus_conn_free(_var_); \ + }), ({ 0; })) + +#define RUN_CLONE_CHILD(clone_ret, flags, _setup_, _child_body_, \ + _parent_setup_, _parent_body_) ({ \ + pid_t pid, rpid; \ + int ret; \ + int efd = -1; \ + \ + _setup_; \ + efd = eventfd(0, EFD_CLOEXEC); \ + ASSERT_RETURN(efd >= 0); \ + *(clone_ret) = 0; \ + pid = syscall(__NR_clone, flags, NULL); \ + if (pid == 0) { \ + eventfd_t event_status = 0; \ + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); \ + ASSERT_EXIT(ret == 0); \ + ret = eventfd_read(efd, &event_status); \ + if (ret < 0 || event_status != 1) { \ + kdbus_printf("error eventfd_read()\n"); \ + _exit(EXIT_FAILURE); \ + } \ + _child_body_; \ + _exit(0); \ + } else if (pid > 0) { \ + _parent_setup_; \ + ret = eventfd_write(efd, 1); \ + ASSERT_RETURN(ret >= 0); \ + _parent_body_; \ + rpid = waitpid(pid, &ret, 0); \ + ASSERT_RETURN(rpid == pid); \ + ASSERT_RETURN(WIFEXITED(ret)); \ + ASSERT_RETURN(WEXITSTATUS(ret) == 0); \ + ret = TEST_OK; \ + } else { \ + ret = -errno; \ + *(clone_ret) = -errno; \ + } \ + close(efd); \ + ret; \ +}) + +/* Enums for parent if it should drop privs or not */ +enum kdbus_drop_parent { + DO_NOT_DROP, + DROP_SAME_UNPRIV, + DROP_OTHER_UNPRIV, +}; + +struct kdbus_conn { + int fd; + uint64_t id; + unsigned char *buf; +}; + +int kdbus_sysfs_get_parameter_mask(const char *path, uint64_t *mask); +int kdbus_sysfs_set_parameter_mask(const char *path, uint64_t mask); + +int sys_memfd_create(const char *name, __u64 size); +int sys_memfd_seal_set(int fd); +off_t sys_memfd_get_size(int fd, off_t *size); + +int kdbus_list(struct kdbus_conn *conn, uint64_t flags); +int kdbus_name_release(struct kdbus_conn *conn, const char *name); +int kdbus_name_acquire(struct kdbus_conn *conn, const char *name, + uint64_t *flags); +void kdbus_msg_free(struct kdbus_msg *msg); +int kdbus_msg_recv(struct kdbus_conn *conn, + struct kdbus_msg **msg, uint64_t *offset); +int kdbus_msg_recv_poll(struct kdbus_conn *conn, int timeout_ms, + struct kdbus_msg **msg_out, uint64_t *offset); +int kdbus_free(const struct kdbus_conn *conn, uint64_t offset); +int kdbus_msg_dump(const struct kdbus_conn *conn, + const struct kdbus_msg *msg); +int kdbus_create_bus(int control_fd, const char *name, + uint64_t owner_meta, char **path); +int kdbus_msg_send(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id); +int kdbus_msg_send_sync(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id, int cancel_fd); +int kdbus_msg_send_reply(const struct kdbus_conn *conn, + uint64_t reply_cookie, + uint64_t dst_id); +struct kdbus_conn *kdbus_hello(const char *path, uint64_t hello_flags, + const struct kdbus_item *item, + size_t item_size); +struct kdbus_conn *kdbus_hello_registrar(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access, uint64_t flags); +struct kdbus_conn *kdbus_hello_activator(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access); +bool kdbus_item_in_message(struct kdbus_msg *msg, uint64_t type); +int kdbus_bus_creator_info(struct kdbus_conn *conn, + uint64_t flags, + uint64_t *offset); +int kdbus_conn_info(struct kdbus_conn *conn, uint64_t id, + const char *name, uint64_t flags, uint64_t *offset); +void kdbus_conn_free(struct kdbus_conn *conn); +int kdbus_conn_update_attach_flags(struct kdbus_conn *conn, + uint64_t attach_flags_send, + uint64_t attach_flags_recv); +int kdbus_conn_update_policy(struct kdbus_conn *conn, const char *name, + const struct kdbus_policy_access *access, + size_t num_access); + +int kdbus_add_match_id(struct kdbus_conn *conn, uint64_t cookie, + uint64_t type, uint64_t id); +int kdbus_add_match_empty(struct kdbus_conn *conn); + +int all_uids_gids_are_mapped(void); +int drop_privileges(uid_t uid, gid_t gid); +uint64_t now(clockid_t clock); +char *unique_name(const char *prefix); + +int userns_map_uid_gid(pid_t pid, const char *map_uid, const char *map_gid); +int test_is_capable(int cap, ...); +int config_user_ns_is_enabled(void); +int config_auditsyscall_is_enabled(void); +int config_cgroups_is_enabled(void); +int config_security_is_enabled(void); diff --git a/tools/testing/selftests/kdbus/test-activator.c b/tools/testing/selftests/kdbus/test-activator.c new file mode 100644 index 000000000..3d1b76370 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-activator.c @@ -0,0 +1,318 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <sys/capability.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static int kdbus_starter_poll(struct kdbus_conn *conn) +{ + int ret; + struct pollfd fd; + + fd.fd = conn->fd; + fd.events = POLLIN | POLLPRI | POLLHUP; + fd.revents = 0; + + ret = poll(&fd, 1, 100); + if (ret == 0) + return -ETIMEDOUT; + else if (ret > 0) { + if (fd.revents & POLLIN) + return 0; + + if (fd.revents & (POLLHUP | POLLERR)) + ret = -ECONNRESET; + } + + return ret; +} + +/* Ensure that kdbus activator logic is safe */ +static int kdbus_priv_activator(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_msg *msg = NULL; + uint64_t cookie = 0xdeadbeef; + uint64_t flags = KDBUS_NAME_REPLACE_EXISTING; + struct kdbus_conn *activator; + struct kdbus_conn *service; + struct kdbus_conn *client; + struct kdbus_conn *holder; + struct kdbus_policy_access *access; + + access = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = getuid(), + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = getuid(), + .access = KDBUS_POLICY_TALK, + }, + }; + + activator = kdbus_hello_activator(env->buspath, "foo.priv.activator", + access, 2); + ASSERT_RETURN(activator); + + service = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(service); + + client = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(client); + + /* + * Make sure that other users can't TALK to the activator + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk using the ID */ + ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef, 0, 0, + 0, activator->id); + ASSERT_EXIT(ret == -ENXIO); + + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + 0xdeadbeef, 0, 0, 0, + KDBUS_DST_ID_NAME); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure that we did not receive anything, so the + * service will not be started automatically + */ + + ret = kdbus_starter_poll(activator); + ASSERT_RETURN(ret == -ETIMEDOUT); + + /* + * Now try to emulate the starter/service logic and + * acquire the name. + */ + + cookie++; + ret = kdbus_msg_send(service, "foo.priv.activator", cookie, + 0, 0, 0, KDBUS_DST_ID_NAME); + ASSERT_RETURN(ret == 0); + + ret = kdbus_starter_poll(activator); + ASSERT_RETURN(ret == 0); + + /* Policies are still checked, access denied */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "foo.priv.activator", + &flags); + ASSERT_RETURN(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_name_acquire(service, "foo.priv.activator", + &flags); + ASSERT_RETURN(ret == 0); + + /* We read our previous starter message */ + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + /* Try to talk, we still fail */ + + cookie++; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + cookie, 0, 0, 0, + KDBUS_DST_ID_NAME); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* Still nothing to read */ + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + /* We receive every thing now */ + + cookie++; + ret = kdbus_msg_send(client, "foo.priv.activator", cookie, + 0, 0, 0, KDBUS_DST_ID_NAME); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_recv_poll(service, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* Policies default to deny TALK now */ + kdbus_conn_free(activator); + + cookie++; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + cookie, 0, 0, 0, + KDBUS_DST_ID_NAME); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + /* Same user is able to TALK */ + cookie++; + ret = kdbus_msg_send(client, "foo.priv.activator", cookie, + 0, 0, 0, KDBUS_DST_ID_NAME); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_recv_poll(service, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + access = (struct kdbus_policy_access []){ + { + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = getuid(), + .access = KDBUS_POLICY_TALK, + }, + }; + + holder = kdbus_hello_registrar(env->buspath, "foo.priv.activator", + access, 1, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder); + + /* Now we are able to TALK to the name */ + + cookie++; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + cookie, 0, 0, 0, + KDBUS_DST_ID_NAME); + ASSERT_EXIT(ret == 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "foo.priv.activator", + &flags); + ASSERT_RETURN(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + kdbus_conn_free(service); + kdbus_conn_free(client); + kdbus_conn_free(holder); + + return 0; +} + +int kdbus_test_activator(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_conn *activator; + struct pollfd fds[2]; + bool activator_done = false; + struct kdbus_policy_access access[2]; + + access[0].type = KDBUS_POLICY_ACCESS_USER; + access[0].id = getuid(); + access[0].access = KDBUS_POLICY_OWN; + + access[1].type = KDBUS_POLICY_ACCESS_WORLD; + access[1].access = KDBUS_POLICY_TALK; + + activator = kdbus_hello_activator(env->buspath, "foo.test.activator", + access, 2); + ASSERT_RETURN(activator); + + ret = kdbus_add_match_empty(env->conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_list(env->conn, KDBUS_LIST_NAMES | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_send(env->conn, "foo.test.activator", 0xdeafbeef, + 0, 0, 0, KDBUS_DST_ID_NAME); + ASSERT_RETURN(ret == 0); + + fds[0].fd = activator->fd; + fds[1].fd = env->conn->fd; + + kdbus_printf("-- entering poll loop ...\n"); + + for (;;) { + int i, nfds = sizeof(fds) / sizeof(fds[0]); + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 3000); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_list(env->conn, KDBUS_LIST_NAMES); + ASSERT_RETURN(ret == 0); + + if ((fds[0].revents & POLLIN) && !activator_done) { + uint64_t flags = KDBUS_NAME_REPLACE_EXISTING; + + kdbus_printf("Starter was called back!\n"); + + ret = kdbus_name_acquire(env->conn, + "foo.test.activator", &flags); + ASSERT_RETURN(ret == 0); + + activator_done = true; + } + + if (fds[1].revents & POLLIN) { + kdbus_msg_recv(env->conn, NULL, NULL); + break; + } + } + + /* Check if all uids/gids are mapped */ + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + /* Check now capabilities, so we run the previous tests */ + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + if (!ret) + return TEST_SKIP; + + ret = kdbus_priv_activator(env); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(activator); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-benchmark.c b/tools/testing/selftests/kdbus/test-benchmark.c new file mode 100644 index 000000000..8a9744b00 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-benchmark.c @@ -0,0 +1,451 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <locale.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <sys/time.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <math.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#define SERVICE_NAME "foo.bar.echo" + +/* + * To have a banchmark comparison with unix socket, set: + * user_memfd = false; + * compare_uds = true; + * attach_none = true; do not attached metadata + */ + +static bool use_memfd = true; /* transmit memfd? */ +static bool compare_uds = false; /* unix-socket comparison? */ +static bool attach_none = false; /* clear attach-flags? */ +static char stress_payload[8192]; + +struct stats { + uint64_t count; + uint64_t latency_acc; + uint64_t latency_low; + uint64_t latency_high; + uint64_t latency_avg; + uint64_t latency_ssquares; +}; + +static struct stats stats; + +static void reset_stats(void) +{ + stats.count = 0; + stats.latency_acc = 0; + stats.latency_low = UINT64_MAX; + stats.latency_high = 0; + stats.latency_avg = 0; + stats.latency_ssquares = 0; +} + +static void dump_stats(bool is_uds) +{ + if (stats.count > 0) { + kdbus_printf("stats %s: %'llu packets processed, latency (nsecs) min/max/avg/dev %'7llu // %'7llu // %'7llu // %'7.f\n", + is_uds ? " (UNIX)" : "(KDBUS)", + (unsigned long long) stats.count, + (unsigned long long) stats.latency_low, + (unsigned long long) stats.latency_high, + (unsigned long long) stats.latency_avg, + sqrt(stats.latency_ssquares / stats.count)); + } else { + kdbus_printf("*** no packets received. bus stuck?\n"); + } +} + +static void add_stats(uint64_t prev) +{ + uint64_t diff, latency_avg_prev; + + diff = now(CLOCK_THREAD_CPUTIME_ID) - prev; + + stats.count++; + stats.latency_acc += diff; + + /* see Welford62 */ + latency_avg_prev = stats.latency_avg; + stats.latency_avg = stats.latency_acc / stats.count; + stats.latency_ssquares += (diff - latency_avg_prev) * (diff - stats.latency_avg); + + if (stats.latency_low > diff) + stats.latency_low = diff; + + if (stats.latency_high < diff) + stats.latency_high = diff; +} + +static int setup_simple_kdbus_msg(struct kdbus_conn *conn, + uint64_t dst_id, + struct kdbus_msg **msg_out) +{ + struct kdbus_msg *msg; + struct kdbus_item *item; + uint64_t size; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t) stress_payload; + item->vec.size = sizeof(stress_payload); + item = KDBUS_ITEM_NEXT(item); + + *msg_out = msg; + + return 0; +} + +static int setup_memfd_kdbus_msg(struct kdbus_conn *conn, + uint64_t dst_id, + off_t *memfd_item_offset, + struct kdbus_msg **msg_out) +{ + struct kdbus_msg *msg; + struct kdbus_item *item; + uint64_t size; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t) stress_payload; + item->vec.size = sizeof(stress_payload); + item = KDBUS_ITEM_NEXT(item); + + item->type = KDBUS_ITEM_PAYLOAD_MEMFD; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_memfd); + item->memfd.size = sizeof(uint64_t); + + *memfd_item_offset = (unsigned char *)item - (unsigned char *)msg; + *msg_out = msg; + + return 0; +} + +static int +send_echo_request(struct kdbus_conn *conn, uint64_t dst_id, + void *kdbus_msg, off_t memfd_item_offset) +{ + struct kdbus_cmd_send cmd = {}; + int memfd = -1; + int ret; + + if (use_memfd) { + uint64_t now_ns = now(CLOCK_THREAD_CPUTIME_ID); + struct kdbus_item *item = memfd_item_offset + kdbus_msg; + memfd = sys_memfd_create("memfd-name", 0); + ASSERT_RETURN_VAL(memfd >= 0, memfd); + + ret = write(memfd, &now_ns, sizeof(now_ns)); + ASSERT_RETURN_VAL(ret == sizeof(now_ns), -EAGAIN); + + ret = sys_memfd_seal_set(memfd); + ASSERT_RETURN_VAL(ret == 0, -errno); + + item->memfd.fd = memfd; + } + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)kdbus_msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + ASSERT_RETURN_VAL(ret == 0, ret); + + close(memfd); + + return 0; +} + +static int +handle_echo_reply(struct kdbus_conn *conn, uint64_t send_ns) +{ + int ret; + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *msg; + const struct kdbus_item *item; + bool has_memfd = false; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret == -EAGAIN) + return ret; + + ASSERT_RETURN_VAL(ret == 0, ret); + + if (!use_memfd) + goto out; + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + + KDBUS_ITEM_FOREACH(item, msg, items) { + switch (item->type) { + case KDBUS_ITEM_PAYLOAD_MEMFD: { + char *buf; + + buf = mmap(NULL, item->memfd.size, PROT_READ, + MAP_PRIVATE, item->memfd.fd, 0); + ASSERT_RETURN_VAL(buf != MAP_FAILED, -EINVAL); + ASSERT_RETURN_VAL(item->memfd.size == sizeof(uint64_t), + -EINVAL); + + add_stats(*(uint64_t*)buf); + munmap(buf, item->memfd.size); + close(item->memfd.fd); + has_memfd = true; + break; + } + + case KDBUS_ITEM_PAYLOAD_OFF: + /* ignore */ + break; + } + } + +out: + if (!has_memfd) + add_stats(send_ns); + + ret = kdbus_free(conn, recv.msg.offset); + ASSERT_RETURN_VAL(ret == 0, -errno); + + return 0; +} + +static int benchmark(struct kdbus_test_env *env) +{ + static char buf[sizeof(stress_payload)]; + struct kdbus_msg *kdbus_msg = NULL; + off_t memfd_cached_offset = 0; + int ret; + struct kdbus_conn *conn_a, *conn_b; + struct pollfd fds[2]; + uint64_t start, send_ns, now_ns, diff; + unsigned int i; + int uds[2]; + + setlocale(LC_ALL, ""); + + for (i = 0; i < sizeof(stress_payload); i++) + stress_payload[i] = i; + + /* setup kdbus pair */ + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + ret = kdbus_add_match_empty(conn_a); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(conn_b); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, SERVICE_NAME, NULL); + ASSERT_RETURN(ret == 0); + + if (attach_none) { + ret = kdbus_conn_update_attach_flags(conn_a, + _KDBUS_ATTACH_ALL, + 0); + ASSERT_RETURN(ret == 0); + } + + /* setup UDS pair */ + + ret = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, uds); + ASSERT_RETURN(ret == 0); + + /* setup a kdbus msg now */ + if (use_memfd) { + ret = setup_memfd_kdbus_msg(conn_b, conn_a->id, + &memfd_cached_offset, + &kdbus_msg); + ASSERT_RETURN(ret == 0); + } else { + ret = setup_simple_kdbus_msg(conn_b, conn_a->id, &kdbus_msg); + ASSERT_RETURN(ret == 0); + } + + /* start benchmark */ + + kdbus_printf("-- entering poll loop ...\n"); + + do { + /* run kdbus benchmark */ + fds[0].fd = conn_a->fd; + fds[1].fd = conn_b->fd; + + /* cancel any pending message */ + handle_echo_reply(conn_a, 0); + + start = now(CLOCK_THREAD_CPUTIME_ID); + reset_stats(); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = send_echo_request(conn_b, conn_a->id, + kdbus_msg, memfd_cached_offset); + ASSERT_RETURN(ret == 0); + + while (1) { + unsigned int nfds = sizeof(fds) / sizeof(fds[0]); + unsigned int i; + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 10); + if (ret < 0) + break; + + if (fds[0].revents & POLLIN) { + ret = handle_echo_reply(conn_a, send_ns); + ASSERT_RETURN(ret == 0); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = send_echo_request(conn_b, conn_a->id, + kdbus_msg, + memfd_cached_offset); + ASSERT_RETURN(ret == 0); + } + + now_ns = now(CLOCK_THREAD_CPUTIME_ID); + diff = now_ns - start; + if (diff > 1000000000ULL) { + start = now_ns; + + dump_stats(false); + break; + } + } + + if (!compare_uds) + continue; + + /* run unix-socket benchmark as comparison */ + + fds[0].fd = uds[0]; + fds[1].fd = uds[1]; + + /* cancel any pendign message */ + read(uds[1], buf, sizeof(buf)); + + start = now(CLOCK_THREAD_CPUTIME_ID); + reset_stats(); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = write(uds[0], stress_payload, sizeof(stress_payload)); + ASSERT_RETURN(ret == sizeof(stress_payload)); + + while (1) { + unsigned int nfds = sizeof(fds) / sizeof(fds[0]); + unsigned int i; + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 10); + if (ret < 0) + break; + + if (fds[1].revents & POLLIN) { + ret = read(uds[1], buf, sizeof(buf)); + ASSERT_RETURN(ret == sizeof(buf)); + + add_stats(send_ns); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = write(uds[0], buf, sizeof(buf)); + ASSERT_RETURN(ret == sizeof(buf)); + } + + now_ns = now(CLOCK_THREAD_CPUTIME_ID); + diff = now_ns - start; + if (diff > 1000000000ULL) { + start = now_ns; + + dump_stats(true); + break; + } + } + + } while (kdbus_util_verbose); + + kdbus_printf("-- closing bus connections\n"); + + free(kdbus_msg); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return (stats.count > 1) ? TEST_OK : TEST_ERR; +} + +int kdbus_test_benchmark(struct kdbus_test_env *env) +{ + use_memfd = true; + attach_none = false; + compare_uds = false; + return benchmark(env); +} + +int kdbus_test_benchmark_nomemfds(struct kdbus_test_env *env) +{ + use_memfd = false; + attach_none = false; + compare_uds = false; + return benchmark(env); +} + +int kdbus_test_benchmark_uds(struct kdbus_test_env *env) +{ + use_memfd = false; + attach_none = true; + compare_uds = true; + return benchmark(env); +} diff --git a/tools/testing/selftests/kdbus/test-bus.c b/tools/testing/selftests/kdbus/test-bus.c new file mode 100644 index 000000000..762fb3039 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-bus.c @@ -0,0 +1,175 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <sys/mman.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +static struct kdbus_item *kdbus_get_item(struct kdbus_info *info, + uint64_t type) +{ + struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == type) + return item; + + return NULL; +} + +static int test_bus_creator_info(const char *bus_path) +{ + int ret; + uint64_t offset; + struct kdbus_conn *conn; + struct kdbus_info *info; + struct kdbus_item *item; + char *tmp, *busname; + + /* extract the bus-name from @bus_path */ + tmp = strdup(bus_path); + ASSERT_RETURN(tmp); + busname = strrchr(tmp, '/'); + ASSERT_RETURN(busname); + *busname = 0; + busname = strrchr(tmp, '/'); + ASSERT_RETURN(busname); + ++busname; + + conn = kdbus_hello(bus_path, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_bus_creator_info(conn, _KDBUS_ATTACH_ALL, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + + item = kdbus_get_item(info, KDBUS_ITEM_MAKE_NAME); + ASSERT_RETURN(item); + ASSERT_RETURN(!strcmp(item->str, busname)); + + ret = kdbus_free(conn, offset); + ASSERT_RETURN_VAL(ret == 0, ret); + + free(tmp); + kdbus_conn_free(conn); + return 0; +} + +int kdbus_test_bus_make(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd cmd; + + /* bloom size item */ + struct { + uint64_t size; + uint64_t type; + struct kdbus_bloom_parameter bloom; + } bs; + + /* name item */ + uint64_t n_size; + uint64_t n_type; + char name[64]; + } bus_make; + char s[PATH_MAX], *name; + int ret, control_fd2; + uid_t uid; + + name = unique_name(""); + ASSERT_RETURN(name); + + snprintf(s, sizeof(s), "%s/control", env->root); + env->control_fd = open(s, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(env->control_fd >= 0); + + control_fd2 = open(s, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(control_fd2 >= 0); + + memset(&bus_make, 0, sizeof(bus_make)); + + bus_make.bs.size = sizeof(bus_make.bs); + bus_make.bs.type = KDBUS_ITEM_BLOOM_PARAMETER; + bus_make.bs.bloom.size = 64; + bus_make.bs.bloom.n_hash = 1; + + bus_make.n_type = KDBUS_ITEM_MAKE_NAME; + + uid = getuid(); + + /* missing uid prefix */ + snprintf(bus_make.name, sizeof(bus_make.name), "foo"); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EINVAL); + + /* non alphanumeric character */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-blah@123", uid); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EINVAL); + + /* '-' at the end */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-blah-", uid); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EINVAL); + + /* create a new bus */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-1", uid, name); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == 0); + + ret = kdbus_cmd_bus_make(control_fd2, &bus_make.cmd); + ASSERT_RETURN(ret == -EEXIST); + + snprintf(s, sizeof(s), "%s/%u-%s-1/bus", env->root, uid, name); + ASSERT_RETURN(access(s, F_OK) == 0); + + ret = test_bus_creator_info(s); + ASSERT_RETURN(ret == 0); + + /* can't use the same fd for bus make twice, even though a different + * bus name is used + */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-2", uid, name); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EBADFD); + + /* create a new bus, with different fd and different bus name */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-2", uid, name); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(control_fd2, &bus_make.cmd); + ASSERT_RETURN(ret == 0); + + close(control_fd2); + free(name); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-chat.c b/tools/testing/selftests/kdbus/test-chat.c new file mode 100644 index 000000000..71a92d8b7 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-chat.c @@ -0,0 +1,122 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <stdbool.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int kdbus_test_chat(struct kdbus_test_env *env) +{ + int ret, cookie; + struct kdbus_conn *conn_a, *conn_b; + struct pollfd fds[2]; + uint64_t flags; + int count; + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + flags = KDBUS_NAME_ALLOW_REPLACEMENT; + ret = kdbus_name_acquire(conn_a, "foo.bar.test", &flags); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, "foo.bar.baz", NULL); + ASSERT_RETURN(ret == 0); + + flags = KDBUS_NAME_QUEUE; + ret = kdbus_name_acquire(conn_b, "foo.bar.baz", &flags); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, "foo.bar.double", NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, "foo.bar.double", NULL); + ASSERT_RETURN(ret == -EALREADY); + + ret = kdbus_name_release(conn_a, "foo.bar.double"); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_release(conn_a, "foo.bar.double"); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_list(conn_b, KDBUS_LIST_UNIQUE | + KDBUS_LIST_NAMES | + KDBUS_LIST_QUEUED | + KDBUS_LIST_ACTIVATORS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(conn_a); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(conn_b); + ASSERT_RETURN(ret == 0); + + cookie = 0; + ret = kdbus_msg_send(conn_b, NULL, 0xc0000000 | cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + fds[0].fd = conn_a->fd; + fds[1].fd = conn_b->fd; + + kdbus_printf("-- entering poll loop ...\n"); + + for (count = 0;; count++) { + int i, nfds = sizeof(fds) / sizeof(fds[0]); + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 3000); + ASSERT_RETURN(ret >= 0); + + if (fds[0].revents & POLLIN) { + if (count > 2) + kdbus_name_release(conn_a, "foo.bar.baz"); + + ret = kdbus_msg_recv(conn_a, NULL, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(conn_a, NULL, + 0xc0000000 | cookie++, + 0, 0, 0, conn_b->id); + ASSERT_RETURN(ret == 0); + } + + if (fds[1].revents & POLLIN) { + ret = kdbus_msg_recv(conn_b, NULL, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(conn_b, NULL, + 0xc0000000 | cookie++, + 0, 0, 0, conn_a->id); + ASSERT_RETURN(ret == 0); + } + + ret = kdbus_list(conn_b, KDBUS_LIST_UNIQUE | + KDBUS_LIST_NAMES | + KDBUS_LIST_QUEUED | + KDBUS_LIST_ACTIVATORS); + ASSERT_RETURN(ret == 0); + + if (count > 10) + break; + } + + kdbus_printf("-- closing bus connections\n"); + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-connection.c b/tools/testing/selftests/kdbus/test-connection.c new file mode 100644 index 000000000..4688ce8ec --- /dev/null +++ b/tools/testing/selftests/kdbus/test-connection.c @@ -0,0 +1,597 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +int kdbus_test_hello(struct kdbus_test_env *env) +{ + struct kdbus_cmd_free cmd_free = {}; + struct kdbus_cmd_hello hello; + int fd, ret; + + memset(&hello, 0, sizeof(hello)); + + fd = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(fd >= 0); + + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + hello.attach_flags_recv = _KDBUS_ATTACH_ALL; + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + + /* an unaligned hello must result in -EFAULT */ + ret = kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) ((char *) &hello + 1)); + ASSERT_RETURN(ret == -EFAULT); + + /* a size of 0 must return EMSGSIZE */ + hello.size = 1; + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + hello.size = sizeof(struct kdbus_cmd_hello); + + /* check faulty flags */ + hello.flags = 1ULL << 32; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + /* check for faulty pool sizes */ + hello.pool_size = 0; + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + hello.pool_size = 4097; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + hello.pool_size = POOL_SIZE; + + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + hello.offset = (__u64)-1; + + /* success test */ + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == 0); + + /* The kernel should have returned some items */ + ASSERT_RETURN(hello.offset != (__u64)-1); + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(fd, &cmd_free); + ASSERT_RETURN(ret >= 0); + + close(fd); + + fd = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(fd >= 0); + + /* no ACTIVATOR flag without a name */ + hello.flags = KDBUS_HELLO_ACTIVATOR; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + close(fd); + + return TEST_OK; +} + +int kdbus_test_byebye(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + struct kdbus_cmd_recv cmd_recv = { .size = sizeof(cmd_recv) }; + struct kdbus_cmd cmd_byebye = { .size = sizeof(cmd_byebye) }; + int ret; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(env->conn); + ASSERT_RETURN(ret == 0); + + /* send over 1st connection */ + ret = kdbus_msg_send(env->conn, NULL, 0, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + /* say byebye on the 2nd, which must fail */ + ret = kdbus_cmd_byebye(conn->fd, &cmd_byebye); + ASSERT_RETURN(ret == -EBUSY); + + /* receive the message */ + ret = kdbus_cmd_recv(conn->fd, &cmd_recv); + ASSERT_RETURN(ret == 0); + + ret = kdbus_free(conn, cmd_recv.msg.offset); + ASSERT_RETURN(ret == 0); + + /* and try again */ + ret = kdbus_cmd_byebye(conn->fd, &cmd_byebye); + ASSERT_RETURN(ret == 0); + + /* a 2nd try should result in -ECONNRESET */ + ret = kdbus_cmd_byebye(conn->fd, &cmd_byebye); + ASSERT_RETURN(ret == -ECONNRESET); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +/* Get only the first item */ +static struct kdbus_item *kdbus_get_item(struct kdbus_info *info, + uint64_t type) +{ + struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == type) + return item; + + return NULL; +} + +static unsigned int kdbus_count_item(struct kdbus_info *info, + uint64_t type) +{ + unsigned int i = 0; + const struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == type) + i++; + + return i; +} + +static int kdbus_fuzz_conn_info(struct kdbus_test_env *env, int capable) +{ + int ret; + unsigned int cnt = 0; + uint64_t offset = 0; + struct kdbus_info *info; + struct kdbus_conn *conn; + struct kdbus_conn *privileged; + const struct kdbus_item *item; + uint64_t valid_flags = KDBUS_ATTACH_NAMES | + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_CONN_DESCRIPTION; + + uint64_t invalid_flags = KDBUS_ATTACH_NAMES | + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_CONN_DESCRIPTION; + + struct kdbus_creds cached_creds; + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + + getresuid(&ruid, &euid, &suid); + getresgid(&rgid, &egid, &sgid); + + cached_creds.uid = ruid; + cached_creds.euid = euid; + cached_creds.suid = suid; + cached_creds.fsuid = ruid; + + cached_creds.gid = rgid; + cached_creds.egid = egid; + cached_creds.sgid = sgid; + cached_creds.fsgid = rgid; + + struct kdbus_pids cached_pids = { + .pid = getpid(), + .tid = syscall(SYS_gettid), + .ppid = getppid(), + }; + + ret = kdbus_conn_info(env->conn, env->conn->id, NULL, + valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(env->conn->buf + offset); + ASSERT_RETURN(info->id == env->conn->id); + + /* We do not have any well-known name */ + item = kdbus_get_item(info, KDBUS_ITEM_NAME); + ASSERT_RETURN(item == NULL); + + item = kdbus_get_item(info, KDBUS_ITEM_CONN_DESCRIPTION); + if (valid_flags & KDBUS_ATTACH_CONN_DESCRIPTION) { + ASSERT_RETURN(item); + } else { + ASSERT_RETURN(item == NULL); + } + + kdbus_free(env->conn, offset); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + privileged = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(privileged); + + ret = kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + /* We do not have any well-known name */ + item = kdbus_get_item(info, KDBUS_ITEM_NAME); + ASSERT_RETURN(item == NULL); + + cnt = kdbus_count_item(info, KDBUS_ITEM_CREDS); + if (valid_flags & KDBUS_ATTACH_CREDS) { + ASSERT_RETURN(cnt == 1); + + item = kdbus_get_item(info, KDBUS_ITEM_CREDS); + ASSERT_RETURN(item); + + /* Compare received items with cached creds */ + ASSERT_RETURN(memcmp(&item->creds, &cached_creds, + sizeof(struct kdbus_creds)) == 0); + } else { + ASSERT_RETURN(cnt == 0); + } + + item = kdbus_get_item(info, KDBUS_ITEM_PIDS); + if (valid_flags & KDBUS_ATTACH_PIDS) { + ASSERT_RETURN(item); + + /* Compare item->pids with cached PIDs */ + ASSERT_RETURN(item->pids.pid == cached_pids.pid && + item->pids.tid == cached_pids.tid && + item->pids.ppid == cached_pids.ppid); + } else { + ASSERT_RETURN(item == NULL); + } + + /* We did not request KDBUS_ITEM_CAPS */ + item = kdbus_get_item(info, KDBUS_ITEM_CAPS); + ASSERT_RETURN(item == NULL); + + kdbus_free(conn, offset); + + ret = kdbus_name_acquire(conn, "com.example.a", NULL); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + item = kdbus_get_item(info, KDBUS_ITEM_OWNED_NAME); + if (valid_flags & KDBUS_ATTACH_NAMES) { + ASSERT_RETURN(item && !strcmp(item->name.name, "com.example.a")); + } else { + ASSERT_RETURN(item == NULL); + } + + kdbus_free(conn, offset); + + ret = kdbus_conn_info(conn, 0, "com.example.a", valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + kdbus_free(conn, offset); + + /* does not have the necessary caps to drop to unprivileged */ + if (!capable) + goto continue_test; + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + ret = kdbus_conn_info(conn, conn->id, NULL, + valid_flags, &offset); + ASSERT_EXIT(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_EXIT(info->id == conn->id); + + if (valid_flags & KDBUS_ATTACH_NAMES) { + item = kdbus_get_item(info, KDBUS_ITEM_OWNED_NAME); + ASSERT_EXIT(item && + strcmp(item->name.name, + "com.example.a") == 0); + } + + if (valid_flags & KDBUS_ATTACH_CREDS) { + item = kdbus_get_item(info, KDBUS_ITEM_CREDS); + ASSERT_EXIT(item); + + /* Compare received items with cached creds */ + ASSERT_EXIT(memcmp(&item->creds, &cached_creds, + sizeof(struct kdbus_creds)) == 0); + } + + if (valid_flags & KDBUS_ATTACH_PIDS) { + item = kdbus_get_item(info, KDBUS_ITEM_PIDS); + ASSERT_EXIT(item); + + /* + * Compare item->pids with cached pids of + * privileged one. + * + * cmd_info will always return cached pids. + */ + ASSERT_EXIT(item->pids.pid == cached_pids.pid && + item->pids.tid == cached_pids.tid); + } + + kdbus_free(conn, offset); + + /* + * Use invalid_flags and make sure that userspace + * do not play with us. + */ + ret = kdbus_conn_info(conn, conn->id, NULL, + invalid_flags, &offset); + ASSERT_EXIT(ret == 0); + + /* + * Make sure that we return only one creds item and + * it points to the cached creds. + */ + cnt = kdbus_count_item(info, KDBUS_ITEM_CREDS); + if (invalid_flags & KDBUS_ATTACH_CREDS) { + ASSERT_EXIT(cnt == 1); + + item = kdbus_get_item(info, KDBUS_ITEM_CREDS); + ASSERT_EXIT(item); + + /* Compare received items with cached creds */ + ASSERT_EXIT(memcmp(&item->creds, &cached_creds, + sizeof(struct kdbus_creds)) == 0); + } else { + ASSERT_EXIT(cnt == 0); + } + + if (invalid_flags & KDBUS_ATTACH_PIDS) { + cnt = kdbus_count_item(info, KDBUS_ITEM_PIDS); + ASSERT_EXIT(cnt == 1); + + item = kdbus_get_item(info, KDBUS_ITEM_PIDS); + ASSERT_EXIT(item); + + /* Compare item->pids with cached pids */ + ASSERT_EXIT(item->pids.pid == cached_pids.pid && + item->pids.tid == cached_pids.tid); + } + + cnt = kdbus_count_item(info, KDBUS_ITEM_CGROUP); + if (invalid_flags & KDBUS_ATTACH_CGROUP) { + ASSERT_EXIT(cnt == 1); + } else { + ASSERT_EXIT(cnt == 0); + } + + cnt = kdbus_count_item(info, KDBUS_ITEM_CAPS); + if (invalid_flags & KDBUS_ATTACH_CAPS) { + ASSERT_EXIT(cnt == 1); + } else { + ASSERT_EXIT(cnt == 0); + } + + kdbus_free(conn, offset); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + +continue_test: + + /* A second name */ + ret = kdbus_name_acquire(conn, "com.example.b", NULL); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + cnt = kdbus_count_item(info, KDBUS_ITEM_OWNED_NAME); + if (valid_flags & KDBUS_ATTACH_NAMES) { + ASSERT_RETURN(cnt == 2); + } else { + ASSERT_RETURN(cnt == 0); + } + + kdbus_free(conn, offset); + + ASSERT_RETURN(ret == 0); + + return 0; +} + +int kdbus_test_conn_info(struct kdbus_test_env *env) +{ + int ret; + int have_caps; + struct { + struct kdbus_cmd_info cmd_info; + + struct { + uint64_t size; + uint64_t type; + char str[64]; + } name; + } buf; + + buf.cmd_info.size = sizeof(struct kdbus_cmd_info); + buf.cmd_info.flags = 0; + buf.cmd_info.attach_flags = 0; + buf.cmd_info.id = env->conn->id; + + ret = kdbus_conn_info(env->conn, env->conn->id, NULL, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* try to pass a name that is longer than the buffer's size */ + buf.name.size = KDBUS_ITEM_HEADER_SIZE + 1; + buf.name.type = KDBUS_ITEM_NAME; + strcpy(buf.name.str, "foo.bar.bla"); + + buf.cmd_info.id = 0; + buf.cmd_info.size = sizeof(buf.cmd_info) + buf.name.size; + ret = kdbus_cmd_conn_info(env->conn->fd, (struct kdbus_cmd_info *) &buf); + ASSERT_RETURN(ret == -EINVAL); + + /* Pass a non existent name */ + ret = kdbus_conn_info(env->conn, 0, "non.existent.name", 0, NULL); + ASSERT_RETURN(ret == -ESRCH); + + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + /* Test for caps here, so we run the previous test */ + have_caps = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(have_caps >= 0); + + ret = kdbus_fuzz_conn_info(env, have_caps); + ASSERT_RETURN(ret == 0); + + /* Now if we have skipped some tests then let the user know */ + if (!have_caps) + return TEST_SKIP; + + return TEST_OK; +} + +int kdbus_test_conn_update(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + struct kdbus_msg *msg; + int found = 0; + int ret; + + /* + * kdbus_hello() sets all attach flags. Receive a message by this + * connection, and make sure a timestamp item (just to pick one) is + * present. + */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_msg_send(env->conn, NULL, 0x12345678, 0, 0, 0, conn->id); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + found = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(found == 1); + + kdbus_msg_free(msg); + + /* + * Now, modify the attach flags and repeat the action. The item must + * now be missing. + */ + found = 0; + + ret = kdbus_conn_update_attach_flags(conn, + _KDBUS_ATTACH_ALL, + _KDBUS_ATTACH_ALL & + ~KDBUS_ATTACH_TIMESTAMP); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_send(env->conn, NULL, 0x12345678, 0, 0, 0, conn->id); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + found = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(found == 0); + + /* Provide a bogus attach_flags value */ + ret = kdbus_conn_update_attach_flags(conn, + _KDBUS_ATTACH_ALL + 1, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(ret == -EINVAL); + + kdbus_msg_free(msg); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_writable_pool(struct kdbus_test_env *env) +{ + struct kdbus_cmd_free cmd_free = {}; + struct kdbus_cmd_hello hello; + int fd, ret; + void *map; + + fd = open(env->buspath, O_RDWR | O_CLOEXEC); + ASSERT_RETURN(fd >= 0); + + memset(&hello, 0, sizeof(hello)); + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + hello.attach_flags_recv = _KDBUS_ATTACH_ALL; + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + hello.offset = (__u64)-1; + + /* success test */ + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == 0); + + /* The kernel should have returned some items */ + ASSERT_RETURN(hello.offset != (__u64)-1); + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(fd, &cmd_free); + ASSERT_RETURN(ret >= 0); + + /* pools cannot be mapped writable */ + map = mmap(NULL, POOL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_RETURN(map == MAP_FAILED); + + /* pools can always be mapped readable */ + map = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0); + ASSERT_RETURN(map != MAP_FAILED); + + /* make sure we cannot change protection masks to writable */ + ret = mprotect(map, POOL_SIZE, PROT_READ | PROT_WRITE); + ASSERT_RETURN(ret < 0); + + munmap(map, POOL_SIZE); + close(fd); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-daemon.c b/tools/testing/selftests/kdbus/test-daemon.c new file mode 100644 index 000000000..8bc238619 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-daemon.c @@ -0,0 +1,65 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <stdbool.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int kdbus_test_daemon(struct kdbus_test_env *env) +{ + struct pollfd fds[2]; + int count; + int ret; + + /* This test doesn't make any sense in non-interactive mode */ + if (!kdbus_util_verbose) + return TEST_OK; + + printf("Created connection %llu on bus '%s'\n", + (unsigned long long) env->conn->id, env->buspath); + + ret = kdbus_name_acquire(env->conn, "com.example.kdbus-test", NULL); + ASSERT_RETURN(ret == 0); + printf(" Aquired name: com.example.kdbus-test\n"); + + fds[0].fd = env->conn->fd; + fds[1].fd = STDIN_FILENO; + + printf("Monitoring connections:\n"); + + for (count = 0;; count++) { + int i, nfds = sizeof(fds) / sizeof(fds[0]); + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, -1); + if (ret <= 0) + break; + + if (fds[0].revents & POLLIN) { + ret = kdbus_msg_recv(env->conn, NULL, NULL); + ASSERT_RETURN(ret == 0); + } + + /* stdin */ + if (fds[1].revents & POLLIN) + break; + } + + printf("Closing bus connection\n"); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-endpoint.c b/tools/testing/selftests/kdbus/test-endpoint.c new file mode 100644 index 000000000..34a7be49c --- /dev/null +++ b/tools/testing/selftests/kdbus/test-endpoint.c @@ -0,0 +1,352 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <libgen.h> +#include <sys/capability.h> +#include <sys/wait.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +#define KDBUS_SYSNAME_MAX_LEN 63 + +static int install_name_add_match(struct kdbus_conn *conn, const char *name) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + int ret; + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_ADD; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(conn->fd, &buf.cmd); + if (ret < 0) + return ret; + + return 0; +} + +static int create_endpoint(const char *buspath, uid_t uid, const char *name, + uint64_t flags) +{ + struct { + struct kdbus_cmd cmd; + + /* name item */ + struct { + uint64_t size; + uint64_t type; + /* max should be KDBUS_SYSNAME_MAX_LEN */ + char str[128]; + } name; + } ep_make; + int fd, ret; + + fd = open(buspath, O_RDWR); + if (fd < 0) + return fd; + + memset(&ep_make, 0, sizeof(ep_make)); + + snprintf(ep_make.name.str, + /* Use the KDBUS_SYSNAME_MAX_LEN or sizeof(str) */ + KDBUS_SYSNAME_MAX_LEN > strlen(name) ? + KDBUS_SYSNAME_MAX_LEN : sizeof(ep_make.name.str), + "%u-%s", uid, name); + + ep_make.name.type = KDBUS_ITEM_MAKE_NAME; + ep_make.name.size = KDBUS_ITEM_HEADER_SIZE + + strlen(ep_make.name.str) + 1; + + ep_make.cmd.flags = flags; + ep_make.cmd.size = sizeof(ep_make.cmd) + ep_make.name.size; + + ret = kdbus_cmd_endpoint_make(fd, &ep_make.cmd); + if (ret < 0) { + kdbus_printf("error creating endpoint: %d (%m)\n", ret); + return ret; + } + + return fd; +} + +static int unpriv_test_custom_ep(const char *buspath) +{ + int ret, ep_fd1, ep_fd2; + char *ep1, *ep2, *tmp1, *tmp2; + + tmp1 = strdup(buspath); + tmp2 = strdup(buspath); + ASSERT_RETURN(tmp1 && tmp2); + + ret = asprintf(&ep1, "%s/%u-%s", dirname(tmp1), getuid(), "apps1"); + ASSERT_RETURN(ret >= 0); + + ret = asprintf(&ep2, "%s/%u-%s", dirname(tmp2), getuid(), "apps2"); + ASSERT_RETURN(ret >= 0); + + free(tmp1); + free(tmp2); + + /* endpoint only accessible to current uid */ + ep_fd1 = create_endpoint(buspath, getuid(), "apps1", 0); + ASSERT_RETURN(ep_fd1 >= 0); + + /* endpoint world accessible */ + ep_fd2 = create_endpoint(buspath, getuid(), "apps2", + KDBUS_MAKE_ACCESS_WORLD); + ASSERT_RETURN(ep_fd2 >= 0); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({ + int ep_fd; + struct kdbus_conn *ep_conn; + + /* + * Make sure that we are not able to create custom + * endpoints + */ + ep_fd = create_endpoint(buspath, getuid(), + "unpriv_costum_ep", 0); + ASSERT_EXIT(ep_fd == -EPERM); + + /* + * Endpoint "apps1" only accessible to same users, + * that own the endpoint. Access denied by VFS + */ + ep_conn = kdbus_hello(ep1, 0, NULL, 0); + ASSERT_EXIT(!ep_conn && errno == EACCES); + + /* Endpoint "apps2" world accessible */ + ep_conn = kdbus_hello(ep2, 0, NULL, 0); + ASSERT_EXIT(ep_conn); + + kdbus_conn_free(ep_conn); + + _exit(EXIT_SUCCESS); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + + close(ep_fd1); + close(ep_fd2); + free(ep1); + free(ep2); + + return 0; +} + +static int update_endpoint(int fd, const char *name) +{ + int len = strlen(name) + 1; + struct { + struct kdbus_cmd cmd; + + /* name item */ + struct { + uint64_t size; + uint64_t type; + char str[KDBUS_ALIGN8(len)]; + } name; + + struct { + uint64_t size; + uint64_t type; + struct kdbus_policy_access access; + } access; + } ep_update; + int ret; + + memset(&ep_update, 0, sizeof(ep_update)); + + ep_update.name.size = KDBUS_ITEM_HEADER_SIZE + len; + ep_update.name.type = KDBUS_ITEM_NAME; + strncpy(ep_update.name.str, name, sizeof(ep_update.name.str) - 1); + + ep_update.access.size = sizeof(ep_update.access); + ep_update.access.type = KDBUS_ITEM_POLICY_ACCESS; + ep_update.access.access.type = KDBUS_POLICY_ACCESS_WORLD; + ep_update.access.access.access = KDBUS_POLICY_SEE; + + ep_update.cmd.size = sizeof(ep_update); + + ret = kdbus_cmd_endpoint_update(fd, &ep_update.cmd); + if (ret < 0) { + kdbus_printf("error updating endpoint: %d (%m)\n", ret); + return ret; + } + + return 0; +} + +int kdbus_test_custom_endpoint(struct kdbus_test_env *env) +{ + char *ep, *tmp; + int ret, ep_fd; + struct kdbus_msg *msg; + struct kdbus_conn *ep_conn; + struct kdbus_conn *reader; + const char *name = "foo.bar.baz"; + const char *epname = "foo"; + char fake_ep[KDBUS_SYSNAME_MAX_LEN + 1] = {'\0'}; + + memset(fake_ep, 'X', sizeof(fake_ep) - 1); + + /* Try to create a custom endpoint with a long name */ + ret = create_endpoint(env->buspath, getuid(), fake_ep, 0); + ASSERT_RETURN(ret == -ENAMETOOLONG); + + /* Try to create a custom endpoint with a different uid */ + ret = create_endpoint(env->buspath, getuid() + 1, "foobar", 0); + ASSERT_RETURN(ret == -EINVAL); + + /* create a custom endpoint, and open a connection on it */ + ep_fd = create_endpoint(env->buspath, getuid(), "foo", 0); + ASSERT_RETURN(ep_fd >= 0); + + tmp = strdup(env->buspath); + ASSERT_RETURN(tmp); + + ret = asprintf(&ep, "%s/%u-%s", dirname(tmp), getuid(), epname); + free(tmp); + ASSERT_RETURN(ret >= 0); + + /* Register a connection that listen to broadcasts */ + reader = kdbus_hello(ep, 0, NULL, 0); + ASSERT_RETURN(reader); + + /* Register to kernel signals */ + ret = kdbus_add_match_id(reader, 0x1, KDBUS_ITEM_ID_ADD, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_id(reader, 0x2, KDBUS_ITEM_ID_REMOVE, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = install_name_add_match(reader, name); + ASSERT_RETURN(ret == 0); + + /* Monitor connections are not supported on custom endpoints */ + ep_conn = kdbus_hello(ep, KDBUS_HELLO_MONITOR, NULL, 0); + ASSERT_RETURN(!ep_conn && errno == EOPNOTSUPP); + + ep_conn = kdbus_hello(ep, 0, NULL, 0); + ASSERT_RETURN(ep_conn); + + /* Check that the reader got the IdAdd notification */ + ret = kdbus_msg_recv(reader, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_ADD); + ASSERT_RETURN(msg->items[0].id_change.id == ep_conn->id); + kdbus_msg_free(msg); + + /* + * Add a name add match on the endpoint connection, acquire name from + * the unfiltered connection, and make sure the filtered connection + * did not get the notification on the name owner change. Also, the + * endpoint connection may not be able to call conn_info, neither on + * the name nor on the ID. + */ + ret = install_name_add_match(ep_conn, name); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(ep_conn, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = kdbus_conn_info(ep_conn, 0, name, 0, NULL); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_conn_info(ep_conn, 0, "random.crappy.name", 0, NULL); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_conn_info(ep_conn, env->conn->id, NULL, 0, NULL); + ASSERT_RETURN(ret == -ENXIO); + + ret = kdbus_conn_info(ep_conn, 0x0fffffffffffffffULL, NULL, 0, NULL); + ASSERT_RETURN(ret == -ENXIO); + + /* Check that the reader did not receive the name notification */ + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Release the name again, update the custom endpoint policy, + * and try again. This time, the connection on the custom endpoint + * should have gotten it. + */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* Check that the reader did not receive the name notification */ + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = update_endpoint(ep_fd, name); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(ep_conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_ADD); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == 0); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == env->conn->id); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + kdbus_msg_free(msg); + + ret = kdbus_msg_recv(reader, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + kdbus_msg_free(msg); + + ret = kdbus_conn_info(ep_conn, 0, name, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_info(ep_conn, env->conn->id, NULL, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* If we have privileges test custom endpoints */ + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + /* + * All uids/gids are mapped and we have the necessary caps + */ + if (ret && all_uids_gids_are_mapped()) { + ret = unpriv_test_custom_ep(env->buspath); + ASSERT_RETURN(ret == 0); + } + + kdbus_conn_free(reader); + kdbus_conn_free(ep_conn); + close(ep_fd); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-fd.c b/tools/testing/selftests/kdbus/test-fd.c new file mode 100644 index 000000000..2ae0f5ae8 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-fd.c @@ -0,0 +1,789 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#define KDBUS_MSG_MAX_ITEMS 128 +#define KDBUS_USER_MAX_CONN 256 + +/* maximum number of inflight fds in a target queue per user */ +#define KDBUS_CONN_MAX_FDS_PER_USER 16 + +/* maximum number of memfd items per message */ +#define KDBUS_MSG_MAX_MEMFD_ITEMS 16 + +static int make_msg_payload_dbus(uint64_t src_id, uint64_t dst_id, + uint64_t msg_size, + struct kdbus_msg **msg_dbus) +{ + struct kdbus_msg *msg; + + msg = malloc(msg_size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, msg_size); + msg->size = msg_size; + msg->src_id = src_id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + *msg_dbus = msg; + + return 0; +} + +static void make_item_memfds(struct kdbus_item *item, + int *memfds, size_t memfd_size) +{ + size_t i; + + for (i = 0; i < memfd_size; i++) { + item->type = KDBUS_ITEM_PAYLOAD_MEMFD; + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_memfd); + item->memfd.fd = memfds[i]; + item->memfd.size = sizeof(uint64_t); /* const size */ + item = KDBUS_ITEM_NEXT(item); + } +} + +static void make_item_fds(struct kdbus_item *item, + int *fd_array, size_t fd_size) +{ + size_t i; + item->type = KDBUS_ITEM_FDS; + item->size = KDBUS_ITEM_HEADER_SIZE + (sizeof(int) * fd_size); + + for (i = 0; i < fd_size; i++) + item->fds[i] = fd_array[i]; +} + +static int memfd_write(const char *name, void *buf, size_t bufsize) +{ + ssize_t ret; + int memfd; + + memfd = sys_memfd_create(name, 0); + ASSERT_RETURN_VAL(memfd >= 0, memfd); + + ret = write(memfd, buf, bufsize); + ASSERT_RETURN_VAL(ret == (ssize_t)bufsize, -EAGAIN); + + ret = sys_memfd_seal_set(memfd); + ASSERT_RETURN_VAL(ret == 0, -errno); + + return memfd; +} + +static int send_memfds(struct kdbus_conn *conn, uint64_t dst_id, + int *memfds_array, size_t memfd_count) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += memfd_count * KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + + if (dst_id == KDBUS_DST_ID_BROADCAST) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + + ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, ret); + + item = msg->items; + + if (dst_id == KDBUS_DST_ID_BROADCAST) { + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + item = KDBUS_ITEM_NEXT(item); + + msg->flags |= KDBUS_MSG_SIGNAL; + } + + make_item_memfds(item, memfds_array, memfd_count); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + free(msg); + return 0; +} + +static int send_fds(struct kdbus_conn *conn, uint64_t dst_id, + int *fd_array, size_t fd_count) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(int) * fd_count); + + if (dst_id == KDBUS_DST_ID_BROADCAST) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + + ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, ret); + + item = msg->items; + + if (dst_id == KDBUS_DST_ID_BROADCAST) { + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + item = KDBUS_ITEM_NEXT(item); + + msg->flags |= KDBUS_MSG_SIGNAL; + } + + make_item_fds(item, fd_array, fd_count); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + free(msg); + return ret; +} + +static int send_fds_memfds(struct kdbus_conn *conn, uint64_t dst_id, + int *fds_array, size_t fd_count, + int *memfds_array, size_t memfd_count) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += memfd_count * KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + size += KDBUS_ITEM_SIZE(sizeof(int) * fd_count); + + ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, ret); + + item = msg->items; + + make_item_fds(item, fds_array, fd_count); + item = KDBUS_ITEM_NEXT(item); + make_item_memfds(item, memfds_array, memfd_count); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + free(msg); + return ret; +} + +/* Return the number of received fds */ +static unsigned int kdbus_item_get_nfds(struct kdbus_msg *msg) +{ + unsigned int fds = 0; + const struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, msg, items) { + switch (item->type) { + case KDBUS_ITEM_FDS: { + fds += (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + break; + } + + case KDBUS_ITEM_PAYLOAD_MEMFD: + fds++; + break; + + default: + break; + } + } + + return fds; +} + +static struct kdbus_msg * +get_kdbus_msg_with_fd(struct kdbus_conn *conn_src, + uint64_t dst_id, uint64_t cookie, int fd) +{ + int ret; + uint64_t size; + struct kdbus_item *item; + struct kdbus_msg *msg; + + size = sizeof(struct kdbus_msg); + if (fd >= 0) + size += KDBUS_ITEM_SIZE(sizeof(int)); + + ret = make_msg_payload_dbus(conn_src->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, NULL); + + msg->cookie = cookie; + + if (fd >= 0) { + item = msg->items; + + make_item_fds(item, (int *)&fd, 1); + } + + return msg; +} + +static int kdbus_test_no_fds(struct kdbus_test_env *env, + int *fds, int *memfd) +{ + pid_t pid; + int ret, status; + uint64_t cookie; + int connfd1, connfd2; + struct kdbus_msg *msg, *msg_sync_reply; + struct kdbus_cmd_hello hello; + struct kdbus_conn *conn_src, *conn_dst, *conn_dummy; + struct kdbus_cmd_send cmd = {}; + struct kdbus_cmd_free cmd_free = {}; + + conn_src = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_src); + + connfd1 = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(connfd1 >= 0); + + connfd2 = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(connfd2 >= 0); + + /* + * Create connections without KDBUS_HELLO_ACCEPT_FD + * to test if send fd operations are blocked + */ + conn_dst = malloc(sizeof(*conn_dst)); + ASSERT_RETURN(conn_dst); + + conn_dummy = malloc(sizeof(*conn_dummy)); + ASSERT_RETURN(conn_dummy); + + memset(&hello, 0, sizeof(hello)); + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + + ret = kdbus_cmd_hello(connfd1, &hello); + ASSERT_RETURN(ret == 0); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(connfd1, &cmd_free); + ASSERT_RETURN(ret >= 0); + + conn_dst->fd = connfd1; + conn_dst->id = hello.id; + + memset(&hello, 0, sizeof(hello)); + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + + ret = kdbus_cmd_hello(connfd2, &hello); + ASSERT_RETURN(ret == 0); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(connfd2, &cmd_free); + ASSERT_RETURN(ret >= 0); + + conn_dummy->fd = connfd2; + conn_dummy->id = hello.id; + + conn_dst->buf = mmap(NULL, POOL_SIZE, PROT_READ, + MAP_SHARED, connfd1, 0); + ASSERT_RETURN(conn_dst->buf != MAP_FAILED); + + conn_dummy->buf = mmap(NULL, POOL_SIZE, PROT_READ, + MAP_SHARED, connfd2, 0); + ASSERT_RETURN(conn_dummy->buf != MAP_FAILED); + + /* + * Send fds to connection that do not accept fd passing + */ + ret = send_fds(conn_src, conn_dst->id, fds, 1); + ASSERT_RETURN(ret == -ECOMM); + + /* + * memfd are kdbus payload + */ + ret = send_memfds(conn_src, conn_dst->id, memfd, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(conn_dst, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + cookie = time(NULL); + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + struct timespec now; + + /* + * A sync send/reply to a connection that do not + * accept fds should fail if it contains an fd + */ + msg_sync_reply = get_kdbus_msg_with_fd(conn_dst, + conn_dummy->id, + cookie, fds[0]); + ASSERT_EXIT(msg_sync_reply); + + ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &now); + ASSERT_EXIT(ret == 0); + + msg_sync_reply->timeout_ns = now.tv_sec * 1000000000ULL + + now.tv_nsec + 100000000ULL; + msg_sync_reply->flags = KDBUS_MSG_EXPECT_REPLY; + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg_sync_reply; + cmd.flags = KDBUS_SEND_SYNC_REPLY; + + ret = kdbus_cmd_send(conn_dst->fd, &cmd); + ASSERT_EXIT(ret == -ECOMM); + + /* + * Now send a normal message, but the sync reply + * will fail since it contains an fd that the + * original sender do not want. + * + * The original sender will fail with -ETIMEDOUT + */ + cookie++; + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_src->id, -1); + ASSERT_EXIT(ret == -EREMOTEIO); + + cookie++; + ret = kdbus_msg_recv_poll(conn_dst, 100, &msg, NULL); + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->cookie == cookie); + + free(msg_sync_reply); + kdbus_msg_free(msg); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_dummy, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + cookie++; + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* + * Try to reply with a kdbus connection handle, this should + * fail with -EOPNOTSUPP + */ + msg_sync_reply = get_kdbus_msg_with_fd(conn_src, + conn_dst->id, + cookie, conn_dst->fd); + ASSERT_RETURN(msg_sync_reply); + + msg_sync_reply->cookie_reply = cookie; + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg_sync_reply; + + ret = kdbus_cmd_send(conn_src->fd, &cmd); + ASSERT_RETURN(ret == -EOPNOTSUPP); + + free(msg_sync_reply); + + /* + * Try to reply with a normal fd, this should fail even + * if the response is a sync reply + * + * From the sender view we fail with -ECOMM + */ + msg_sync_reply = get_kdbus_msg_with_fd(conn_src, + conn_dst->id, + cookie, fds[0]); + ASSERT_RETURN(msg_sync_reply); + + msg_sync_reply->cookie_reply = cookie; + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg_sync_reply; + + ret = kdbus_cmd_send(conn_src->fd, &cmd); + ASSERT_RETURN(ret == -ECOMM); + + free(msg_sync_reply); + + /* + * Resend another normal message and check if the queue + * is clear + */ + cookie++; + ret = kdbus_msg_send(conn_src, NULL, cookie, 0, 0, 0, + conn_dst->id); + ASSERT_RETURN(ret == 0); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + kdbus_conn_free(conn_dummy); + kdbus_conn_free(conn_dst); + kdbus_conn_free(conn_src); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int kdbus_send_multiple_fds(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + int ret, i; + unsigned int nfds; + int fds[KDBUS_CONN_MAX_FDS_PER_USER + 1]; + int memfds[KDBUS_MSG_MAX_ITEMS + 1]; + struct kdbus_msg *msg; + uint64_t dummy_value; + + dummy_value = time(NULL); + + for (i = 0; i < KDBUS_CONN_MAX_FDS_PER_USER + 1; i++) { + fds[i] = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_RETURN_VAL(fds[i] >= 0, -errno); + } + + /* Send KDBUS_CONN_MAX_FDS_PER_USER with one more fd */ + ret = send_fds(conn_src, conn_dst->id, fds, + KDBUS_CONN_MAX_FDS_PER_USER + 1); + ASSERT_RETURN(ret == -EMFILE); + + /* Retry with the correct KDBUS_CONN_MAX_FDS_PER_USER */ + ret = send_fds(conn_src, conn_dst->id, fds, + KDBUS_CONN_MAX_FDS_PER_USER); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* Check we got the right number of fds */ + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_CONN_MAX_FDS_PER_USER); + + kdbus_msg_free(msg); + + for (i = 0; i < KDBUS_MSG_MAX_ITEMS + 1; i++, dummy_value++) { + memfds[i] = memfd_write("memfd-name", + &dummy_value, + sizeof(dummy_value)); + ASSERT_RETURN_VAL(memfds[i] >= 0, memfds[i]); + } + + /* Send KDBUS_MSG_MAX_ITEMS with one more memfd */ + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_ITEMS + 1); + ASSERT_RETURN(ret == -E2BIG); + + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS + 1); + ASSERT_RETURN(ret == -E2BIG); + + /* Retry with the correct KDBUS_MSG_MAX_ITEMS */ + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* Check we got the right number of fds */ + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + + /* + * Combine multiple KDBUS_CONN_MAX_FDS_PER_USER+1 fds and + * 10 memfds + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER + 1, + memfds, 10); + ASSERT_RETURN(ret == -EMFILE); + + ret = kdbus_msg_recv(conn_dst, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Combine multiple KDBUS_CONN_MAX_FDS_PER_USER fds and + * (128 - 1) + 1 memfds, all fds take one item, while each + * memfd takes one item + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, (KDBUS_MSG_MAX_ITEMS - 1) + 1); + ASSERT_RETURN(ret == -E2BIG); + + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS + 1); + ASSERT_RETURN(ret == -E2BIG); + + ret = kdbus_msg_recv(conn_dst, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Send KDBUS_CONN_MAX_FDS_PER_USER fds + + * KDBUS_MSG_MAX_MEMFD_ITEMS memfds + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* Check we got the right number of fds */ + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_CONN_MAX_FDS_PER_USER + + KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + + /* + * Re-send fds + memfds, close them, but do not receive them + * and try to queue more + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + /* close old references and get a new ones */ + for (i = 0; i < KDBUS_CONN_MAX_FDS_PER_USER + 1; i++) { + close(fds[i]); + fds[i] = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_RETURN_VAL(fds[i] >= 0, -errno); + } + + /* should fail since we have already fds in the queue */ + ret = send_fds(conn_src, conn_dst->id, fds, + KDBUS_CONN_MAX_FDS_PER_USER); + ASSERT_RETURN(ret == -EMFILE); + + /* This should succeed */ + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_CONN_MAX_FDS_PER_USER + + KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + ret = kdbus_msg_recv(conn_dst, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + for (i = 0; i < KDBUS_CONN_MAX_FDS_PER_USER + 1; i++) + close(fds[i]); + + for (i = 0; i < KDBUS_MSG_MAX_ITEMS + 1; i++) + close(memfds[i]); + + return 0; +} + +int kdbus_test_fd_passing(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_src, *conn_dst; + const char *str = "stackenblocken"; + const struct kdbus_item *item; + struct kdbus_msg *msg; + unsigned int i; + uint64_t now; + int fds_conn[2]; + int sock_pair[2]; + int fds[2]; + int memfd; + int ret; + + now = (uint64_t) time(NULL); + + /* create two connections */ + conn_src = kdbus_hello(env->buspath, 0, NULL, 0); + conn_dst = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_src && conn_dst); + + fds_conn[0] = conn_src->fd; + fds_conn[1] = conn_dst->fd; + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair); + ASSERT_RETURN(ret == 0); + + /* Setup memfd */ + memfd = memfd_write("memfd-name", &now, sizeof(now)); + ASSERT_RETURN(memfd >= 0); + + /* Setup pipes */ + ret = pipe(fds); + ASSERT_RETURN(ret == 0); + + i = write(fds[1], str, strlen(str)); + ASSERT_RETURN(i == strlen(str)); + + /* + * Try to ass the handle of a connection as message payload. + * This must fail. + */ + ret = send_fds(conn_src, conn_dst->id, fds_conn, 2); + ASSERT_RETURN(ret == -ENOTSUP); + + ret = send_fds(conn_dst, conn_src->id, fds_conn, 2); + ASSERT_RETURN(ret == -ENOTSUP); + + ret = send_fds(conn_src, conn_dst->id, sock_pair, 2); + ASSERT_RETURN(ret == -ENOTSUP); + + /* + * Send fds and memfds to connection that do not accept fds + */ + ret = kdbus_test_no_fds(env, fds, (int *)&memfd); + ASSERT_RETURN(ret == 0); + + /* Try to broadcast file descriptors. This must fail. */ + ret = send_fds(conn_src, KDBUS_DST_ID_BROADCAST, fds, 1); + ASSERT_RETURN(ret == -ENOTUNIQ); + + /* Try to broadcast memfd. This must succeed. */ + ret = send_memfds(conn_src, KDBUS_DST_ID_BROADCAST, (int *)&memfd, 1); + ASSERT_RETURN(ret == 0); + + /* Open code this loop */ +loop_send_fds: + + /* + * Send the read end of the pipe and close it. + */ + ret = send_fds(conn_src, conn_dst->id, fds, 1); + ASSERT_RETURN(ret == 0); + close(fds[0]); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + KDBUS_ITEM_FOREACH(item, msg, items) { + if (item->type == KDBUS_ITEM_FDS) { + char tmp[14]; + int nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + ASSERT_RETURN(nfds == 1); + + i = read(item->fds[0], tmp, sizeof(tmp)); + if (i != 0) { + ASSERT_RETURN(i == sizeof(tmp)); + ASSERT_RETURN(memcmp(tmp, str, sizeof(tmp)) == 0); + + /* Write EOF */ + close(fds[1]); + + /* + * Resend the read end of the pipe, + * the receiver still holds a reference + * to it... + */ + goto loop_send_fds; + } + + /* Got EOF */ + + /* + * Close the last reference to the read end + * of the pipe, other references are + * automatically closed just after send. + */ + close(item->fds[0]); + } + } + + /* + * Try to resend the read end of the pipe. Must fail with + * -EBADF since both the sender and receiver closed their + * references to it. We assume the above since sender and + * receiver are on the same process. + */ + ret = send_fds(conn_src, conn_dst->id, fds, 1); + ASSERT_RETURN(ret == -EBADF); + + /* Then we clear out received any data... */ + kdbus_msg_free(msg); + + ret = kdbus_send_multiple_fds(conn_src, conn_dst); + ASSERT_RETURN(ret == 0); + + close(sock_pair[0]); + close(sock_pair[1]); + close(memfd); + + kdbus_conn_free(conn_src); + kdbus_conn_free(conn_dst); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-free.c b/tools/testing/selftests/kdbus/test-free.c new file mode 100644 index 000000000..f666da3e8 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-free.c @@ -0,0 +1,64 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +static int sample_ioctl_call(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_cmd_list cmd_list = { + .flags = KDBUS_LIST_QUEUED, + .size = sizeof(cmd_list), + }; + + ret = kdbus_cmd_list(env->conn->fd, &cmd_list); + ASSERT_RETURN(ret == 0); + + /* DON'T FREE THIS SLICE OF MEMORY! */ + + return TEST_OK; +} + +int kdbus_test_free(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_cmd_free cmd_free = {}; + + /* free an unallocated buffer */ + cmd_free.size = sizeof(cmd_free); + cmd_free.flags = 0; + cmd_free.offset = 0; + ret = kdbus_cmd_free(env->conn->fd, &cmd_free); + ASSERT_RETURN(ret == -ENXIO); + + /* free a buffer out of the pool's bounds */ + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = POOL_SIZE + 1; + ret = kdbus_cmd_free(env->conn->fd, &cmd_free); + ASSERT_RETURN(ret == -ENXIO); + + /* + * The user application is responsible for freeing the allocated + * memory with the KDBUS_CMD_FREE ioctl, so let's test what happens + * if we forget about it. + */ + + ret = sample_ioctl_call(env); + ASSERT_RETURN(ret == 0); + + ret = sample_ioctl_call(env); + ASSERT_RETURN(ret == 0); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-match.c b/tools/testing/selftests/kdbus/test-match.c new file mode 100644 index 000000000..2360dc1d7 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-match.c @@ -0,0 +1,441 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +int kdbus_test_match_id_add(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = 0xdeafbeefdeaddead; + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_ID_ADD; + buf.item.chg.id = KDBUS_MATCH_ID_ANY; + + /* match on id add */ + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* create 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* 1st connection should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_ADD); + ASSERT_RETURN(msg->items[0].id_change.id == conn->id); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_match_id_remove(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + size_t id; + int ret; + + /* create 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + id = conn->id; + + memset(&buf, 0, sizeof(buf)); + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = 0xdeafbeefdeaddead; + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_ID_REMOVE; + buf.item.chg.id = id; + + /* register match on 2nd connection */ + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* remove 2nd connection again */ + kdbus_conn_free(conn); + + /* 1st connection should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_REMOVE); + ASSERT_RETURN(msg->items[0].id_change.id == id); + + return TEST_OK; +} + +int kdbus_test_match_replace(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + size_t id; + int ret; + + /* add a match to id_add */ + ASSERT_RETURN(kdbus_test_match_id_add(env) == TEST_OK); + + /* do a replace of the match from id_add to id_remove */ + memset(&buf, 0, sizeof(buf)); + + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = 0xdeafbeefdeaddead; + buf.cmd.flags = KDBUS_MATCH_REPLACE; + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_ID_REMOVE; + buf.item.chg.id = KDBUS_MATCH_ID_ANY; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + + /* create 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + id = conn->id; + + /* 1st connection should _not_ have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret != 0); + + /* remove 2nd connection */ + kdbus_conn_free(conn); + + /* 1st connection should _now_ have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_REMOVE); + ASSERT_RETURN(msg->items[0].id_change.id == id); + + return TEST_OK; +} + +int kdbus_test_match_name_add(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + struct kdbus_msg *msg; + char *name; + int ret; + + name = "foo.bla.blaz"; + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_ADD; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* acquire the name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + /* we should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_ADD); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == 0); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == env->conn->id); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + return TEST_OK; +} + +int kdbus_test_match_name_remove(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + struct kdbus_msg *msg; + char *name; + int ret; + + name = "foo.bla.blaz"; + + /* acquire the name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_REMOVE; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* release the name again */ + kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* we should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_REMOVE); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == env->conn->id); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == 0); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + return TEST_OK; +} + +int kdbus_test_match_name_change(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + uint64_t flags; + char *name = "foo.bla.baz"; + int ret; + + /* acquire the name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_CHANGE; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* allow the new connection to own the same name */ + /* queue the 2nd connection as waiting owner */ + flags = KDBUS_NAME_QUEUE; + ret = kdbus_name_acquire(conn, name, &flags); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(flags & KDBUS_NAME_IN_QUEUE); + + /* release name from 1st connection */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* we should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_CHANGE); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == env->conn->id); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == conn->id); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +static int send_bloom_filter(const struct kdbus_conn *conn, + uint64_t cookie, + const uint8_t *filter, + size_t filter_size, + uint64_t filter_generation) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_msg *msg; + struct kdbus_item *item; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + filter_size; + + msg = alloca(size); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = KDBUS_DST_ID_BROADCAST; + msg->flags = KDBUS_MSG_SIGNAL; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + msg->cookie = cookie; + + item = msg->items; + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + + filter_size; + + item->bloom_filter.generation = filter_generation; + memcpy(item->bloom_filter.data, filter, filter_size); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + return 0; +} + +int kdbus_test_match_bloom(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + uint8_t data_gen0[64]; + uint8_t data_gen1[64]; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + uint64_t cookie = 0xf000f00f; + uint8_t filter[64]; + int ret; + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.cmd.size = sizeof(buf); + + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_BLOOM_MASK; + buf.item.data_gen0[0] = 0x55; + buf.item.data_gen0[63] = 0x80; + + buf.item.data_gen1[1] = 0xaa; + buf.item.data_gen1[9] = 0x02; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* a message with a 0'ed out filter must not reach the other peer */ + memset(filter, 0, sizeof(filter)); + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* now set the filter to the connection's mask and expect success */ + filter[0] = 0x55; + filter[63] = 0x80; + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + /* broaden the filter and try again. this should also succeed. */ + filter[0] = 0xff; + filter[8] = 0xff; + filter[63] = 0xff; + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + /* the same filter must not match against bloom generation 1 */ + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* set a different filter and try again */ + filter[1] = 0xaa; + filter[9] = 0x02; + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_conn_free(conn); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-message.c b/tools/testing/selftests/kdbus/test-message.c new file mode 100644 index 000000000..ddc1e0af8 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-message.c @@ -0,0 +1,734 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <stdbool.h> +#include <sys/eventfd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +/* maximum number of queued messages from the same individual user */ +#define KDBUS_CONN_MAX_MSGS 256 + +/* maximum number of queued requests waiting for a reply */ +#define KDBUS_CONN_MAX_REQUESTS_PENDING 128 + +/* maximum message payload size */ +#define KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE (2 * 1024UL * 1024UL) + +int kdbus_test_message_basic(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + struct kdbus_conn *sender; + struct kdbus_msg *msg; + uint64_t cookie = 0x1234abcd5678eeff; + uint64_t offset; + int ret; + + sender = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(sender != NULL); + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(sender); + ASSERT_RETURN(ret == 0); + + /* send over 1st connection */ + ret = kdbus_msg_send(sender, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + /* Make sure that we do get our own broadcasts */ + ret = kdbus_msg_recv(sender, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* ... and receive on the 2nd */ + ret = kdbus_msg_recv_poll(conn, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* Msgs that expect a reply must have timeout and cookie */ + ret = kdbus_msg_send(sender, NULL, 0, KDBUS_MSG_EXPECT_REPLY, + 0, 0, conn->id); + ASSERT_RETURN(ret == -EINVAL); + + /* Faked replies with a valid reply cookie are rejected */ + ret = kdbus_msg_send_reply(conn, time(NULL) ^ cookie, sender->id); + ASSERT_RETURN(ret == -EPERM); + + ret = kdbus_free(conn, offset); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(sender); + kdbus_conn_free(conn); + + return TEST_OK; +} + +static int msg_recv_prio(struct kdbus_conn *conn, + int64_t requested_prio, + int64_t expected_prio) +{ + struct kdbus_cmd_recv recv = { + .size = sizeof(recv), + .flags = KDBUS_RECV_USE_PRIORITY, + .priority = requested_prio, + }; + struct kdbus_msg *msg; + int ret; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret < 0) { + kdbus_printf("error receiving message: %d (%m)\n", -errno); + return ret; + } + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + kdbus_msg_dump(conn, msg); + + if (msg->priority != expected_prio) { + kdbus_printf("expected message prio %lld, got %lld\n", + (unsigned long long) expected_prio, + (unsigned long long) msg->priority); + return -EINVAL; + } + + kdbus_msg_free(msg); + ret = kdbus_free(conn, recv.msg.offset); + if (ret < 0) + return ret; + + return 0; +} + +int kdbus_test_message_prio(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b; + uint64_t cookie = 0; + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(a && b); + + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, 25, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -600, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, 10, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -35, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -100, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, 20, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -15, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -800, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -150, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, 10, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -800, a->id) == 0); + ASSERT_RETURN(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -10, a->id) == 0); + + ASSERT_RETURN(msg_recv_prio(a, -200, -800) == 0); + ASSERT_RETURN(msg_recv_prio(a, -100, -800) == 0); + ASSERT_RETURN(msg_recv_prio(a, -400, -600) == 0); + ASSERT_RETURN(msg_recv_prio(a, -400, -600) == -EAGAIN); + ASSERT_RETURN(msg_recv_prio(a, 10, -150) == 0); + ASSERT_RETURN(msg_recv_prio(a, 10, -100) == 0); + + kdbus_printf("--- get priority (all)\n"); + ASSERT_RETURN(kdbus_msg_recv(a, NULL, NULL) == 0); + + kdbus_conn_free(a); + kdbus_conn_free(b); + + return TEST_OK; +} + +static int kdbus_test_notify_kernel_quota(struct kdbus_test_env *env) +{ + int ret; + unsigned int i; + struct kdbus_conn *conn; + struct kdbus_conn *reader; + struct kdbus_msg *msg = NULL; + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + + reader = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(reader); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* Register for ID signals */ + ret = kdbus_add_match_id(reader, 0x1, KDBUS_ITEM_ID_ADD, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_id(reader, 0x2, KDBUS_ITEM_ID_REMOVE, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + /* Each iteration two notifications: add and remove ID */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS / 2; i++) { + struct kdbus_conn *notifier; + + notifier = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(notifier); + + kdbus_conn_free(notifier); + } + + /* + * Now the reader queue is full with kernel notfications, + * but as a user we still have room to push our messages. + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, 0, reader->id); + ASSERT_RETURN(ret == 0); + + /* More ID kernel notifications that will be lost */ + kdbus_conn_free(conn); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + kdbus_conn_free(conn); + + /* + * We lost only 3 packets since only signal msgs are + * accounted. The connection ID add/remove notification + */ + ret = kdbus_cmd_recv(reader->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS); + ASSERT_RETURN(recv.dropped_msgs == 3); + + msg = (struct kdbus_msg *)(reader->buf + recv.msg.offset); + kdbus_msg_free(msg); + + /* Read our queue */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS - 1; i++) { + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + + ret = kdbus_cmd_recv(reader->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(!(recv.return_flags & + KDBUS_RECV_RETURN_DROPPED_MSGS)); + + msg = (struct kdbus_msg *)(reader->buf + recv.msg.offset); + kdbus_msg_free(msg); + } + + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + kdbus_conn_free(reader); + + return 0; +} + +/* Return the number of message successfully sent */ +static int kdbus_fill_conn_queue(struct kdbus_conn *conn_src, + uint64_t dst_id, + unsigned int max_msgs) +{ + unsigned int i; + uint64_t cookie = 0; + size_t size; + struct kdbus_cmd_send cmd = {}; + struct kdbus_msg *msg; + int ret; + + size = sizeof(struct kdbus_msg); + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn_src->id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + for (i = 0; i < max_msgs; i++) { + msg->cookie = cookie++; + ret = kdbus_cmd_send(conn_src->fd, &cmd); + if (ret < 0) + break; + } + + free(msg); + + return i; +} + +static int kdbus_test_activator_quota(struct kdbus_test_env *env) +{ + int ret; + unsigned int i; + unsigned int activator_msgs_count = 0; + uint64_t cookie = time(NULL); + struct kdbus_conn *conn; + struct kdbus_conn *sender; + struct kdbus_conn *activator; + struct kdbus_msg *msg; + uint64_t flags = KDBUS_NAME_REPLACE_EXISTING; + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_policy_access access = { + .type = KDBUS_POLICY_ACCESS_USER, + .id = geteuid(), + .access = KDBUS_POLICY_OWN, + }; + + activator = kdbus_hello_activator(env->buspath, "foo.test.activator", + &access, 1); + ASSERT_RETURN(activator); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + sender = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn || sender); + + ret = kdbus_list(sender, KDBUS_LIST_NAMES | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED); + ASSERT_RETURN(ret == 0); + + for (i = 0; i < KDBUS_CONN_MAX_MSGS; i++) { + ret = kdbus_msg_send(sender, "foo.test.activator", + cookie++, 0, 0, 0, + KDBUS_DST_ID_NAME); + if (ret < 0) + break; + activator_msgs_count++; + } + + /* we must have at least sent one message */ + ASSERT_RETURN_VAL(i > 0, -errno); + ASSERT_RETURN(ret == -ENOBUFS); + + /* Good, activator queue is full now */ + + /* ENXIO on direct send (activators can never be addressed by ID) */ + ret = kdbus_msg_send(conn, NULL, cookie++, 0, 0, 0, activator->id); + ASSERT_RETURN(ret == -ENXIO); + + /* can't queue more */ + ret = kdbus_msg_send(conn, "foo.test.activator", cookie++, + 0, 0, 0, KDBUS_DST_ID_NAME); + ASSERT_RETURN(ret == -ENOBUFS); + + /* no match installed, so the broadcast will not inc dropped_msgs */ + ret = kdbus_msg_send(sender, NULL, cookie++, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + /* Check activator queue */ + ret = kdbus_cmd_recv(activator->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs == 0); + + activator_msgs_count--; + + msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset); + kdbus_msg_free(msg); + + + /* Stage 1) of test check the pool memory quota */ + + /* Consume the connection pool memory */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS; i++) { + ret = kdbus_msg_send(sender, NULL, + cookie++, 0, 0, 0, conn->id); + if (ret < 0) + break; + } + + /* consume one message, so later at least one can be moved */ + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + ret = kdbus_cmd_recv(conn->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs == 0); + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + kdbus_msg_free(msg); + + /* Try to acquire the name now */ + ret = kdbus_name_acquire(conn, "foo.test.activator", &flags); + ASSERT_RETURN(ret == 0); + + /* try to read messages and see if we have lost some */ + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + ret = kdbus_cmd_recv(conn->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs != 0); + + /* number of dropped msgs < received ones (at least one was moved) */ + ASSERT_RETURN(recv.dropped_msgs < activator_msgs_count); + + /* Deduct the number of dropped msgs from the activator msgs */ + activator_msgs_count -= recv.dropped_msgs; + + msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset); + kdbus_msg_free(msg); + + /* + * Release the name and hand it back to activator, now + * we should have 'activator_msgs_count' msgs again in + * the activator queue + */ + ret = kdbus_name_release(conn, "foo.test.activator"); + ASSERT_RETURN(ret == 0); + + /* make sure that we got our previous activator msgs */ + ret = kdbus_msg_recv(activator, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->src_id == sender->id); + + activator_msgs_count--; + + kdbus_msg_free(msg); + + + /* Stage 2) of test check max message quota */ + + /* Empty conn queue */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS; i++) { + ret = kdbus_msg_recv(conn, NULL, NULL); + if (ret == -EAGAIN) + break; + } + + /* fill queue with max msgs quota */ + ret = kdbus_fill_conn_queue(sender, conn->id, KDBUS_CONN_MAX_MSGS); + ASSERT_RETURN(ret == KDBUS_CONN_MAX_MSGS); + + /* This one is lost but it is not accounted */ + ret = kdbus_msg_send(sender, NULL, + cookie++, 0, 0, 0, conn->id); + ASSERT_RETURN(ret == -ENOBUFS); + + /* Acquire the name again */ + ret = kdbus_name_acquire(conn, "foo.test.activator", &flags); + ASSERT_RETURN(ret == 0); + + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + + /* + * Try to read messages and make sure that we have lost all + * the activator messages due to quota checks. Our queue is + * already full. + */ + ret = kdbus_cmd_recv(conn->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs == activator_msgs_count); + + msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset); + kdbus_msg_free(msg); + + kdbus_conn_free(sender); + kdbus_conn_free(conn); + kdbus_conn_free(activator); + + return 0; +} + +static int kdbus_test_expected_reply_quota(struct kdbus_test_env *env) +{ + int ret; + unsigned int i, n; + unsigned int count; + uint64_t cookie = 0x1234abcd5678eeff; + struct kdbus_conn *conn; + struct kdbus_conn *connections[9]; + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + for (i = 0; i < 9; i++) { + connections[i] = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(connections[i]); + } + + count = 0; + /* Send 16 messages to 8 different connections */ + for (i = 0; i < 8; i++) { + for (n = 0; n < 16; n++) { + ret = kdbus_msg_send(conn, NULL, cookie++, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, + connections[i]->id); + if (ret < 0) + break; + + count++; + } + } + + /* + * We should have queued at least + * KDBUS_CONN_MAX_REQUESTS_PENDING method call + */ + ASSERT_RETURN(count == KDBUS_CONN_MAX_REQUESTS_PENDING); + + /* + * Now try to send a message to the last connection, + * if we have reached KDBUS_CONN_MAX_REQUESTS_PENDING + * no further requests are allowed + */ + ret = kdbus_msg_send(conn, NULL, cookie++, KDBUS_MSG_EXPECT_REPLY, + 1000000000ULL, 0, connections[8]->id); + ASSERT_RETURN(ret == -EMLINK); + + for (i = 0; i < 9; i++) + kdbus_conn_free(connections[i]); + + kdbus_conn_free(conn); + + return 0; +} + +int kdbus_test_pool_quota(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b, *c; + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *recv_msg; + struct kdbus_msg *msg; + uint64_t cookie = time(NULL); + uint64_t size; + unsigned int i; + char *payload; + int ret; + + /* just a guard */ + if (POOL_SIZE <= KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE || + POOL_SIZE % KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE != 0) + return 0; + + payload = calloc(KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE, sizeof(char)); + ASSERT_RETURN_VAL(payload, -ENOMEM); + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + c = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(a && b && c); + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = a->id; + msg->dst_id = c->id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)payload; + item->vec.size = KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE; + item = KDBUS_ITEM_NEXT(item); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + /* + * Send 2097248 bytes, a user is only allowed to get 33% of half of + * the free space of the pool, the already used space is + * accounted as free space + */ + size += KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE; + for (i = size; i < (POOL_SIZE / 2 / 3); i += size) { + msg->cookie = cookie++; + + ret = kdbus_cmd_send(a->fd, &cmd); + ASSERT_RETURN_VAL(ret == 0, ret); + } + + /* Try to get more than 33% */ + msg->cookie = cookie++; + ret = kdbus_cmd_send(a->fd, &cmd); + ASSERT_RETURN(ret == -ENOBUFS); + + /* We still can pass small messages */ + ret = kdbus_msg_send(b, NULL, cookie++, 0, 0, 0, c->id); + ASSERT_RETURN(ret == 0); + + for (i = size; i < (POOL_SIZE / 2 / 3); i += size) { + ret = kdbus_msg_recv(c, &recv_msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv_msg->src_id == a->id); + + kdbus_msg_free(recv_msg); + } + + ret = kdbus_msg_recv(c, &recv_msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv_msg->src_id == b->id); + + kdbus_msg_free(recv_msg); + + ret = kdbus_msg_recv(c, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + free(msg); + free(payload); + + kdbus_conn_free(c); + kdbus_conn_free(b); + kdbus_conn_free(a); + + return 0; +} + +int kdbus_test_message_quota(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b; + uint64_t cookie = 0; + int ret; + int i; + + ret = kdbus_test_activator_quota(env); + ASSERT_RETURN(ret == 0); + + ret = kdbus_test_notify_kernel_quota(env); + ASSERT_RETURN(ret == 0); + + ret = kdbus_test_pool_quota(env); + ASSERT_RETURN(ret == 0); + + ret = kdbus_test_expected_reply_quota(env); + ASSERT_RETURN(ret == 0); + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + + ret = kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS); + ASSERT_RETURN(ret == KDBUS_CONN_MAX_MSGS); + + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 0, a->id); + ASSERT_RETURN(ret == -ENOBUFS); + + for (i = 0; i < KDBUS_CONN_MAX_MSGS; ++i) { + ret = kdbus_msg_recv(a, NULL, NULL); + ASSERT_RETURN(ret == 0); + } + + ret = kdbus_msg_recv(a, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS + 1); + ASSERT_RETURN(ret == KDBUS_CONN_MAX_MSGS); + + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 0, a->id); + ASSERT_RETURN(ret == -ENOBUFS); + + kdbus_conn_free(a); + kdbus_conn_free(b); + + return TEST_OK; +} + +int kdbus_test_memory_access(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b; + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t test_addr = 0; + char line[256]; + uint64_t size; + FILE *f; + int ret; + + /* + * Search in /proc/kallsyms for the address of a kernel symbol that + * should always be there, regardless of the config. Use that address + * in a PAYLOAD_VEC item and make sure it's inaccessible. + */ + + f = fopen("/proc/kallsyms", "r"); + if (!f) + return TEST_SKIP; + + while (fgets(line, sizeof(line), f)) { + char *s = line; + + if (!strsep(&s, " ")) + continue; + + if (!strsep(&s, " ")) + continue; + + if (!strncmp(s, "mutex_lock", 10)) { + test_addr = strtoull(line, NULL, 16); + break; + } + } + + fclose(f); + + if (!test_addr) + return TEST_SKIP; + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(a && b); + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = alloca(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = a->id; + msg->dst_id = b->id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = test_addr; + item->vec.size = sizeof(void*); + item = KDBUS_ITEM_NEXT(item); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(a->fd, &cmd); + ASSERT_RETURN(ret == -EFAULT); + + kdbus_conn_free(b); + kdbus_conn_free(a); + + return 0; +} diff --git a/tools/testing/selftests/kdbus/test-metadata-ns.c b/tools/testing/selftests/kdbus/test-metadata-ns.c new file mode 100644 index 000000000..1f6edc090 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-metadata-ns.c @@ -0,0 +1,500 @@ +/* + * Test metadata in new namespaces. Even if our tests can run + * in a namespaced setup, this test is necessary so we can inspect + * metadata on the same kdbusfs but between multiple namespaces + */ + +#include <stdio.h> +#include <string.h> +#include <sched.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/eventfd.h> +#include <sys/syscall.h> +#include <sys/capability.h> +#include <linux/sched.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static const struct kdbus_creds privileged_creds = {}; + +static const struct kdbus_creds unmapped_creds = { + .uid = UNPRIV_UID, + .euid = UNPRIV_UID, + .suid = UNPRIV_UID, + .fsuid = UNPRIV_UID, + .gid = UNPRIV_GID, + .egid = UNPRIV_GID, + .sgid = UNPRIV_GID, + .fsgid = UNPRIV_GID, +}; + +static const struct kdbus_pids unmapped_pids = {}; + +/* Get only the first item */ +static struct kdbus_item *kdbus_get_item(struct kdbus_msg *msg, + uint64_t type) +{ + struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, msg, items) + if (item->type == type) + return item; + + return NULL; +} + +static int kdbus_match_kdbus_creds(struct kdbus_msg *msg, + const struct kdbus_creds *expected_creds) +{ + struct kdbus_item *item; + + item = kdbus_get_item(msg, KDBUS_ITEM_CREDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(memcmp(&item->creds, expected_creds, + sizeof(struct kdbus_creds)) == 0); + + return 0; +} + +static int kdbus_match_kdbus_pids(struct kdbus_msg *msg, + const struct kdbus_pids *expected_pids) +{ + struct kdbus_item *item; + + item = kdbus_get_item(msg, KDBUS_ITEM_PIDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(memcmp(&item->pids, expected_pids, + sizeof(struct kdbus_pids)) == 0); + + return 0; +} + +static int __kdbus_clone_userns_test(const char *bus, + struct kdbus_conn *conn, + uint64_t grandpa_pid, + int signal_fd) +{ + int clone_ret; + int ret; + struct kdbus_msg *msg = NULL; + const struct kdbus_item *item; + uint64_t cookie = time(NULL) ^ 0xdeadbeef; + struct kdbus_conn *unpriv_conn = NULL; + struct kdbus_pids parent_pids = { + .pid = getppid(), + .tid = getppid(), + .ppid = grandpa_pid, + }; + + ret = drop_privileges(UNPRIV_UID, UNPRIV_GID); + ASSERT_EXIT(ret == 0); + + unpriv_conn = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(unpriv_conn); + + ret = kdbus_add_match_empty(unpriv_conn); + ASSERT_EXIT(ret == 0); + + /* + * ping privileged connection from this new unprivileged + * one + */ + + ret = kdbus_msg_send(unpriv_conn, NULL, cookie, 0, 0, + 0, conn->id); + ASSERT_EXIT(ret == 0); + + /* + * Since we just dropped privileges, the dumpable flag + * was just cleared which makes the /proc/$clone_child/uid_map + * to be owned by root, hence any userns uid mapping will fail + * with -EPERM since the mapping will be done by uid 65534. + * + * To avoid this set the dumpable flag again which makes + * procfs update the /proc/$clone_child/ inodes owner to 65534. + * + * Using this we will be able write to /proc/$clone_child/uid_map + * as uid 65534 and map the uid 65534 to 0 inside the user namespace. + */ + ret = prctl(PR_SET_DUMPABLE, SUID_DUMP_USER); + ASSERT_EXIT(ret == 0); + + /* Make child privileged in its new userns and run tests */ + + ret = RUN_CLONE_CHILD(&clone_ret, + SIGCHLD | CLONE_NEWUSER | CLONE_NEWPID, + ({ 0; /* Clone setup, nothing */ }), + ({ + eventfd_t event_status = 0; + struct kdbus_conn *userns_conn; + + /* ping connection from the new user namespace */ + userns_conn = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(userns_conn); + + ret = kdbus_add_match_empty(userns_conn); + ASSERT_EXIT(ret == 0); + + cookie++; + ret = kdbus_msg_send(userns_conn, NULL, cookie, + 0, 0, 0, conn->id); + ASSERT_EXIT(ret == 0); + + /* Parent did send */ + ret = eventfd_read(signal_fd, &event_status); + ASSERT_RETURN(ret >= 0 && event_status == 1); + + /* + * Receive from privileged connection + */ + kdbus_printf("Privileged → unprivileged/privileged " + "in its userns " + "(different userns and pidns):\n"); + ret = kdbus_msg_recv_poll(userns_conn, 300, &msg, NULL); + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == userns_conn->id); + + item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); + ASSERT_EXIT(item); + + /* uid/gid not mapped, so we have unpriv cached creds */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_EXIT(ret == 0); + + /* + * Diffent pid namepsaces. This is the child pidns + * so it should not see its parent kdbus_pids + */ + ret = kdbus_match_kdbus_pids(msg, &unmapped_pids); + ASSERT_EXIT(ret == 0); + + kdbus_msg_free(msg); + + + /* + * Receive broadcast from privileged connection + */ + kdbus_printf("Privileged → unprivileged/privileged " + "in its userns " + "(different userns and pidns):\n"); + ret = kdbus_msg_recv_poll(userns_conn, 300, &msg, NULL); + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == KDBUS_DST_ID_BROADCAST); + + item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); + ASSERT_EXIT(item); + + /* uid/gid not mapped, so we have unpriv cached creds */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_EXIT(ret == 0); + + /* + * Diffent pid namepsaces. This is the child pidns + * so it should not see its parent kdbus_pids + */ + ret = kdbus_match_kdbus_pids(msg, &unmapped_pids); + ASSERT_EXIT(ret == 0); + + kdbus_msg_free(msg); + + kdbus_conn_free(userns_conn); + }), + ({ + /* Parent setup map child uid/gid */ + ret = userns_map_uid_gid(pid, "0 65534 1", "0 65534 1"); + ASSERT_EXIT(ret == 0); + }), + ({ 0; })); + /* Unprivileged was not able to create user namespace */ + if (clone_ret == -EPERM) { + kdbus_printf("-- CLONE_NEWUSER TEST Failed for " + "uid: %u\n -- Make sure that your kernel " + "do not allow CLONE_NEWUSER for " + "unprivileged users\n", UNPRIV_UID); + ret = 0; + goto out; + } + + ASSERT_EXIT(ret == 0); + + + /* + * Receive from privileged connection + */ + kdbus_printf("\nPrivileged → unprivileged (same namespaces):\n"); + ret = kdbus_msg_recv_poll(unpriv_conn, 300, &msg, NULL); + + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == unpriv_conn->id); + + /* will get the privileged creds */ + ret = kdbus_match_kdbus_creds(msg, &privileged_creds); + ASSERT_EXIT(ret == 0); + + /* Same pidns so will get the kdbus_pids */ + ret = kdbus_match_kdbus_pids(msg, &parent_pids); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + + + /* + * Receive broadcast from privileged connection + */ + kdbus_printf("\nPrivileged → unprivileged (same namespaces):\n"); + ret = kdbus_msg_recv_poll(unpriv_conn, 300, &msg, NULL); + + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == KDBUS_DST_ID_BROADCAST); + + /* will get the privileged creds */ + ret = kdbus_match_kdbus_creds(msg, &privileged_creds); + ASSERT_EXIT(ret == 0); + + ret = kdbus_match_kdbus_pids(msg, &parent_pids); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + +out: + kdbus_conn_free(unpriv_conn); + + return ret; +} + +static int kdbus_clone_userns_test(const char *bus, + struct kdbus_conn *conn) +{ + int ret, status, efd; + pid_t pid, ppid; + uint64_t unpriv_conn_id, userns_conn_id; + struct kdbus_msg *msg; + const struct kdbus_item *item; + struct kdbus_pids expected_pids; + struct kdbus_conn *monitor; + + kdbus_printf("STARTING TEST 'metadata-ns'.\n"); + + monitor = kdbus_hello(bus, KDBUS_HELLO_MONITOR, NULL, 0); + ASSERT_EXIT(monitor); + + /* + * parent will signal to child that is in its + * userns to read its queue + */ + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + ppid = getppid(); + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, -errno); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT_VAL(ret == 0, -errno); + + ret = __kdbus_clone_userns_test(bus, conn, ppid, efd); + _exit(ret); + } + + + /* Phase 1) privileged receives from unprivileged */ + + /* + * Receive from the unprivileged child + */ + kdbus_printf("\nUnprivileged → privileged (same namespaces):\n"); + ret = kdbus_msg_recv_poll(conn, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + unpriv_conn_id = msg->src_id; + + /* Unprivileged user */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_RETURN(ret == 0); + + /* Set the expected creds_pids */ + expected_pids = (struct kdbus_pids) { + .pid = pid, + .tid = pid, + .ppid = getpid(), + }; + ret = kdbus_match_kdbus_pids(msg, &expected_pids); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + + + /* + * Receive from the unprivileged that is in his own + * userns and pidns + */ + + kdbus_printf("\nUnprivileged/privileged in its userns → privileged " + "(different userns and pidns)\n"); + ret = kdbus_msg_recv_poll(conn, 300, &msg, NULL); + if (ret == -ETIMEDOUT) + /* perhaps unprivileged userns is not allowed */ + goto wait; + + ASSERT_RETURN(ret == 0); + + userns_conn_id = msg->src_id; + + item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); + ASSERT_RETURN(item); + + /* + * Compare received items, creds must be translated into + * the receiver user namespace, so the user is unprivileged + */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_RETURN(ret == 0); + + /* + * We should have the kdbus_pids since we are the parent + * pidns + */ + item = kdbus_get_item(msg, KDBUS_ITEM_PIDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(memcmp(&item->pids, &unmapped_pids, + sizeof(struct kdbus_pids)) != 0); + + /* + * Parent pid of the unprivileged/privileged in its userns + * is the unprivileged child pid that was forked here. + */ + ASSERT_RETURN((uint64_t)pid == item->pids.ppid); + + kdbus_msg_free(msg); + + + /* Phase 2) Privileged connection sends now 3 packets */ + + /* + * Sending to unprivileged connections a unicast + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, + 0, unpriv_conn_id); + ASSERT_RETURN(ret == 0); + + /* signal to child that is in its userns */ + ret = eventfd_write(efd, 1); + ASSERT_EXIT(ret == 0); + + /* + * Sending to unprivileged/privilged in its userns + * connections a unicast + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, + 0, userns_conn_id); + ASSERT_RETURN(ret == 0); + + /* + * Sending to unprivileged connections a broadcast + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, + 0, KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + +wait: + ret = waitpid(pid, &status, 0); + ASSERT_RETURN(ret >= 0); + + ASSERT_RETURN(WIFEXITED(status)) + ASSERT_RETURN(!WEXITSTATUS(status)); + + /* Dump monitor queue */ + kdbus_printf("\n\nMonitor queue:\n"); + for (;;) { + ret = kdbus_msg_recv_poll(monitor, 100, &msg, NULL); + if (ret < 0) + break; + + if (msg->payload_type == KDBUS_PAYLOAD_DBUS) { + /* + * Parent pidns should see all the + * pids + */ + item = kdbus_get_item(msg, KDBUS_ITEM_PIDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(item->pids.pid != 0 && + item->pids.tid != 0 && + item->pids.ppid != 0); + } + + kdbus_msg_free(msg); + } + + kdbus_conn_free(monitor); + close(efd); + + return 0; +} + +int kdbus_test_metadata_ns(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_conn *holder, *conn; + struct kdbus_policy_access policy_access = { + /* Allow world so we can inspect metadata in namespace */ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = geteuid(), + .access = KDBUS_POLICY_TALK, + }; + + /* + * We require user-namespaces and all uids/gids + * should be mapped (we can just require the necessary ones) + */ + if (!config_user_ns_is_enabled() || + !all_uids_gids_are_mapped()) + return TEST_SKIP; + + ret = test_is_capable(CAP_SETUID, CAP_SETGID, CAP_SYS_ADMIN, -1); + ASSERT_RETURN(ret >= 0); + + /* no enough privileges, SKIP test */ + if (!ret) + return TEST_SKIP; + + holder = kdbus_hello_registrar(env->buspath, "com.example.metadata", + &policy_access, 1, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn, "com.example.metadata", NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_clone_userns_test(env->buspath, conn); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(holder); + kdbus_conn_free(conn); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-monitor.c b/tools/testing/selftests/kdbus/test-monitor.c new file mode 100644 index 000000000..e00d738a3 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-monitor.c @@ -0,0 +1,176 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/mman.h> +#include <sys/capability.h> +#include <sys/wait.h> + +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +int kdbus_test_monitor(struct kdbus_test_env *env) +{ + struct kdbus_conn *monitor, *conn; + unsigned int cookie = 0xdeadbeef; + struct kdbus_msg *msg; + uint64_t offset = 0; + int ret; + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* add matches to make sure the monitor do not trigger an item add or + * remove on connect and disconnect, respectively. + */ + ret = kdbus_add_match_id(conn, 0x1, KDBUS_ITEM_ID_ADD, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_id(conn, 0x2, KDBUS_ITEM_ID_REMOVE, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + /* register a monitor */ + monitor = kdbus_hello(env->buspath, KDBUS_HELLO_MONITOR, NULL, 0); + ASSERT_RETURN(monitor); + + /* make sure we did not receive a monitor connect notification */ + ret = kdbus_msg_recv(conn, &msg, &offset); + ASSERT_RETURN(ret == -EAGAIN); + + /* check that a monitor cannot acquire a name */ + ret = kdbus_name_acquire(monitor, "foo.bar.baz", NULL); + ASSERT_RETURN(ret == -EOPNOTSUPP); + + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, conn->id); + ASSERT_RETURN(ret == 0); + + /* the recipient should have gotten the message */ + ret = kdbus_msg_recv(conn, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + kdbus_msg_free(msg); + kdbus_free(conn, offset); + + /* and so should the monitor */ + ret = kdbus_msg_recv(monitor, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + /* Installing matches for monitors must fais must fail */ + ret = kdbus_add_match_empty(monitor); + ASSERT_RETURN(ret == -EOPNOTSUPP); + + cookie++; + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + /* The monitor should get the message. */ + ret = kdbus_msg_recv_poll(monitor, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + /* + * Since we are the only monitor, update the attach flags + * and tell we are not interessted in attach flags recv + */ + + ret = kdbus_conn_update_attach_flags(monitor, + _KDBUS_ATTACH_ALL, + 0); + ASSERT_RETURN(ret == 0); + + cookie++; + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(monitor, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + ret = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + /* + * Now we are interested in KDBUS_ITEM_TIMESTAMP and + * KDBUS_ITEM_CREDS + */ + ret = kdbus_conn_update_attach_flags(monitor, + _KDBUS_ATTACH_ALL, + KDBUS_ATTACH_TIMESTAMP | + KDBUS_ATTACH_CREDS); + ASSERT_RETURN(ret == 0); + + cookie++; + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(monitor, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + ret = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(ret == 1); + + ret = kdbus_item_in_message(msg, KDBUS_ITEM_CREDS); + ASSERT_RETURN(ret == 1); + + /* the KDBUS_ITEM_PID_COMM was not requested */ + ret = kdbus_item_in_message(msg, KDBUS_ITEM_PID_COMM); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + kdbus_conn_free(monitor); + /* make sure we did not receive a monitor disconnect notification */ + ret = kdbus_msg_recv(conn, &msg, &offset); + ASSERT_RETURN(ret == -EAGAIN); + + kdbus_conn_free(conn); + + /* Make sure that monitor as unprivileged is not allowed */ + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + if (ret && all_uids_gids_are_mapped()) { + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({ + monitor = kdbus_hello(env->buspath, + KDBUS_HELLO_MONITOR, + NULL, 0); + ASSERT_EXIT(!monitor && errno == EPERM); + + _exit(EXIT_SUCCESS); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + } + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-names.c b/tools/testing/selftests/kdbus/test-names.c new file mode 100644 index 000000000..66ebb4737 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-names.c @@ -0,0 +1,194 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <getopt.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +static int conn_is_name_owner(const struct kdbus_conn *conn, + const char *needle) +{ + struct kdbus_cmd_list cmd_list = { .size = sizeof(cmd_list) }; + struct kdbus_info *name, *list; + bool found = false; + int ret; + + cmd_list.flags = KDBUS_LIST_NAMES; + + ret = kdbus_cmd_list(conn->fd, &cmd_list); + ASSERT_RETURN(ret == 0); + + list = (struct kdbus_info *)(conn->buf + cmd_list.offset); + KDBUS_FOREACH(name, list, cmd_list.list_size) { + struct kdbus_item *item; + const char *n = NULL; + + KDBUS_ITEM_FOREACH(item, name, items) + if (item->type == KDBUS_ITEM_OWNED_NAME) + n = item->name.name; + + if (name->id == conn->id && + n && strcmp(needle, n) == 0) { + found = true; + break; + } + } + + ret = kdbus_free(conn, cmd_list.offset); + ASSERT_RETURN(ret == 0); + + return found ? 0 : -1; +} + +int kdbus_test_name_basic(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + char *name, *dot_name, *invalid_name, *wildcard_name; + int ret; + + name = "foo.bla.blaz"; + dot_name = ".bla.blaz"; + invalid_name = "foo"; + wildcard_name = "foo.bla.bl.*"; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* acquire name "foo.bar.xxx" name */ + ret = kdbus_name_acquire(conn, "foo.bar.xxx", NULL); + ASSERT_RETURN(ret == 0); + + /* Name is not valid, must fail */ + ret = kdbus_name_acquire(env->conn, dot_name, NULL); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_name_acquire(env->conn, invalid_name, NULL); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_name_acquire(env->conn, wildcard_name, NULL); + ASSERT_RETURN(ret == -EINVAL); + + /* check that we can acquire a name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* ... and release it again */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret != 0); + + /* check that we can't release it again */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == -ESRCH); + + /* check that we can't release a name that we don't own */ + ret = kdbus_name_release(env->conn, "foo.bar.xxx"); + ASSERT_RETURN(ret == -EADDRINUSE); + + /* Name is not valid, must fail */ + ret = kdbus_name_release(env->conn, dot_name); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_name_release(env->conn, invalid_name); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_name_release(env->conn, wildcard_name); + ASSERT_RETURN(ret == -ESRCH); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_name_conflict(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + char *name; + int ret; + + name = "foo.bla.blaz"; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* allow the new connection to own the same name */ + /* acquire name from the 1st connection */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* check that we can't acquire it again from the 1st connection */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == -EALREADY); + + /* check that we also can't acquire it again from the 2nd connection */ + ret = kdbus_name_acquire(conn, name, NULL); + ASSERT_RETURN(ret == -EEXIST); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_name_queue(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + const char *name; + uint64_t flags; + int ret; + + name = "foo.bla.blaz"; + + flags = KDBUS_NAME_ALLOW_REPLACEMENT; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* allow the new connection to own the same name */ + /* acquire name from the 1st connection */ + ret = kdbus_name_acquire(env->conn, name, &flags); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* queue the 2nd connection as waiting owner */ + flags = KDBUS_NAME_QUEUE; + ret = kdbus_name_acquire(conn, name, &flags); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(flags & KDBUS_NAME_IN_QUEUE); + + /* release name from 1st connection */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* now the name should be owned by the 2nd connection */ + ret = conn_is_name_owner(conn, name); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-policy-ns.c b/tools/testing/selftests/kdbus/test-policy-ns.c new file mode 100644 index 000000000..3437012f9 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-policy-ns.c @@ -0,0 +1,632 @@ +/* + * Test metadata and policies in new namespaces. Even if our tests + * can run in a namespaced setup, this test is necessary so we can + * inspect policies on the same kdbusfs but between multiple + * namespaces. + * + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <pthread.h> +#include <sched.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/eventfd.h> +#include <sys/syscall.h> +#include <sys/capability.h> +#include <linux/sched.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#define MAX_CONN 64 +#define POLICY_NAME "foo.test.policy-test" + +#define KDBUS_CONN_MAX_MSGS_PER_USER 16 + +/** + * Note: this test can be used to inspect policy_db->talk_access_hash + * + * The purpose of these tests: + * 1) Check KDBUS_POLICY_TALK + * 2) Check the cache state: kdbus_policy_db->talk_access_hash + * Should be extended + */ + +/** + * Check a list of connections against conn_db[0] + * conn_db[0] will own the name "foo.test.policy-test" and the + * policy holder connection for this name will update the policy + * entries, so different use cases can be tested. + */ +static struct kdbus_conn **conn_db; + +static void *kdbus_recv_echo(void *ptr) +{ + int ret; + struct kdbus_conn *conn = ptr; + + ret = kdbus_msg_recv_poll(conn, 200, NULL, NULL); + + return (void *)(long)ret; +} + +/* Trigger kdbus_policy_set() */ +static int kdbus_set_policy_talk(struct kdbus_conn *conn, + const char *name, + uid_t id, unsigned int type) +{ + int ret; + struct kdbus_policy_access access = { + .type = type, + .id = id, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn, name, &access, 1); + ASSERT_RETURN(ret == 0); + + return TEST_OK; +} + +/* return TEST_OK or TEST_ERR on failure */ +static int kdbus_register_same_activator(char *bus, const char *name, + struct kdbus_conn **c) +{ + int ret; + struct kdbus_conn *activator; + + activator = kdbus_hello_activator(bus, name, NULL, 0); + if (activator) { + *c = activator; + fprintf(stderr, "--- error was able to register name twice '%s'.\n", + name); + return TEST_ERR; + } + + ret = -errno; + /* -EEXIST means test succeeded */ + if (ret == -EEXIST) + return TEST_OK; + + return TEST_ERR; +} + +/* return TEST_OK or TEST_ERR on failure */ +static int kdbus_register_policy_holder(char *bus, const char *name, + struct kdbus_conn **conn) +{ + struct kdbus_conn *c; + struct kdbus_policy_access access[2]; + + access[0].type = KDBUS_POLICY_ACCESS_USER; + access[0].access = KDBUS_POLICY_OWN; + access[0].id = geteuid(); + + access[1].type = KDBUS_POLICY_ACCESS_WORLD; + access[1].access = KDBUS_POLICY_TALK; + access[1].id = geteuid(); + + c = kdbus_hello_registrar(bus, name, access, 2, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(c); + + *conn = c; + + return TEST_OK; +} + +/** + * Create new threads for receiving from multiple senders, + * The 'conn_db' will be populated by newly created connections. + * Caller should free all allocated connections. + * + * return 0 on success, negative errno on failure. + */ +static int kdbus_recv_in_threads(const char *bus, const char *name, + struct kdbus_conn **conn_db) +{ + int ret; + bool pool_full = false; + unsigned int sent_packets = 0; + unsigned int lost_packets = 0; + unsigned int i, tid; + unsigned long dst_id; + unsigned long cookie = 1; + unsigned int thread_nr = MAX_CONN - 1; + pthread_t thread_id[MAX_CONN - 1] = {'\0'}; + + dst_id = name ? KDBUS_DST_ID_NAME : conn_db[0]->id; + + for (tid = 0, i = 1; tid < thread_nr; tid++, i++) { + ret = pthread_create(&thread_id[tid], NULL, + kdbus_recv_echo, (void *)conn_db[0]); + if (ret < 0) { + ret = -errno; + kdbus_printf("error pthread_create: %d (%m)\n", + ret); + break; + } + + /* just free before re-using */ + kdbus_conn_free(conn_db[i]); + conn_db[i] = NULL; + + /* We need to create connections here */ + conn_db[i] = kdbus_hello(bus, 0, NULL, 0); + if (!conn_db[i]) { + ret = -errno; + break; + } + + ret = kdbus_add_match_empty(conn_db[i]); + if (ret < 0) + break; + + ret = kdbus_msg_send(conn_db[i], name, cookie++, + 0, 0, 0, dst_id); + if (ret < 0) { + /* + * Receivers are not reading their messages, + * not scheduled ?! + * + * So set the pool full here, perhaps the + * connection pool or queue was full, later + * recheck receivers errors + */ + if (ret == -ENOBUFS || ret == -EXFULL) + pool_full = true; + break; + } + + sent_packets++; + } + + for (tid = 0; tid < thread_nr; tid++) { + int thread_ret = 0; + + if (thread_id[tid]) { + pthread_join(thread_id[tid], (void *)&thread_ret); + if (thread_ret < 0) { + /* Update only if send did not fail */ + if (ret == 0) + ret = thread_ret; + + lost_packets++; + } + } + } + + /* + * When sending if we did fail with -ENOBUFS or -EXFULL + * then we should have set lost_packet and we should at + * least have sent_packets set to KDBUS_CONN_MAX_MSGS_PER_USER + */ + if (pool_full) { + ASSERT_RETURN(lost_packets > 0); + + /* + * We should at least send KDBUS_CONN_MAX_MSGS_PER_USER + * + * For every send operation we create a thread to + * recv the packet, so we keep the queue clean + */ + ASSERT_RETURN(sent_packets >= KDBUS_CONN_MAX_MSGS_PER_USER); + + /* + * Set ret to zero since we only failed due to + * the receiving threads that have not been + * scheduled + */ + ret = 0; + } + + return ret; +} + +/* Return: TEST_OK or TEST_ERR on failure */ +static int kdbus_normal_test(const char *bus, const char *name, + struct kdbus_conn **conn_db) +{ + int ret; + + ret = kdbus_recv_in_threads(bus, name, conn_db); + ASSERT_RETURN(ret >= 0); + + return TEST_OK; +} + +static int kdbus_fork_test_by_id(const char *bus, + struct kdbus_conn **conn_db, + int parent_status, int child_status) +{ + int ret; + pid_t pid; + uint64_t cookie = 0x9876ecba; + struct kdbus_msg *msg = NULL; + uint64_t offset = 0; + int status = 0; + + /* + * If the child_status is not EXIT_SUCCESS, then we expect + * that sending from the child will fail, thus receiving + * from parent must error with -ETIMEDOUT, and vice versa. + */ + bool parent_timedout = !!child_status; + bool child_timedout = !!parent_status; + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + struct kdbus_conn *conn_src; + + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT(ret == 0); + + ret = drop_privileges(65534, 65534); + ASSERT_EXIT(ret == 0); + + conn_src = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(conn_src); + + ret = kdbus_add_match_empty(conn_src); + ASSERT_EXIT(ret == 0); + + /* + * child_status is always checked against send + * operations, in case it fails always return + * EXIT_FAILURE. + */ + ret = kdbus_msg_send(conn_src, NULL, cookie, + 0, 0, 0, conn_db[0]->id); + ASSERT_EXIT(ret == child_status); + + ret = kdbus_msg_recv_poll(conn_src, 100, NULL, NULL); + + kdbus_conn_free(conn_src); + + /* + * Child kdbus_msg_recv_poll() should timeout since + * the parent_status was set to a non EXIT_SUCCESS + * value. + */ + if (child_timedout) + _exit(ret == -ETIMEDOUT ? EXIT_SUCCESS : EXIT_FAILURE); + + _exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + } + + ret = kdbus_msg_recv_poll(conn_db[0], 100, &msg, &offset); + /* + * If parent_timedout is set then this should fail with + * -ETIMEDOUT since the child_status was set to a non + * EXIT_SUCCESS value. Otherwise, assume + * that kdbus_msg_recv_poll() has succeeded. + */ + if (parent_timedout) { + ASSERT_RETURN_VAL(ret == -ETIMEDOUT, TEST_ERR); + + /* timedout no need to continue, we don't have the + * child connection ID, so just terminate. */ + goto out; + } else { + ASSERT_RETURN_VAL(ret == 0, ret); + } + + ret = kdbus_msg_send(conn_db[0], NULL, ++cookie, + 0, 0, 0, msg->src_id); + /* + * parent_status is checked against send operations, + * on failures always return TEST_ERR. + */ + ASSERT_RETURN_VAL(ret == parent_status, TEST_ERR); + + kdbus_msg_free(msg); + kdbus_free(conn_db[0], offset); + +out: + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +/* + * Return: TEST_OK, TEST_ERR or TEST_SKIP + * we return TEST_OK only if the children return with the expected + * 'expected_status' that is specified as an argument. + */ +static int kdbus_fork_test(const char *bus, const char *name, + struct kdbus_conn **conn_db, int expected_status) +{ + pid_t pid; + int ret = 0; + int status = 0; + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT(ret == 0); + + ret = drop_privileges(65534, 65534); + ASSERT_EXIT(ret == 0); + + ret = kdbus_recv_in_threads(bus, name, conn_db); + _exit(ret == expected_status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN(ret >= 0); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +/* Return EXIT_SUCCESS, EXIT_FAILURE or negative errno */ +static int __kdbus_clone_userns_test(const char *bus, + const char *name, + struct kdbus_conn **conn_db, + int expected_status) +{ + int efd; + pid_t pid; + int ret = 0; + unsigned int uid = 65534; + int status; + + ret = drop_privileges(uid, uid); + ASSERT_RETURN_VAL(ret == 0, ret); + + /* + * Since we just dropped privileges, the dumpable flag was just + * cleared which makes the /proc/$clone_child/uid_map to be + * owned by root, hence any userns uid mapping will fail with + * -EPERM since the mapping will be done by uid 65534. + * + * To avoid this set the dumpable flag again which makes procfs + * update the /proc/$clone_child/ inodes owner to 65534. + * + * Using this we will be able write to /proc/$clone_child/uid_map + * as uid 65534 and map the uid 65534 to 0 inside the user + * namespace. + */ + ret = prctl(PR_SET_DUMPABLE, SUID_DUMP_USER); + ASSERT_RETURN_VAL(ret == 0, ret); + + /* sync parent/child */ + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWUSER, NULL); + if (pid < 0) { + ret = -errno; + kdbus_printf("error clone: %d (%m)\n", ret); + /* + * Normal user not allowed to create userns, + * so nothing to worry about ? + */ + if (ret == -EPERM) { + kdbus_printf("-- CLONE_NEWUSER TEST Failed for uid: %u\n" + "-- Make sure that your kernel do not allow " + "CLONE_NEWUSER for unprivileged users\n" + "-- Upstream Commit: " + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=5eaf563e\n", + uid); + ret = 0; + } + + return ret; + } + + if (pid == 0) { + struct kdbus_conn *conn_src; + eventfd_t event_status = 0; + + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT(ret == 0); + + ret = eventfd_read(efd, &event_status); + ASSERT_EXIT(ret >= 0 && event_status == 1); + + /* ping connection from the new user namespace */ + conn_src = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(conn_src); + + ret = kdbus_add_match_empty(conn_src); + ASSERT_EXIT(ret == 0); + + ret = kdbus_msg_send(conn_src, name, 0xabcd1234, + 0, 0, 0, KDBUS_DST_ID_NAME); + kdbus_conn_free(conn_src); + + _exit(ret == expected_status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + ret = userns_map_uid_gid(pid, "0 65534 1", "0 65534 1"); + ASSERT_RETURN_VAL(ret == 0, ret); + + /* Tell child we are ready */ + ret = eventfd_write(efd, 1); + ASSERT_RETURN_VAL(ret == 0, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + close(efd); + + return status == EXIT_SUCCESS ? TEST_OK : TEST_ERR; +} + +static int kdbus_clone_userns_test(const char *bus, + const char *name, + struct kdbus_conn **conn_db, + int expected_status) +{ + pid_t pid; + int ret = 0; + int status; + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, -errno); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + if (ret < 0) + _exit(EXIT_FAILURE); + + ret = __kdbus_clone_userns_test(bus, name, conn_db, + expected_status); + _exit(ret); + } + + /* + * Receive in the original (root privileged) user namespace, + * must fail with -ETIMEDOUT. + */ + ret = kdbus_msg_recv_poll(conn_db[0], 100, NULL, NULL); + ASSERT_RETURN_VAL(ret == -ETIMEDOUT, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +int kdbus_test_policy_ns(struct kdbus_test_env *env) +{ + int i; + int ret; + struct kdbus_conn *activator = NULL; + struct kdbus_conn *policy_holder = NULL; + char *bus = env->buspath; + + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + /* no enough privileges, SKIP test */ + if (!ret) + return TEST_SKIP; + + /* we require user-namespaces */ + if (access("/proc/self/uid_map", F_OK) != 0) + return TEST_SKIP; + + /* uids/gids must be mapped */ + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + conn_db = calloc(MAX_CONN, sizeof(struct kdbus_conn *)); + ASSERT_RETURN(conn_db); + + memset(conn_db, 0, MAX_CONN * sizeof(struct kdbus_conn *)); + + conn_db[0] = kdbus_hello(bus, 0, NULL, 0); + ASSERT_RETURN(conn_db[0]); + + ret = kdbus_add_match_empty(conn_db[0]); + ASSERT_RETURN(ret == 0); + + ret = kdbus_fork_test_by_id(bus, conn_db, -EPERM, -EPERM); + ASSERT_EXIT(ret == 0); + + ret = kdbus_register_policy_holder(bus, POLICY_NAME, + &policy_holder); + ASSERT_RETURN(ret == 0); + + /* Try to register the same name with an activator */ + ret = kdbus_register_same_activator(bus, POLICY_NAME, + &activator); + ASSERT_RETURN(ret == 0); + + /* Acquire POLICY_NAME */ + ret = kdbus_name_acquire(conn_db[0], POLICY_NAME, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_normal_test(bus, POLICY_NAME, conn_db); + ASSERT_RETURN(ret == 0); + + ret = kdbus_list(conn_db[0], KDBUS_LIST_NAMES | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED); + ASSERT_RETURN(ret == 0); + + ret = kdbus_fork_test(bus, POLICY_NAME, conn_db, EXIT_SUCCESS); + ASSERT_RETURN(ret == 0); + + /* + * children connections are able to talk to conn_db[0] since + * current POLICY_NAME TALK type is KDBUS_POLICY_ACCESS_WORLD, + * so expect EXIT_SUCCESS when sending from child. However, + * since the child's connection does not own any well-known + * name, The parent connection conn_db[0] should fail with + * -EPERM but since it is a privileged bus user the TALK is + * allowed. + */ + ret = kdbus_fork_test_by_id(bus, conn_db, + EXIT_SUCCESS, EXIT_SUCCESS); + ASSERT_EXIT(ret == 0); + + /* + * Connections that can talk are perhaps being destroyed now. + * Restrict the policy and purge cache entries where the + * conn_db[0] is the destination. + * + * Now only connections with uid == 0 are allowed to talk. + */ + ret = kdbus_set_policy_talk(policy_holder, POLICY_NAME, + geteuid(), KDBUS_POLICY_ACCESS_USER); + ASSERT_RETURN(ret == 0); + + /* + * Testing connections (FORK+DROP) again: + * After setting the policy re-check connections + * we expect the children to fail with -EPERM + */ + ret = kdbus_fork_test(bus, POLICY_NAME, conn_db, -EPERM); + ASSERT_RETURN(ret == 0); + + /* + * Now expect that both parent and child to fail. + * + * Child should fail with -EPERM since we just restricted + * the POLICY_NAME TALK to uid 0 and its uid is 65534. + * + * Since the parent's connection will timeout when receiving + * from the child, we never continue. FWIW just put -EPERM. + */ + ret = kdbus_fork_test_by_id(bus, conn_db, -EPERM, -EPERM); + ASSERT_EXIT(ret == 0); + + /* Check if the name can be reached in a new userns */ + ret = kdbus_clone_userns_test(bus, POLICY_NAME, conn_db, -EPERM); + ASSERT_RETURN(ret == 0); + + for (i = 0; i < MAX_CONN; i++) + kdbus_conn_free(conn_db[i]); + + kdbus_conn_free(activator); + kdbus_conn_free(policy_holder); + + free(conn_db); + + return ret; +} diff --git a/tools/testing/selftests/kdbus/test-policy-priv.c b/tools/testing/selftests/kdbus/test-policy-priv.c new file mode 100644 index 000000000..0208638a7 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-policy-priv.c @@ -0,0 +1,1285 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <time.h> +#include <sys/capability.h> +#include <sys/eventfd.h> +#include <sys/wait.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static int test_policy_priv_by_id(const char *bus, + struct kdbus_conn *conn_dst, + bool drop_second_user, + int parent_status, + int child_status) +{ + int ret = 0; + uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef; + + ASSERT_RETURN(conn_dst); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, bus, ({ + ret = kdbus_msg_send(unpriv, NULL, + expected_cookie, 0, 0, 0, + conn_dst->id); + ASSERT_EXIT(ret == child_status); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_dst, 300, NULL, NULL); + ASSERT_RETURN(ret == parent_status); + + return 0; +} + +static int test_policy_priv_by_broadcast(const char *bus, + struct kdbus_conn *conn_dst, + int drop_second_user, + int parent_status, + int child_status) +{ + int efd; + int ret = 0; + eventfd_t event_status = 0; + struct kdbus_msg *msg = NULL; + uid_t second_uid = UNPRIV_UID; + gid_t second_gid = UNPRIV_GID; + struct kdbus_conn *child_2 = conn_dst; + uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef; + + /* Drop to another unprivileged user other than UNPRIV_UID */ + if (drop_second_user == DROP_OTHER_UNPRIV) { + second_uid = UNPRIV_UID - 1; + second_gid = UNPRIV_GID - 1; + } + + /* child will signal parent to send broadcast */ + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + struct kdbus_conn *child; + + child = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(child); + + ret = kdbus_add_match_empty(child); + ASSERT_EXIT(ret == 0); + + /* signal parent */ + ret = eventfd_write(efd, 1); + ASSERT_EXIT(ret == 0); + + /* Use a little bit high time */ + ret = kdbus_msg_recv_poll(child, 500, &msg, NULL); + ASSERT_EXIT(ret == child_status); + + /* + * If we expect the child to get the broadcast + * message, then check the received cookie. + */ + if (ret == 0) { + ASSERT_EXIT(expected_cookie == msg->cookie); + } + + /* Use expected_cookie since 'msg' might be NULL */ + ret = kdbus_msg_send(child, NULL, expected_cookie + 1, + 0, 0, 0, KDBUS_DST_ID_BROADCAST); + ASSERT_EXIT(ret == 0); + + kdbus_msg_free(msg); + kdbus_conn_free(child); + }), + ({ + if (drop_second_user == DO_NOT_DROP) { + ASSERT_RETURN(child_2); + + ret = eventfd_read(efd, &event_status); + ASSERT_RETURN(ret >= 0 && event_status == 1); + + ret = kdbus_msg_send(child_2, NULL, + expected_cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + /* drop own broadcast */ + ret = kdbus_msg_recv(child_2, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->src_id == child_2->id); + kdbus_msg_free(msg); + + /* Use a little bit high time */ + ret = kdbus_msg_recv_poll(child_2, 1000, + &msg, NULL); + ASSERT_RETURN(ret == parent_status); + + /* + * Check returned cookie in case we expect + * success. + */ + if (ret == 0) { + ASSERT_RETURN(msg->cookie == + expected_cookie + 1); + } + + kdbus_msg_free(msg); + } else { + /* + * Two unprivileged users will try to + * communicate using broadcast. + */ + ret = RUN_UNPRIVILEGED(second_uid, second_gid, ({ + child_2 = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(child_2); + + ret = kdbus_add_match_empty(child_2); + ASSERT_EXIT(ret == 0); + + ret = eventfd_read(efd, &event_status); + ASSERT_EXIT(ret >= 0 && event_status == 1); + + ret = kdbus_msg_send(child_2, NULL, + expected_cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_EXIT(ret == 0); + + /* drop own broadcast */ + ret = kdbus_msg_recv(child_2, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->src_id == child_2->id); + kdbus_msg_free(msg); + + /* Use a little bit high time */ + ret = kdbus_msg_recv_poll(child_2, 1000, + &msg, NULL); + ASSERT_EXIT(ret == parent_status); + + /* + * Check returned cookie in case we expect + * success. + */ + if (ret == 0) { + ASSERT_EXIT(msg->cookie == + expected_cookie + 1); + } + + kdbus_msg_free(msg); + kdbus_conn_free(child_2); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + } + })); + ASSERT_RETURN(ret == 0); + + close(efd); + + return ret; +} + +static void nosig(int sig) +{ +} + +static int test_priv_before_policy_upload(struct kdbus_test_env *env) +{ + int ret = 0; + struct kdbus_conn *conn; + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* + * Make sure unprivileged bus user cannot acquire names + * before registring any policy holder. + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret == 0); + + /* + * Make sure unprivileged bus users cannot talk by default + * to privileged ones, unless a policy holder that allows + * this was uploaded. + */ + + ret = test_policy_priv_by_id(env->buspath, conn, false, + -ETIMEDOUT, -EPERM); + ASSERT_RETURN(ret == 0); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + /* + * First make sure that BROADCAST with msg flag + * KDBUS_MSG_EXPECT_REPLY will fail with -ENOTUNIQ + */ + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_EXIT(ret == -ENOTUNIQ); + })); + ASSERT_RETURN(ret == 0); + + /* + * Test broadcast with a privileged connection. + * + * The first unprivileged receiver should not get the + * broadcast message sent by the privileged connection, + * since there is no a TALK policy that allows the + * unprivileged to TALK to the privileged connection. It + * will fail with -ETIMEDOUT + * + * Then second case: + * The privileged connection should get the broadcast + * message from the unprivileged one. Since the receiver is + * a privileged bus user and it has default TALK access to + * all connections it will receive those. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, conn, + DO_NOT_DROP, + 0, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + + /* + * Test broadcast with two unprivileged connections running + * under the same user. + * + * Both connections should succeed. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_SAME_UNPRIV, 0, 0); + ASSERT_RETURN(ret == 0); + + /* + * Test broadcast with two unprivileged connections running + * under different users. + * + * Both connections will fail with -ETIMEDOUT. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_OTHER_UNPRIV, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + + return ret; +} + +static int test_broadcast_after_policy_upload(struct kdbus_test_env *env) +{ + int ret; + int efd; + eventfd_t event_status = 0; + struct kdbus_msg *msg = NULL; + struct kdbus_conn *owner_a, *owner_b; + struct kdbus_conn *holder_a, *holder_b; + struct kdbus_policy_access access = {}; + uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef; + + owner_a = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner_a); + + ret = kdbus_name_acquire(owner_a, "com.example.broadcastA", NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users cannot talk by default + * to privileged ones, unless a policy holder that allows + * this was uploaded. + */ + + ++expected_cookie; + ret = test_policy_priv_by_id(env->buspath, owner_a, false, + -ETIMEDOUT, -EPERM); + ASSERT_RETURN(ret == 0); + + /* + * Make sure that privileged won't receive broadcasts unless + * it installs a match. It will fail with -ETIMEDOUT + * + * At same time check that the unprivileged connection will + * not receive the broadcast message from the privileged one + * since the privileged one owns a name with a restricted + * policy TALK (actually the TALK policy is still not + * registered so we fail by default), thus the unprivileged + * receiver is not able to TALK to that name. + */ + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(owner_a); + ASSERT_RETURN(ret == 0); + + /* + * Redo the previous test. The privileged conn owner_a is + * able to TALK to any connection so it will receive the + * broadcast message now. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, owner_a, + DO_NOT_DROP, + 0, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + /* + * Test that broadcast between two unprivileged users running + * under the same user still succeed. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_SAME_UNPRIV, 0, 0); + ASSERT_RETURN(ret == 0); + + /* + * Test broadcast with two unprivileged connections running + * under different users. + * + * Both connections will fail with -ETIMEDOUT. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_OTHER_UNPRIV, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = geteuid(), + .access = KDBUS_POLICY_OWN, + }; + + holder_a = kdbus_hello_registrar(env->buspath, + "com.example.broadcastA", + &access, 1, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder_a); + + holder_b = kdbus_hello_registrar(env->buspath, + "com.example.broadcastB", + &access, 1, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder_b); + + /* Free connections and their received messages and restart */ + kdbus_conn_free(owner_a); + + owner_a = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner_a); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(owner_a); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(owner_a, "com.example.broadcastA", NULL); + ASSERT_EXIT(ret >= 0); + + owner_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner_b); + + ret = kdbus_name_acquire(owner_b, "com.example.broadcastB", NULL); + ASSERT_EXIT(ret >= 0); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(owner_b); + ASSERT_RETURN(ret == 0); + + /* + * Test that even if "com.example.broadcastA" and + * "com.example.broadcastB" do have a TALK access by default + * they are able to signal each other using broadcast due to + * the fact they are privileged connections, they receive + * all broadcasts if the match allows it. + */ + + ++expected_cookie; + ret = kdbus_msg_send(owner_a, NULL, expected_cookie, 0, + 0, 0, KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(owner_a, 100, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == expected_cookie); + + /* Check src ID */ + ASSERT_RETURN(msg->src_id == owner_a->id); + + kdbus_msg_free(msg); + + ret = kdbus_msg_recv_poll(owner_b, 100, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == expected_cookie); + + /* Check src ID */ + ASSERT_RETURN(msg->src_id == owner_a->id); + + kdbus_msg_free(msg); + + /* Release name "com.example.broadcastB" */ + + ret = kdbus_name_release(owner_b, "com.example.broadcastB"); + ASSERT_EXIT(ret >= 0); + + /* KDBUS_POLICY_OWN for unprivileged connections */ + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = geteuid(), + .access = KDBUS_POLICY_OWN, + }; + + /* Update the policy so unprivileged will own the name */ + + ret = kdbus_conn_update_policy(holder_b, + "com.example.broadcastB", + &access, 1); + ASSERT_RETURN(ret == 0); + + /* + * Send broadcasts from an unprivileged connection that + * owns a name "com.example.broadcastB". + * + * We'll have four destinations here: + * + * 1) destination owner_a: privileged connection that owns + * "com.example.broadcastA". It will receive the broadcast + * since it is a privileged has default TALK access to all + * connections, and it is subscribed to the match. + * Will succeed. + * + * owner_b: privileged connection (running under a different + * uid) that do not own names, but with an empty broadcast + * match, so it will receive broadcasts since it has default + * TALK access to all connection. + * + * unpriv_a: unpriv connection that do not own any name. + * It will receive the broadcast since it is running under + * the same user of the one broadcasting and did install + * matches. It should get the message. + * + * unpriv_b: unpriv connection is not interested in broadcast + * messages, so it did not install broadcast matches. Should + * fail with -ETIMEDOUT + */ + + ++expected_cookie; + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({ + struct kdbus_conn *unpriv_owner; + struct kdbus_conn *unpriv_a, *unpriv_b; + + unpriv_owner = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_EXIT(unpriv_owner); + + unpriv_a = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_EXIT(unpriv_a); + + unpriv_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_EXIT(unpriv_b); + + ret = kdbus_name_acquire(unpriv_owner, + "com.example.broadcastB", + NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_add_match_empty(unpriv_a); + ASSERT_EXIT(ret == 0); + + /* Signal that we are doing broadcasts */ + ret = eventfd_write(efd, 1); + ASSERT_EXIT(ret == 0); + + /* + * Do broadcast from a connection that owns the + * names "com.example.broadcastB". + */ + ret = kdbus_msg_send(unpriv_owner, NULL, + expected_cookie, + 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_EXIT(ret == 0); + + /* + * Unprivileged connection running under the same + * user. It should succeed. + */ + ret = kdbus_msg_recv_poll(unpriv_a, 300, &msg, NULL); + ASSERT_EXIT(ret == 0 && msg->cookie == expected_cookie); + + /* + * Did not install matches, not interested in + * broadcasts + */ + ret = kdbus_msg_recv_poll(unpriv_b, 300, NULL, NULL); + ASSERT_EXIT(ret == -ETIMEDOUT); + }), + ({ + ret = eventfd_read(efd, &event_status); + ASSERT_RETURN(ret >= 0 && event_status == 1); + + /* + * owner_a must fail with -ETIMEDOUT, since it owns + * name "com.example.broadcastA" and its TALK + * access is restriced. + */ + ret = kdbus_msg_recv_poll(owner_a, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + /* + * owner_b got the broadcast from an unprivileged + * connection. + */ + ret = kdbus_msg_recv_poll(owner_b, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + })); + ASSERT_RETURN(ret == 0); + + close(efd); + + /* + * Test broadcast with two unprivileged connections running + * under different users. + * + * Both connections will fail with -ETIMEDOUT. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_OTHER_UNPRIV, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + /* Drop received broadcasts by privileged */ + ret = kdbus_msg_recv_poll(owner_a, 100, NULL, NULL); + ret = kdbus_msg_recv_poll(owner_a, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(owner_a, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = kdbus_msg_recv_poll(owner_b, 100, NULL, NULL); + ret = kdbus_msg_recv_poll(owner_b, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(owner_b, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Perform last tests, allow others to talk to name + * "com.example.broadcastA". So now receiving broadcasts + * from it should succeed since the TALK policy allow it. + */ + + /* KDBUS_POLICY_OWN for unprivileged connections */ + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = geteuid(), + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(holder_a, + "com.example.broadcastA", + &access, 1); + ASSERT_RETURN(ret == 0); + + /* + * Unprivileged is able to TALK to "com.example.broadcastA" + * now so it will receive its broadcasts + */ + ret = test_policy_priv_by_broadcast(env->buspath, owner_a, + DO_NOT_DROP, 0, 0); + ASSERT_RETURN(ret == 0); + + ++expected_cookie; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.broadcastB", + NULL); + ASSERT_EXIT(ret >= 0); + ret = kdbus_msg_send(unpriv, NULL, expected_cookie, + 0, 0, 0, KDBUS_DST_ID_BROADCAST); + ASSERT_EXIT(ret == 0); + })); + ASSERT_RETURN(ret == 0); + + /* owner_a is privileged it will get the broadcast now. */ + ret = kdbus_msg_recv_poll(owner_a, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + /* + * owner_a released name "com.example.broadcastA". It should + * receive broadcasts since it is still privileged and has + * the right match. + * + * Unprivileged connection will own a name and will try to + * signal to the privileged connection. + */ + + ret = kdbus_name_release(owner_a, "com.example.broadcastA"); + ASSERT_EXIT(ret >= 0); + + ++expected_cookie; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.broadcastB", + NULL); + ASSERT_EXIT(ret >= 0); + ret = kdbus_msg_send(unpriv, NULL, expected_cookie, + 0, 0, 0, KDBUS_DST_ID_BROADCAST); + ASSERT_EXIT(ret == 0); + })); + ASSERT_RETURN(ret == 0); + + /* owner_a will get the broadcast now. */ + ret = kdbus_msg_recv_poll(owner_a, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + kdbus_conn_free(owner_a); + kdbus_conn_free(owner_b); + kdbus_conn_free(holder_a); + kdbus_conn_free(holder_b); + + return 0; +} + +static int test_policy_priv(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_a, *conn_b, *conn, *owner; + struct kdbus_policy_access access, *acc; + sigset_t sset; + size_t num; + int ret; + + /* + * Make sure we have CAP_SETUID/SETGID so we can drop privileges + */ + + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + if (!ret) + return TEST_SKIP; + + /* make sure that uids and gids are mapped */ + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + /* + * Setup: + * conn_a: policy holder for com.example.a + * conn_b: name holder of com.example.b + */ + + signal(SIGUSR1, nosig); + sigemptyset(&sset); + sigaddset(&sset, SIGUSR1); + sigprocmask(SIG_BLOCK, &sset, NULL); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* + * Before registering any policy holder, make sure that the + * bus is secure by default. This test is necessary, it catches + * several cases where old D-Bus was vulnerable. + */ + + ret = test_priv_before_policy_upload(env); + ASSERT_RETURN(ret == 0); + + /* + * Make sure unprivileged are not able to register policy + * holders + */ + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + struct kdbus_conn *holder; + + holder = kdbus_hello_registrar(env->buspath, + "com.example.a", NULL, 0, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_EXIT(holder == NULL && errno == EPERM); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + + + /* Register policy holder */ + + conn_a = kdbus_hello_registrar(env->buspath, "com.example.a", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a); + + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_b); + + ret = kdbus_name_acquire(conn_b, "com.example.b", NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure bus-owners can always acquire names. + */ + ret = kdbus_name_acquire(conn, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + + kdbus_conn_free(conn); + + /* + * Make sure unprivileged users cannot acquire names with default + * policy assigned. + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if we make them + * world-accessible. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_OWN, + }; + + /* + * Make sure unprivileged/normal connections are not able + * to update policies + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_conn_update_policy(unpriv, "com.example.a", + &access, 1); + ASSERT_EXIT(ret == -EOPNOTSUPP); + })); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if we make them + * gid-accessible. But only if the gid matches. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = UNPRIV_GID, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = 1, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if we make them + * uid-accessible. But only if the uid matches. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = 1, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users cannot acquire names if no owner-policy + * matches, even if SEE/TALK policies match. + */ + + num = 4; + acc = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = UNPRIV_GID, + .access = KDBUS_POLICY_SEE, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_SEE, + }, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", acc, num); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if the only matching + * policy is somewhere in the middle. + */ + + num = 5; + acc = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 1, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 2, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 3, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 4, + .access = KDBUS_POLICY_OWN, + }, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", acc, num); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Clear policies + */ + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", NULL, 0); + ASSERT_RETURN(ret == 0); + + /* + * Make sure privileged bus users can _always_ talk to others. + */ + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_msg_send(conn, "com.example.b", 0xdeadbeef, 0, 0, 0, 0); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 300, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + kdbus_conn_free(conn); + + /* + * Make sure unprivileged bus users cannot talk by default. + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to equals, even without + * policy. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.c", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + struct kdbus_conn *owner; + + owner = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner); + + ret = kdbus_name_acquire(owner, "com.example.c", NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + ret = kdbus_msg_recv_poll(owner, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + kdbus_conn_free(owner); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable UID policy is set. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable GID policy is set. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = UNPRIV_GID, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable WORLD policy is set. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users cannot talk to privileged users if + * no suitable policy is set. + */ + + num = 5; + acc = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 0, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 1, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_SEE, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 3, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 4, + .access = KDBUS_POLICY_TALK, + }, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", acc, num); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable OWN privilege overwrites TALK. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure the TALK cache is reset correctly when policies are + * updated. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", + NULL, 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure the TALK cache is reset correctly when policy holders + * disconnect. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_OWN, + }; + + conn = kdbus_hello_registrar(env->buspath, "com.example.c", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn); + + ret = kdbus_conn_update_policy(conn, "com.example.c", &access, 1); + ASSERT_RETURN(ret == 0); + + owner = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner); + + ret = kdbus_name_acquire(owner, "com.example.c", NULL); + ASSERT_RETURN(ret >= 0); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + struct kdbus_conn *unpriv; + + /* wait for parent to be finished */ + sigemptyset(&sset); + ret = sigsuspend(&sset); + ASSERT_RETURN(ret == -1 && errno == EINTR); + + unpriv = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(unpriv); + + ret = kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_recv_poll(owner, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* free policy holder */ + kdbus_conn_free(conn); + + ret = kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, + 0, 0); + ASSERT_EXIT(ret == -EPERM); + + kdbus_conn_free(unpriv); + }), ({ + /* make sure policy holder is only valid in child */ + kdbus_conn_free(conn); + kill(pid, SIGUSR1); + })); + ASSERT_RETURN(ret >= 0); + + + /* + * The following tests are necessary. + */ + + ret = test_broadcast_after_policy_upload(env); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(owner); + + /* + * cleanup resources + */ + + kdbus_conn_free(conn_b); + kdbus_conn_free(conn_a); + + return TEST_OK; +} + +int kdbus_test_policy_priv(struct kdbus_test_env *env) +{ + pid_t pid; + int ret; + + /* make sure to exit() if a child returns from fork() */ + pid = getpid(); + ret = test_policy_priv(env); + if (pid != getpid()) + exit(1); + + return ret; +} diff --git a/tools/testing/selftests/kdbus/test-policy.c b/tools/testing/selftests/kdbus/test-policy.c new file mode 100644 index 000000000..96d20d5e9 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-policy.c @@ -0,0 +1,80 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int kdbus_test_policy(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_a, *conn_b; + struct kdbus_policy_access access; + int ret; + + /* Invalid name */ + conn_a = kdbus_hello_registrar(env->buspath, ".example.a", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a == NULL); + + conn_a = kdbus_hello_registrar(env->buspath, "example", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a == NULL); + + conn_a = kdbus_hello_registrar(env->buspath, "com.example.a", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a); + + conn_b = kdbus_hello_registrar(env->buspath, "com.example.b", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_b); + + /* + * Verify there cannot be any duplicate entries, except for specific vs. + * wildcard entries. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = geteuid(), + .access = KDBUS_POLICY_SEE, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_b, "com.example.a", &access, 1); + ASSERT_RETURN(ret == -EEXIST); + + ret = kdbus_conn_update_policy(conn_b, "com.example.a.*", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_a, "com.example.a.*", &access, 1); + ASSERT_RETURN(ret == -EEXIST); + + ret = kdbus_conn_update_policy(conn_a, "com.example.*", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_b, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_b, "com.example.*", &access, 1); + ASSERT_RETURN(ret == -EEXIST); + + /* Invalid name */ + ret = kdbus_conn_update_policy(conn_b, ".example.*", &access, 1); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_conn_update_policy(conn_b, "example", &access, 1); + ASSERT_RETURN(ret == -EINVAL); + + kdbus_conn_free(conn_b); + kdbus_conn_free(conn_a); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-sync.c b/tools/testing/selftests/kdbus/test-sync.c new file mode 100644 index 000000000..e2be910d2 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-sync.c @@ -0,0 +1,369 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <pthread.h> +#include <stdbool.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/eventfd.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static struct kdbus_conn *conn_a, *conn_b; +static unsigned int cookie = 0xdeadbeef; + +static void nop_handler(int sig) {} + +static int interrupt_sync(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + pid_t pid; + int ret, status; + struct kdbus_msg *msg = NULL; + struct sigaction sa = { + .sa_handler = nop_handler, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = sigaction(SIGINT, &sa, NULL); + ASSERT_EXIT(ret == 0); + + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, conn_src->id, -1); + ASSERT_EXIT(ret == -ETIMEDOUT); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + ret = kill(pid, SIGINT); + ASSERT_RETURN_VAL(ret == 0, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (WIFSIGNALED(status)) + return TEST_ERR; + + ret = kdbus_msg_recv_poll(conn_src, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int close_epipe_sync(const char *bus) +{ + pid_t pid; + int ret, status; + struct kdbus_conn *conn_src; + struct kdbus_conn *conn_dst; + struct kdbus_msg *msg = NULL; + + conn_src = kdbus_hello(bus, 0, NULL, 0); + ASSERT_RETURN(conn_src); + + ret = kdbus_add_match_empty(conn_src); + ASSERT_RETURN(ret == 0); + + conn_dst = kdbus_hello(bus, 0, NULL, 0); + ASSERT_RETURN(conn_dst); + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + uint64_t dst_id; + + /* close our reference */ + dst_id = conn_dst->id; + kdbus_conn_free(conn_dst); + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_EXIT(ret == 0 && msg->cookie == cookie); + ASSERT_EXIT(msg->src_id == dst_id); + + cookie++; + ret = kdbus_msg_send_sync(conn_src, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, dst_id, -1); + ASSERT_EXIT(ret == -EPIPE); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_send(conn_dst, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST); + ASSERT_RETURN(ret == 0); + + cookie++; + ret = kdbus_msg_recv_poll(conn_dst, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* destroy connection */ + kdbus_conn_free(conn_dst); + kdbus_conn_free(conn_src); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (!WIFEXITED(status)) + return TEST_ERR; + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int cancel_fd_sync(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + pid_t pid; + int cancel_fd; + int ret, status; + uint64_t counter = 1; + struct kdbus_msg *msg = NULL; + + cancel_fd = eventfd(0, 0); + ASSERT_RETURN_VAL(cancel_fd >= 0, cancel_fd); + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, conn_src->id, + cancel_fd); + ASSERT_EXIT(ret == -ECANCELED); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + ret = write(cancel_fd, &counter, sizeof(counter)); + ASSERT_RETURN(ret == sizeof(counter)); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (WIFSIGNALED(status)) + return TEST_ERR; + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int no_cancel_sync(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + pid_t pid; + int cancel_fd; + int ret, status; + struct kdbus_msg *msg = NULL; + + /* pass eventfd, but never signal it so it shouldn't have any effect */ + + cancel_fd = eventfd(0, 0); + ASSERT_RETURN_VAL(cancel_fd >= 0, cancel_fd); + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, conn_src->id, + cancel_fd); + ASSERT_EXIT(ret == 0); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN_VAL(ret == 0 && msg->cookie == cookie, -1); + + kdbus_msg_free(msg); + + ret = kdbus_msg_send_reply(conn_src, cookie, conn_dst->id); + ASSERT_RETURN_VAL(ret >= 0, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (WIFSIGNALED(status)) + return -1; + + return (status == EXIT_SUCCESS) ? 0 : -1; +} + +static void *run_thread_reply(void *data) +{ + int ret; + unsigned long status = TEST_OK; + + ret = kdbus_msg_recv_poll(conn_a, 3000, NULL, NULL); + if (ret < 0) + goto exit_thread; + + kdbus_printf("Thread received message, sending reply ...\n"); + + /* using an unknown cookie must fail */ + ret = kdbus_msg_send_reply(conn_a, ~cookie, conn_b->id); + if (ret != -EPERM) { + status = TEST_ERR; + goto exit_thread; + } + + ret = kdbus_msg_send_reply(conn_a, cookie, conn_b->id); + if (ret != 0) { + status = TEST_ERR; + goto exit_thread; + } + +exit_thread: + pthread_exit(NULL); + return (void *) status; +} + +int kdbus_test_sync_reply(struct kdbus_test_env *env) +{ + unsigned long status; + pthread_t thread; + int ret; + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + pthread_create(&thread, NULL, run_thread_reply, NULL); + + ret = kdbus_msg_send_sync(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_a->id, -1); + + pthread_join(thread, (void *) &status); + ASSERT_RETURN(status == 0); + ASSERT_RETURN(ret == 0); + + ret = interrupt_sync(conn_a, conn_b); + ASSERT_RETURN(ret == 0); + + ret = close_epipe_sync(env->buspath); + ASSERT_RETURN(ret == 0); + + ret = cancel_fd_sync(conn_a, conn_b); + ASSERT_RETURN(ret == 0); + + ret = no_cancel_sync(conn_a, conn_b); + ASSERT_RETURN(ret == 0); + + kdbus_printf("-- closing bus connections\n"); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} + +#define BYEBYE_ME ((void*)0L) +#define BYEBYE_THEM ((void*)1L) + +static void *run_thread_byebye(void *data) +{ + struct kdbus_cmd cmd_byebye = { .size = sizeof(cmd_byebye) }; + int ret; + + ret = kdbus_msg_recv_poll(conn_a, 3000, NULL, NULL); + if (ret == 0) { + kdbus_printf("Thread received message, invoking BYEBYE ...\n"); + kdbus_msg_recv(conn_a, NULL, NULL); + if (data == BYEBYE_ME) + kdbus_cmd_byebye(conn_b->fd, &cmd_byebye); + else if (data == BYEBYE_THEM) + kdbus_cmd_byebye(conn_a->fd, &cmd_byebye); + } + + pthread_exit(NULL); + return NULL; +} + +int kdbus_test_sync_byebye(struct kdbus_test_env *env) +{ + pthread_t thread; + int ret; + + /* + * This sends a synchronous message to a thread, which waits until it + * received the message and then invokes BYEBYE on the *ORIGINAL* + * connection. That is, on the same connection that synchronously waits + * for an reply. + * This should properly wake the connection up and cause ECONNRESET as + * the connection is disconnected now. + * + * The second time, we do the same but invoke BYEBYE on the *TARGET* + * connection. This should also wake up the synchronous sender as the + * reply cannot be sent by a disconnected target. + */ + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + pthread_create(&thread, NULL, run_thread_byebye, BYEBYE_ME); + + ret = kdbus_msg_send_sync(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_a->id, -1); + + ASSERT_RETURN(ret == -ECONNRESET); + + pthread_join(thread, NULL); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + pthread_create(&thread, NULL, run_thread_byebye, BYEBYE_THEM); + + ret = kdbus_msg_send_sync(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_a->id, -1); + + ASSERT_RETURN(ret == -EPIPE); + + pthread_join(thread, NULL); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-timeout.c b/tools/testing/selftests/kdbus/test-timeout.c new file mode 100644 index 000000000..cfd193066 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-timeout.c @@ -0,0 +1,99 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int timeout_msg_recv(struct kdbus_conn *conn, uint64_t *expected) +{ + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *msg; + int ret; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret < 0) { + kdbus_printf("error receiving message: %d (%m)\n", ret); + return ret; + } + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + + ASSERT_RETURN_VAL(msg->payload_type == KDBUS_PAYLOAD_KERNEL, -EINVAL); + ASSERT_RETURN_VAL(msg->src_id == KDBUS_SRC_ID_KERNEL, -EINVAL); + ASSERT_RETURN_VAL(msg->dst_id == conn->id, -EINVAL); + + *expected &= ~(1ULL << msg->cookie_reply); + kdbus_printf("Got message timeout for cookie %llu\n", + msg->cookie_reply); + + ret = kdbus_free(conn, recv.msg.offset); + if (ret < 0) + return ret; + + return 0; +} + +int kdbus_test_timeout(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_a, *conn_b; + struct pollfd fd; + int ret, i, n_msgs = 4; + uint64_t expected = 0; + uint64_t cookie = 0xdeadbeef; + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + fd.fd = conn_b->fd; + + /* + * send messages that expect a reply (within 100 msec), + * but never answer it. + */ + for (i = 0; i < n_msgs; i++, cookie++) { + kdbus_printf("Sending message with cookie %llu ...\n", + (unsigned long long)cookie); + ASSERT_RETURN(kdbus_msg_send(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + (i + 1) * 100ULL * 1000000ULL, 0, + conn_a->id) == 0); + expected |= 1ULL << cookie; + } + + for (;;) { + fd.events = POLLIN | POLLPRI | POLLHUP; + fd.revents = 0; + + ret = poll(&fd, 1, (n_msgs + 1) * 100); + if (ret == 0) + kdbus_printf("--- timeout\n"); + if (ret <= 0) + break; + + if (fd.revents & POLLIN) + ASSERT_RETURN(!timeout_msg_recv(conn_b, &expected)); + + if (expected == 0) + break; + } + + ASSERT_RETURN(expected == 0); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} |