diff options
author | Josh Boyer <jwboyer@fedoraproject.org> | 2015-07-08 10:30:06 -0400 |
---|---|---|
committer | Josh Boyer <jwboyer@fedoraproject.org> | 2015-07-08 10:30:06 -0400 |
commit | 8be443055ee741775545f3d86cbfd36410c6ef8d (patch) | |
tree | 90c6fda25ceb55bee60a1d8a30f436eb3b9e1277 /kdbus-add-selftests.patch | |
parent | 84bb446543a6e771a1be4fc09b0712607bc6a376 (diff) | |
download | kernel-8be443055ee741775545f3d86cbfd36410c6ef8d.tar.gz kernel-8be443055ee741775545f3d86cbfd36410c6ef8d.tar.xz kernel-8be443055ee741775545f3d86cbfd36410c6ef8d.zip |
Linux v4.2-rc1-33-gd6ac4ffc61ac
Diffstat (limited to 'kdbus-add-selftests.patch')
-rw-r--r-- | kdbus-add-selftests.patch | 11448 |
1 files changed, 11448 insertions, 0 deletions
diff --git a/kdbus-add-selftests.patch b/kdbus-add-selftests.patch new file mode 100644 index 000000000..911ac4e81 --- /dev/null +++ b/kdbus-add-selftests.patch @@ -0,0 +1,11448 @@ +From: Daniel Mack <daniel@zonque.org> +Date: Sat, 13 Sep 2014 23:15:02 +0200 +Subject: [PATCH] kdbus: add selftests + +This patch adds an extensive test suite for kdbus that checks the most +important code paths in the driver. The idea is to extend the test +suite over time. + +Also, this code can serve as another example for how to use the kernel +API from userspace. + +The code in the kdbus test suite makes use of the ioctl wrappers +provided by samples/kdbus/kdbus-api.h. + +Signed-off-by: Daniel Mack <daniel@zonque.org> +Signed-off-by: David Herrmann <dh.herrmann@gmail.com> +Signed-off-by: Djalal Harouni <tixxdz@opendz.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +--- + tools/testing/selftests/Makefile | 1 + + tools/testing/selftests/kdbus/.gitignore | 3 + + tools/testing/selftests/kdbus/Makefile | 46 + + tools/testing/selftests/kdbus/kdbus-enum.c | 94 ++ + tools/testing/selftests/kdbus/kdbus-enum.h | 14 + + tools/testing/selftests/kdbus/kdbus-test.c | 923 ++++++++++++ + tools/testing/selftests/kdbus/kdbus-test.h | 85 ++ + tools/testing/selftests/kdbus/kdbus-util.c | 1615 +++++++++++++++++++++ + tools/testing/selftests/kdbus/kdbus-util.h | 222 +++ + tools/testing/selftests/kdbus/test-activator.c | 318 ++++ + tools/testing/selftests/kdbus/test-attach-flags.c | 750 ++++++++++ + tools/testing/selftests/kdbus/test-benchmark.c | 451 ++++++ + tools/testing/selftests/kdbus/test-bus.c | 175 +++ + tools/testing/selftests/kdbus/test-chat.c | 122 ++ + tools/testing/selftests/kdbus/test-connection.c | 616 ++++++++ + tools/testing/selftests/kdbus/test-daemon.c | 65 + + tools/testing/selftests/kdbus/test-endpoint.c | 341 +++++ + tools/testing/selftests/kdbus/test-fd.c | 789 ++++++++++ + tools/testing/selftests/kdbus/test-free.c | 64 + + tools/testing/selftests/kdbus/test-match.c | 441 ++++++ + tools/testing/selftests/kdbus/test-message.c | 731 ++++++++++ + tools/testing/selftests/kdbus/test-metadata-ns.c | 506 +++++++ + tools/testing/selftests/kdbus/test-monitor.c | 176 +++ + tools/testing/selftests/kdbus/test-names.c | 194 +++ + tools/testing/selftests/kdbus/test-policy-ns.c | 632 ++++++++ + tools/testing/selftests/kdbus/test-policy-priv.c | 1269 ++++++++++++++++ + tools/testing/selftests/kdbus/test-policy.c | 80 + + tools/testing/selftests/kdbus/test-sync.c | 369 +++++ + tools/testing/selftests/kdbus/test-timeout.c | 99 ++ + 29 files changed, 11191 insertions(+) + create mode 100644 tools/testing/selftests/kdbus/.gitignore + create mode 100644 tools/testing/selftests/kdbus/Makefile + create mode 100644 tools/testing/selftests/kdbus/kdbus-enum.c + create mode 100644 tools/testing/selftests/kdbus/kdbus-enum.h + create mode 100644 tools/testing/selftests/kdbus/kdbus-test.c + create mode 100644 tools/testing/selftests/kdbus/kdbus-test.h + create mode 100644 tools/testing/selftests/kdbus/kdbus-util.c + create mode 100644 tools/testing/selftests/kdbus/kdbus-util.h + create mode 100644 tools/testing/selftests/kdbus/test-activator.c + create mode 100644 tools/testing/selftests/kdbus/test-attach-flags.c + create mode 100644 tools/testing/selftests/kdbus/test-benchmark.c + create mode 100644 tools/testing/selftests/kdbus/test-bus.c + create mode 100644 tools/testing/selftests/kdbus/test-chat.c + create mode 100644 tools/testing/selftests/kdbus/test-connection.c + create mode 100644 tools/testing/selftests/kdbus/test-daemon.c + create mode 100644 tools/testing/selftests/kdbus/test-endpoint.c + create mode 100644 tools/testing/selftests/kdbus/test-fd.c + create mode 100644 tools/testing/selftests/kdbus/test-free.c + create mode 100644 tools/testing/selftests/kdbus/test-match.c + create mode 100644 tools/testing/selftests/kdbus/test-message.c + create mode 100644 tools/testing/selftests/kdbus/test-metadata-ns.c + create mode 100644 tools/testing/selftests/kdbus/test-monitor.c + create mode 100644 tools/testing/selftests/kdbus/test-names.c + create mode 100644 tools/testing/selftests/kdbus/test-policy-ns.c + create mode 100644 tools/testing/selftests/kdbus/test-policy-priv.c + create mode 100644 tools/testing/selftests/kdbus/test-policy.c + create mode 100644 tools/testing/selftests/kdbus/test-sync.c + create mode 100644 tools/testing/selftests/kdbus/test-timeout.c + +diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile +index 24ae9e829e9a..3af31afa0d13 100644 +--- a/tools/testing/selftests/Makefile ++++ b/tools/testing/selftests/Makefile +@@ -6,6 +6,7 @@ TARGETS += firmware + TARGETS += ftrace + TARGETS += futex + TARGETS += kcmp ++TARGETS += kdbus + TARGETS += memfd + TARGETS += memory-hotplug + TARGETS += mount +diff --git a/tools/testing/selftests/kdbus/.gitignore b/tools/testing/selftests/kdbus/.gitignore +new file mode 100644 +index 000000000000..7b421f76c888 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/.gitignore +@@ -0,0 +1,3 @@ ++*.7 ++manpage.* ++*.proc +diff --git a/tools/testing/selftests/kdbus/Makefile b/tools/testing/selftests/kdbus/Makefile +new file mode 100644 +index 000000000000..f6cfab26f315 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/Makefile +@@ -0,0 +1,46 @@ ++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-attach-flags.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 ++ ++%.o: %.c ++ gcc $(CFLAGS) -c $< -o $@ ++ ++kdbus-test: $(OBJS) ++ gcc $(CFLAGS) $^ $(LDLIBS) -o $@ ++ ++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 000000000000..4f1e5797895f +--- /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 000000000000..a67cec3512a7 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/kdbus-enum.h +@@ -0,0 +1,14 @@ ++/* ++ * 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 000000000000..a43674ccdeb0 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/kdbus-test.c +@@ -0,0 +1,923 @@ ++#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; ++ char *mask_param_path; ++}; ++ ++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, ++ }, ++ { ++ /* Last test */ ++ .name = "attach-flags", ++ .desc = "attach flags mask", ++ .func = kdbus_test_attach_flags, ++ .flags = 0, ++ }, ++}; ++ ++#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, ++ _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; ++ env->mask_param_path = args->mask_param_path; ++ ++ 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; ++ uint64_t kdbus_param_mask; ++ static char fspath[4096], parampath[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; ++ } ++ ++ snprintf(parampath, sizeof(parampath), ++ "/sys/module/%s/parameters/attach_flags_mask", ++ kdbus_args->module); ++ kdbus_args->mask_param_path = parampath; ++ ++ ret = kdbus_sysfs_get_parameter_mask(kdbus_args->mask_param_path, ++ &kdbus_param_mask); ++ if (ret < 0) ++ return TEST_ERR; ++ ++ printf("# Starting tests with an attach_flags_mask=0x%llx\n", ++ (unsigned long long)kdbus_param_mask); ++ ++ /* 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 000000000000..647331883763 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/kdbus-test.h +@@ -0,0 +1,85 @@ ++#ifndef _TEST_KDBUS_H_ ++#define _TEST_KDBUS_H_ ++ ++struct kdbus_test_env { ++ char *buspath; ++ const char *root; ++ const char *module; ++ const char *mask_param_path; ++ 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_attach_flags(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 000000000000..4b376ecfdbed +--- /dev/null ++++ b/tools/testing/selftests/kdbus/kdbus-util.c +@@ -0,0 +1,1615 @@ ++/* ++ * 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 req_meta, 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; ++ ++ /* required and owner metadata items */ ++ struct { ++ uint64_t size; ++ uint64_t type; ++ uint64_t flags; ++ } attach[2]; ++ ++ /* 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[0].type = KDBUS_ITEM_ATTACH_FLAGS_RECV; ++ bus_make.attach[0].size = sizeof(bus_make.attach[0]); ++ bus_make.attach[0].flags = req_meta; ++ ++ bus_make.attach[1].type = KDBUS_ITEM_ATTACH_FLAGS_SEND; ++ bus_make.attach[1].size = sizeof(bus_make.attach[0]); ++ bus_make.attach[1].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[0].size + ++ bus_make.attach[1].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; ++ ++ ret = syscall(__NR_memfd_create, name, MFD_ALLOW_SEALING); ++ if (ret < 0) ++ return ret; ++ ++ fd = ret; ++ ++ 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; ++ struct kdbus_msg *msg; ++ 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); ++ size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); ++ size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); ++ size += 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"); ++ return ret; ++ } ++ ++ ret = sys_memfd_seal_set(memfd); ++ if (ret < 0) { ++ ret = -errno; ++ kdbus_printf("memfd sealing failed: %m\n"); ++ return ret; ++ } ++ ++ 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"); ++ return ret; ++ } ++ ++ 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) ++ return ret; ++ ++ 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); ++ 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 (memfd >= 0) ++ close(memfd); ++ ++ if (ret < 0) { ++ kdbus_printf("error sending message: %d (%m)\n", ret); ++ return ret; ++ } ++ ++ 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) ++ return ret; ++ } ++ ++ free(msg); ++ free(cmd); ++ ++ return 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() ++{ ++ 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 (!cap) { ++ 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 000000000000..50ff07140bdd +--- /dev/null ++++ b/tools/testing/selftests/kdbus/kdbus-util.h +@@ -0,0 +1,222 @@ ++/* ++ * 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 req_meta, 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(); ++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 000000000000..3d1b76370ce8 +--- /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-attach-flags.c b/tools/testing/selftests/kdbus/test-attach-flags.c +new file mode 100644 +index 000000000000..deee7c332f25 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/test-attach-flags.c +@@ -0,0 +1,750 @@ ++#include <stdio.h> ++#include <string.h> ++#include <stdlib.h> ++#include <stdbool.h> ++#include <stddef.h> ++#include <fcntl.h> ++#include <unistd.h> ++#include <stdint.h> ++#include <errno.h> ++#include <assert.h> ++#include <sys/capability.h> ++#include <sys/mman.h> ++#include <sys/stat.h> ++#include <sys/types.h> ++#include <linux/unistd.h> ++ ++#include "kdbus-api.h" ++#include "kdbus-test.h" ++#include "kdbus-util.h" ++#include "kdbus-enum.h" ++ ++/* ++ * Should be the sum of the currently supported and compiled-in ++ * KDBUS_ITEMS_* that reflect KDBUS_ATTACH_* flags. ++ */ ++static unsigned int KDBUS_TEST_ITEMS_SUM = KDBUS_ATTACH_ITEMS_TYPE_SUM; ++ ++static struct kdbus_conn *__kdbus_hello(const char *path, uint64_t flags, ++ uint64_t attach_flags_send, ++ uint64_t attach_flags_recv) ++{ ++ struct kdbus_cmd_free cmd_free = {}; ++ int ret, fd; ++ struct kdbus_conn *conn; ++ struct { ++ struct kdbus_cmd_hello hello; ++ ++ struct { ++ uint64_t size; ++ uint64_t type; ++ char str[16]; ++ } conn_name; ++ ++ uint8_t extra_items[0]; ++ } h; ++ ++ memset(&h, 0, sizeof(h)); ++ ++ 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 = attach_flags_send; ++ h.hello.attach_flags_recv = attach_flags_recv; ++ 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("-- New connection ID : %llu\n", ++ (unsigned long long)h.hello.id); ++ ++ cmd_free.size = sizeof(cmd_free); ++ cmd_free.offset = h.hello.offset; ++ ret = kdbus_cmd_free(fd, &cmd_free); ++ if (ret < 0) ++ return NULL; ++ ++ 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) { ++ ret = -errno; ++ free(conn); ++ close(fd); ++ kdbus_printf("--- error mmap: %d (%m)\n", ret); ++ return NULL; ++ } ++ ++ conn->fd = fd; ++ conn->id = h.hello.id; ++ return conn; ++} ++ ++static int kdbus_test_peers_creation(struct kdbus_test_env *env) ++{ ++ int ret; ++ int control_fd; ++ char *path; ++ char *busname; ++ char buspath[2048]; ++ char control_path[2048]; ++ uint64_t attach_flags_mask; ++ struct kdbus_conn *conn; ++ ++ snprintf(control_path, sizeof(control_path), ++ "%s/control", env->root); ++ ++ /* ++ * Set kdbus system-wide mask to 0, this has nothing ++ * to do with the following tests, bus and connection ++ * creation nor connection update, but we do it so we are ++ * sure that everything work as expected ++ */ ++ ++ attach_flags_mask = 0; ++ ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ attach_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ ++ /* ++ * Create bus with a full set of ATTACH flags ++ */ ++ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peers-creation-bus"); ++ ASSERT_RETURN(busname); ++ ++ ret = kdbus_create_bus(control_fd, busname, _KDBUS_ATTACH_ALL, ++ 0, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ /* ++ * Create a connection with an empty send attach flags, or ++ * with just KDBUS_ATTACH_CREDS, this should fail ++ */ ++ conn = __kdbus_hello(buspath, 0, 0, 0); ++ ASSERT_RETURN(conn == NULL); ++ ASSERT_RETURN(errno == ECONNREFUSED); ++ ++ conn = __kdbus_hello(buspath, 0, KDBUS_ATTACH_CREDS, ++ _KDBUS_ATTACH_ALL); ++ ASSERT_RETURN(conn == NULL); ++ ASSERT_RETURN(errno == ECONNREFUSED); ++ ++ conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); ++ ASSERT_RETURN(conn); ++ ++ /* Try to cut back some send attach flags */ ++ ret = kdbus_conn_update_attach_flags(conn, ++ KDBUS_ATTACH_CREDS| ++ KDBUS_ATTACH_PIDS, ++ _KDBUS_ATTACH_ALL); ++ ASSERT_RETURN(ret == -EINVAL); ++ ++ ret = kdbus_conn_update_attach_flags(conn, ++ _KDBUS_ATTACH_ALL, 0); ++ ASSERT_RETURN(ret == 0); ++ ++ kdbus_conn_free(conn); ++ free(path); ++ free(busname); ++ close(control_fd); ++ ++ ++ /* Test a new bus with KDBUS_ATTACH_PIDS */ ++ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peer-flags-bus"); ++ ASSERT_RETURN(busname); ++ ++ ret = kdbus_create_bus(control_fd, busname, KDBUS_ATTACH_PIDS, ++ 0, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ /* ++ * Create a connection with an empty send attach flags, or ++ * all flags except KDBUS_ATTACH_PIDS ++ */ ++ conn = __kdbus_hello(buspath, 0, 0, 0); ++ ASSERT_RETURN(conn == NULL); ++ ASSERT_RETURN(errno == ECONNREFUSED); ++ ++ conn = __kdbus_hello(buspath, 0, ++ _KDBUS_ATTACH_ALL & ~KDBUS_ATTACH_PIDS, ++ _KDBUS_ATTACH_ALL); ++ ASSERT_RETURN(conn == NULL); ++ ASSERT_RETURN(errno == ECONNREFUSED); ++ ++ /* The following should succeed */ ++ conn = __kdbus_hello(buspath, 0, KDBUS_ATTACH_PIDS, 0); ++ ASSERT_RETURN(conn); ++ kdbus_conn_free(conn); ++ ++ conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); ++ ASSERT_RETURN(conn); ++ ++ ret = kdbus_conn_update_attach_flags(conn, ++ _KDBUS_ATTACH_ALL & ++ ~KDBUS_ATTACH_PIDS, ++ _KDBUS_ATTACH_ALL); ++ ASSERT_RETURN(ret == -EINVAL); ++ ++ ret = kdbus_conn_update_attach_flags(conn, 0, ++ _KDBUS_ATTACH_ALL); ++ ASSERT_RETURN(ret == -EINVAL); ++ ++ /* Now we want only KDBUS_ATTACH_PIDS */ ++ ret = kdbus_conn_update_attach_flags(conn, ++ KDBUS_ATTACH_PIDS, 0); ++ ASSERT_RETURN(ret == 0); ++ ++ kdbus_conn_free(conn); ++ free(path); ++ free(busname); ++ close(control_fd); ++ ++ ++ /* ++ * Create bus with 0 as ATTACH flags, the bus does not ++ * require any attach flags ++ */ ++ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peer-flags-bus"); ++ ASSERT_RETURN(busname); ++ ++ ret = kdbus_create_bus(control_fd, busname, 0, 0, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ /* Bus is open it does not require any send attach flags */ ++ conn = __kdbus_hello(buspath, 0, 0, 0); ++ ASSERT_RETURN(conn); ++ kdbus_conn_free(conn); ++ ++ conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); ++ ASSERT_RETURN(conn); ++ ++ ret = kdbus_conn_update_attach_flags(conn, 0, 0); ++ ASSERT_RETURN(ret == 0); ++ ++ ret = kdbus_conn_update_attach_flags(conn, KDBUS_ATTACH_CREDS, 0); ++ ASSERT_RETURN(ret == 0); ++ ++ kdbus_conn_free(conn); ++ free(path); ++ free(busname); ++ close(control_fd); ++ ++ return 0; ++} ++ ++static int kdbus_test_peers_info(struct kdbus_test_env *env) ++{ ++ int ret; ++ int control_fd; ++ char *path; ++ char *busname; ++ unsigned int i = 0; ++ uint64_t offset = 0; ++ char buspath[2048]; ++ char control_path[2048]; ++ uint64_t attach_flags_mask; ++ struct kdbus_item *item; ++ struct kdbus_info *info; ++ struct kdbus_conn *conn; ++ struct kdbus_conn *reader; ++ unsigned long long attach_count = 0; ++ ++ snprintf(control_path, sizeof(control_path), ++ "%s/control", env->root); ++ ++ attach_flags_mask = 0; ++ ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ attach_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peers-info-bus"); ++ ASSERT_RETURN(busname); ++ ++ ret = kdbus_create_bus(control_fd, busname, _KDBUS_ATTACH_ALL, ++ 0, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ /* Create connections with the appropriate flags */ ++ conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); ++ ASSERT_RETURN(conn); ++ ++ reader = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); ++ ASSERT_RETURN(reader); ++ ++ ret = kdbus_conn_info(reader, conn->id, NULL, ++ _KDBUS_ATTACH_ALL, &offset); ++ ASSERT_RETURN(ret == 0); ++ ++ info = (struct kdbus_info *)(reader->buf + offset); ++ ASSERT_RETURN(info->id == conn->id); ++ ++ /* all attach flags are masked, no metadata */ ++ KDBUS_ITEM_FOREACH(item, info, items) ++ i++; ++ ++ ASSERT_RETURN(i == 0); ++ ++ kdbus_free(reader, offset); ++ ++ /* Set the mask to _KDBUS_ATTACH_ANY */ ++ attach_flags_mask = _KDBUS_ATTACH_ANY; ++ ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ attach_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ ret = kdbus_conn_info(reader, conn->id, NULL, ++ _KDBUS_ATTACH_ALL, &offset); ++ ASSERT_RETURN(ret == 0); ++ ++ info = (struct kdbus_info *)(reader->buf + offset); ++ ASSERT_RETURN(info->id == conn->id); ++ ++ attach_count = 0; ++ KDBUS_ITEM_FOREACH(item, info, items) ++ attach_count += item->type; ++ ++ /* ++ * All flags have been returned except for: ++ * KDBUS_ITEM_TIMESTAMP and ++ * KDBUS_ITEM_OWNED_NAME we do not own any name. ++ */ ++ ASSERT_RETURN(attach_count == (KDBUS_TEST_ITEMS_SUM - ++ KDBUS_ITEM_OWNED_NAME - ++ KDBUS_ITEM_TIMESTAMP)); ++ ++ kdbus_free(reader, offset); ++ ++ /* Request only OWNED names */ ++ ret = kdbus_conn_info(reader, conn->id, NULL, ++ KDBUS_ATTACH_NAMES, &offset); ++ ASSERT_RETURN(ret == 0); ++ ++ info = (struct kdbus_info *)(reader->buf + offset); ++ ASSERT_RETURN(info->id == conn->id); ++ ++ attach_count = 0; ++ KDBUS_ITEM_FOREACH(item, info, items) ++ attach_count += item->type; ++ ++ /* we should not get any metadata since we do not own names */ ++ ASSERT_RETURN(attach_count == 0); ++ ++ kdbus_free(reader, offset); ++ ++ kdbus_conn_free(conn); ++ kdbus_conn_free(reader); ++ ++ return 0; ++} ++ ++/** ++ * @kdbus_mask_param: kdbus module mask parameter (system-wide) ++ * @requested_meta: The bus owner metadata that we want ++ * @expected_items: The returned KDBUS_ITEMS_* sum. Used to ++ * validate the returned metadata items ++ */ ++static int kdbus_cmp_bus_creator_metadata(struct kdbus_test_env *env, ++ struct kdbus_conn *conn, ++ uint64_t kdbus_mask_param, ++ uint64_t requested_meta, ++ unsigned long expected_items) ++{ ++ int ret; ++ uint64_t offset = 0; ++ struct kdbus_info *info; ++ struct kdbus_item *item; ++ unsigned long attach_count = 0; ++ ++ ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ kdbus_mask_param); ++ ASSERT_RETURN(ret == 0); ++ ++ ret = kdbus_bus_creator_info(conn, requested_meta, &offset); ++ ASSERT_RETURN(ret == 0); ++ ++ info = (struct kdbus_info *)(conn->buf + offset); ++ ++ KDBUS_ITEM_FOREACH(item, info, items) ++ attach_count += item->type; ++ ++ ASSERT_RETURN(attach_count == expected_items); ++ ++ ret = kdbus_free(conn, offset); ++ ASSERT_RETURN(ret == 0); ++ ++ return 0; ++} ++ ++static int kdbus_test_bus_creator_info(struct kdbus_test_env *env) ++{ ++ int ret; ++ int control_fd; ++ char *path; ++ char *busname; ++ char buspath[2048]; ++ char control_path[2048]; ++ uint64_t attach_flags_mask; ++ struct kdbus_conn *conn; ++ unsigned long expected_items = 0; ++ ++ snprintf(control_path, sizeof(control_path), ++ "%s/control", env->root); ++ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peers-info-bus"); ++ ASSERT_RETURN(busname); ++ ++ /* ++ * Now the bus allows us to see all its KDBUS_ATTACH_* ++ * items ++ */ ++ ret = kdbus_create_bus(control_fd, busname, 0, ++ _KDBUS_ATTACH_ALL, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ conn = __kdbus_hello(buspath, 0, 0, 0); ++ ASSERT_RETURN(conn); ++ ++ /* ++ * Start with a kdbus module mask set to _KDBUS_ATTACH_ANY ++ */ ++ attach_flags_mask = _KDBUS_ATTACH_ANY; ++ ++ /* ++ * All flags will be returned except for: ++ * KDBUS_ITEM_TIMESTAMP ++ * KDBUS_ITEM_OWNED_NAME ++ * KDBUS_ITEM_CONN_DESCRIPTION ++ * ++ * An extra flags is always returned KDBUS_ITEM_MAKE_NAME ++ * which contains the bus name ++ */ ++ expected_items = KDBUS_TEST_ITEMS_SUM + KDBUS_ITEM_MAKE_NAME; ++ expected_items -= KDBUS_ITEM_TIMESTAMP + ++ KDBUS_ITEM_OWNED_NAME + ++ KDBUS_ITEM_CONN_DESCRIPTION; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* ++ * We should have: ++ * KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + KDBUS_ITEM_MAKE_NAME ++ */ ++ expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + ++ KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ KDBUS_ATTACH_PIDS | ++ KDBUS_ATTACH_CREDS, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* KDBUS_ITEM_MAKE_NAME is always returned */ ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ 0, expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* ++ * Restrict kdbus system-wide mask to KDBUS_ATTACH_PIDS ++ */ ++ ++ attach_flags_mask = KDBUS_ATTACH_PIDS; ++ ++ /* ++ * We should have: ++ * KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME ++ */ ++ expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ ++ /* system-wide mask to 0 */ ++ attach_flags_mask = 0; ++ ++ /* we should only see: KDBUS_ITEM_MAKE_NAME */ ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ kdbus_conn_free(conn); ++ free(path); ++ free(busname); ++ close(control_fd); ++ ++ ++ /* ++ * A new bus that hides all its owner metadata ++ */ ++ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peers-info-bus"); ++ ASSERT_RETURN(busname); ++ ++ ret = kdbus_create_bus(control_fd, busname, 0, 0, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ conn = __kdbus_hello(buspath, 0, 0, 0); ++ ASSERT_RETURN(conn); ++ ++ /* ++ * Start with a kdbus module mask set to _KDBUS_ATTACH_ANY ++ */ ++ attach_flags_mask = _KDBUS_ATTACH_ANY; ++ ++ /* ++ * We only get the KDBUS_ITEM_MAKE_NAME ++ */ ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* ++ * We still get only kdbus_ITEM_MAKE_NAME ++ */ ++ attach_flags_mask = 0; ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ kdbus_conn_free(conn); ++ free(path); ++ free(busname); ++ close(control_fd); ++ ++ ++ /* ++ * A new bus that shows only the PID and CREDS metadata ++ * of the bus owner. ++ */ ++ control_fd = open(control_path, O_RDWR); ++ ASSERT_RETURN(control_fd >= 0); ++ ++ busname = unique_name("test-peers-info-bus"); ++ ASSERT_RETURN(busname); ++ ++ ret = kdbus_create_bus(control_fd, busname, 0, ++ KDBUS_ATTACH_PIDS| ++ KDBUS_ATTACH_CREDS, &path); ++ ASSERT_RETURN(ret == 0); ++ ++ snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); ++ ++ conn = __kdbus_hello(buspath, 0, 0, 0); ++ ASSERT_RETURN(conn); ++ ++ /* ++ * Start with a kdbus module mask set to _KDBUS_ATTACH_ANY ++ */ ++ attach_flags_mask = _KDBUS_ATTACH_ANY; ++ ++ /* ++ * We should have: ++ * KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + KDBUS_ITEM_MAKE_NAME ++ */ ++ expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + ++ KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ expected_items = KDBUS_ITEM_CREDS + KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ KDBUS_ATTACH_CREDS, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* KDBUS_ITEM_MAKE_NAME is always returned */ ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ 0, expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* ++ * Restrict kdbus system-wide mask to KDBUS_ATTACH_PIDS ++ */ ++ ++ attach_flags_mask = KDBUS_ATTACH_PIDS; ++ /* ++ * We should have: ++ * KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME ++ */ ++ expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* No KDBUS_ATTACH_CREDS */ ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ KDBUS_ATTACH_CREDS, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ /* system-wide mask to 0 */ ++ attach_flags_mask = 0; ++ ++ /* we should only see: KDBUS_ITEM_MAKE_NAME */ ++ expected_items = KDBUS_ITEM_MAKE_NAME; ++ ret = kdbus_cmp_bus_creator_metadata(env, conn, ++ attach_flags_mask, ++ _KDBUS_ATTACH_ALL, ++ expected_items); ++ ASSERT_RETURN(ret == 0); ++ ++ ++ kdbus_conn_free(conn); ++ free(path); ++ free(busname); ++ close(control_fd); ++ ++ return 0; ++} ++ ++int kdbus_test_attach_flags(struct kdbus_test_env *env) ++{ ++ int ret; ++ uint64_t flags_mask; ++ uint64_t old_kdbus_flags_mask; ++ ++ /* We need CAP_DAC_OVERRIDE to overwrite the kdbus mask */ ++ ret = test_is_capable(CAP_DAC_OVERRIDE, -1); ++ ASSERT_RETURN(ret >= 0); ++ ++ /* no enough privileges, SKIP test */ ++ if (!ret) ++ return TEST_SKIP; ++ ++ /* ++ * We need to be able to write to ++ * "/sys/module/kdbus/parameters/attach_flags_mask" ++ * perhaps we are unprvileged/privileged in its userns ++ */ ++ ret = access(env->mask_param_path, W_OK); ++ if (ret < 0) { ++ kdbus_printf("--- access() '%s' failed: %d (%m)\n", ++ env->mask_param_path, -errno); ++ return TEST_SKIP; ++ } ++ ++ ret = kdbus_sysfs_get_parameter_mask(env->mask_param_path, ++ &old_kdbus_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ /* setup the right KDBUS_TEST_ITEMS_SUM */ ++ if (!config_auditsyscall_is_enabled()) ++ KDBUS_TEST_ITEMS_SUM -= KDBUS_ITEM_AUDIT; ++ ++ if (!config_cgroups_is_enabled()) ++ KDBUS_TEST_ITEMS_SUM -= KDBUS_ITEM_CGROUP; ++ ++ if (!config_security_is_enabled()) ++ KDBUS_TEST_ITEMS_SUM -= KDBUS_ITEM_SECLABEL; ++ ++ /* ++ * Test the connection creation attach flags ++ */ ++ ret = kdbus_test_peers_creation(env); ++ /* Restore previous kdbus mask */ ++ kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ old_kdbus_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ /* ++ * Test the CONN_INFO attach flags ++ */ ++ ret = kdbus_test_peers_info(env); ++ /* Restore previous kdbus mask */ ++ kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ old_kdbus_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ /* ++ * Test the Bus creator info and its attach flags ++ */ ++ ret = kdbus_test_bus_creator_info(env); ++ /* Restore previous kdbus mask */ ++ kdbus_sysfs_set_parameter_mask(env->mask_param_path, ++ old_kdbus_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ ret = kdbus_sysfs_get_parameter_mask(env->mask_param_path, ++ &flags_mask); ++ ASSERT_RETURN(ret == 0 && old_kdbus_flags_mask == flags_mask); ++ ++ 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 000000000000..8a9744b00508 +--- /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 000000000000..762fb30397d4 +--- /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 000000000000..71a92d8b7c85 +--- /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 000000000000..5c2bf3511daa +--- /dev/null ++++ b/tools/testing/selftests/kdbus/test-connection.c +@@ -0,0 +1,616 @@ ++#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; ++ ++ /* ++ * The connection created by the core requires ALL meta flags ++ * to be sent. An attempt to send less than that should result in ++ * -ECONNREFUSED. ++ */ ++ hello.attach_flags_send = _KDBUS_ATTACH_ALL & ~KDBUS_ATTACH_TIMESTAMP; ++ ret = kdbus_cmd_hello(fd, &hello); ++ ASSERT_RETURN(ret == -ECONNREFUSED); ++ ++ 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; ++ uint64_t kdbus_flags_mask; ++ struct kdbus_info *info; ++ struct kdbus_conn *conn; ++ struct kdbus_conn *privileged; ++ const struct kdbus_item *item; ++ uint64_t valid_flags_set; ++ uint64_t invalid_flags_set; ++ 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_sysfs_get_parameter_mask(env->mask_param_path, ++ &kdbus_flags_mask); ++ ASSERT_RETURN(ret == 0); ++ ++ valid_flags_set = valid_flags & kdbus_flags_mask; ++ invalid_flags_set = invalid_flags & kdbus_flags_mask; ++ ++ 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_set & 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_set & 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_set & 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_set & 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_set & 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_set & 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_set & 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_set & 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_set & 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_set & KDBUS_ATTACH_CGROUP) { ++ ASSERT_EXIT(cnt == 1); ++ } else { ++ ASSERT_EXIT(cnt == 0); ++ } ++ ++ cnt = kdbus_count_item(info, KDBUS_ITEM_CAPS); ++ if (invalid_flags_set & 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_set & 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 000000000000..8bc2386190d9 +--- /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 000000000000..dcc6ab91c4e6 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/test-endpoint.c +@@ -0,0 +1,341 @@ ++#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); ++ ++ /* ++ * 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 anything */ ++ 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); ++ ++ 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 000000000000..2ae0f5ae8fd0 +--- /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 000000000000..f666da3e87cc +--- /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 000000000000..2360dc1d76a4 +--- /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 000000000000..f1615dafb7f1 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/test-message.c +@@ -0,0 +1,731 @@ ++#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 not get our own broadcasts */ ++ ret = kdbus_msg_recv(sender, NULL, NULL); ++ ASSERT_RETURN(ret == -EAGAIN); ++ ++ /* ... 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 000000000000..2cb1d4d2a5be +--- /dev/null ++++ b/tools/testing/selftests/kdbus/test-metadata-ns.c +@@ -0,0 +1,506 @@ ++/* ++ * 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); ++ ++ /* Different namespaces no CAPS */ ++ item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); ++ ASSERT_EXIT(item == NULL); ++ ++ /* 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); ++ ++ /* Different namespaces no CAPS */ ++ item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); ++ ASSERT_EXIT(item == NULL); ++ ++ /* 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; ++ int status; ++ int efd = -1; ++ pid_t pid, ppid; ++ uint64_t unpriv_conn_id = 0; ++ uint64_t userns_conn_id = 0; ++ struct kdbus_msg *msg; ++ const struct kdbus_item *item; ++ struct kdbus_pids expected_pids; ++ struct kdbus_conn *monitor = NULL; ++ ++ 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; ++ ++ /* We do not share the userns, os no KDBUS_ITEM_CAPS */ ++ item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); ++ ASSERT_RETURN(item == NULL); ++ ++ /* ++ * 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 000000000000..e00d738a3986 +--- /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 000000000000..66ebb47370eb +--- /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 000000000000..3437012f90af +--- /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 000000000000..a318cccad0d5 +--- /dev/null ++++ b/tools/testing/selftests/kdbus/test-policy-priv.c +@@ -0,0 +1,1269 @@ ++#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); ++ ++ /* 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); ++ ++ /* 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. ++ */ ++ ++ ret = test_policy_priv_by_broadcast(env->buspath, owner_a, ++ DO_NOT_DROP, ++ -ETIMEDOUT, -ETIMEDOUT); ++ ASSERT_RETURN(ret == 0); ++ ++ /* 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_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 000000000000..96d20d5e9172 +--- /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 000000000000..e2be910d2ece +--- /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 000000000000..cfd193066a64 +--- /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; ++} |