Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F171278299
D59373.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
42 KB
Referenced Files
None
Subscribers
None
D59373.diff
View Options
diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile
--- a/tests/sys/pmc/Makefile
+++ b/tests/sys/pmc/Makefile
@@ -1,4 +1,5 @@
TESTSDIR= ${TESTSBASE}/sys/pmc
+BINDIR= ${TESTSDIR}
ATF_TESTS_C= pmc_wrap_test
ATF_TESTS_C+= pmc_detach_test
@@ -6,6 +7,9 @@
ATF_TESTS_C+= pmc_groupread_test
ATF_TESTS_C+= pmc_group_gate_test
ATF_TESTS_C+= pmc_fork_test
+ATF_TESTS_C+= pmc_exec_test
+
+PROGS+= pmc_exec_credential_helper
LIBADD+= pmc
LIBADD.pmc_detach_test+= pthread
diff --git a/tests/sys/pmc/pmc_exec_credential_common.h b/tests/sys/pmc/pmc_exec_credential_common.h
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_exec_credential_common.h
@@ -0,0 +1,64 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ */
+
+#ifndef _PMC_EXEC_CREDENTIAL_COMMON_H_
+#define _PMC_EXEC_CREDENTIAL_COMMON_H_
+
+#include <sys/types.h>
+
+#include <pmc.h>
+#include <stdint.h>
+
+#define PMC_EXEC_CREDENTIAL_RESULT_VERSION 1U
+#define PMC_EXEC_CREDENTIAL_MAX_GROUPS 8
+#define PMC_EXEC_CREDENTIAL_RELAY_MODE "--relay"
+
+enum pmc_exec_credential_stage {
+ PMC_EXEC_CREDENTIAL_STAGE_NONE,
+ PMC_EXEC_CREDENTIAL_STAGE_CREDENTIALS,
+ PMC_EXEC_CREDENTIAL_STAGE_GROUPS,
+ PMC_EXEC_CREDENTIAL_STAGE_PMC_INIT,
+ PMC_EXEC_CREDENTIAL_STAGE_READS,
+ PMC_EXEC_CREDENTIAL_STAGE_DONE
+};
+
+struct pmc_exec_credential_result {
+ uint32_t per_version;
+ uint32_t per_stage;
+ int per_stage_errno;
+ uid_t per_ruid;
+ uid_t per_euid;
+ uid_t per_suid;
+ gid_t per_rgid;
+ gid_t per_egid;
+ gid_t per_sgid;
+ int per_ngroups;
+ gid_t per_groups[PMC_EXEC_CREDENTIAL_MAX_GROUPS];
+ int per_pmc_init_rc;
+ int per_pmc_init_errno;
+ int per_leader_rc;
+ int per_leader_errno;
+ int per_sibling_rc;
+ int per_sibling_errno;
+ uint64_t per_leader_value;
+ uint64_t per_sibling_value;
+};
+
+struct pmc_exec_target_command {
+ pmc_id_t petc_leader;
+ pmc_id_t petc_sibling;
+};
+
+struct pmc_exec_target_precheck {
+ int petp_leader_rc;
+ int petp_leader_errno;
+ int petp_sibling_rc;
+ int petp_sibling_errno;
+ pmc_value_t petp_leader_value;
+ pmc_value_t petp_sibling_value;
+};
+
+#endif /* _PMC_EXEC_CREDENTIAL_COMMON_H_ */
diff --git a/tests/sys/pmc/pmc_exec_credential_helper.c b/tests/sys/pmc/pmc_exec_credential_helper.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_exec_credential_helper.c
@@ -0,0 +1,248 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <pmc.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pmc_exec_credential_common.h"
+
+static int
+write_full(int fd, const void *buffer, size_t length)
+{
+ const char *cursor;
+ ssize_t n;
+ size_t done;
+
+ cursor = buffer;
+ done = 0;
+ while (done < length) {
+ n = write(fd, cursor + done, length - done);
+ if (n == -1 && errno == EINTR)
+ continue;
+ if (n <= 0)
+ return (-1);
+ done += (size_t)n;
+ }
+ return (0);
+}
+
+static ssize_t
+read_full(int fd, void *buffer, size_t length)
+{
+ char *cursor;
+ ssize_t n;
+ size_t done;
+
+ cursor = buffer;
+ done = 0;
+ while (done < length) {
+ n = read(fd, cursor + done, length - done);
+ if (n == -1 && errno == EINTR)
+ continue;
+ if (n <= 0)
+ return (n == 0 ? (ssize_t)done : -1);
+ done += (size_t)n;
+ }
+ return ((ssize_t)done);
+}
+
+static int
+write_byte(int fd, char value)
+{
+ ssize_t n;
+
+ do {
+ n = write(fd, &value, 1);
+ } while (n == -1 && errno == EINTR);
+ return (n == 1 ? 0 : -1);
+}
+
+static int
+parse_fd(const char *text, int *fd)
+{
+ char *end;
+ long value;
+
+ errno = 0;
+ value = strtol(text, &end, 10);
+ if (errno != 0 || end == text || *end != '\0' || value < 0 ||
+ value > INT_MAX) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *fd = (int)value;
+ return (0);
+}
+
+static int
+parse_pmc_id(const char *text, pmc_id_t *pmcid)
+{
+ char *end;
+ uintmax_t value;
+
+ errno = 0;
+ value = strtoumax(text, &end, 0);
+ if (errno != 0 || end == text || *end != '\0' ||
+ (uintmax_t)(pmc_id_t)value != value) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *pmcid = (pmc_id_t)value;
+ return (0);
+}
+
+static void
+relay_failure(int result_fd, int error)
+{
+ struct pmc_exec_credential_result result;
+
+ memset(&result, 0, sizeof(result));
+ result.per_version = PMC_EXEC_CREDENTIAL_RESULT_VERSION;
+ result.per_stage = PMC_EXEC_CREDENTIAL_STAGE_NONE;
+ result.per_stage_errno = error;
+ (void)write_full(result_fd, &result, sizeof(result));
+ _exit(127);
+}
+
+static int
+run_relay(int argc, char **argv)
+{
+ struct pmc_exec_target_command command;
+ struct pmc_exec_target_precheck precheck;
+ const char *helper_path;
+ char fd_arg[32], leader_arg[32], sibling_arg[32];
+ int command_fd, error, length, precheck_fd, ready_fd, result_fd;
+
+ if (argc != 7 || parse_fd(argv[2], &ready_fd) != 0 ||
+ parse_fd(argv[3], &command_fd) != 0 ||
+ parse_fd(argv[4], &precheck_fd) != 0 ||
+ parse_fd(argv[5], &result_fd) != 0)
+ return (2);
+ helper_path = argv[6];
+
+ if (write_byte(ready_fd, 'R') != 0)
+ relay_failure(result_fd, errno);
+ close(ready_fd);
+ if (read_full(command_fd, &command, sizeof(command)) !=
+ (ssize_t)sizeof(command))
+ relay_failure(result_fd, EPIPE);
+ close(command_fd);
+
+ if (pmc_init() != 0)
+ relay_failure(result_fd, errno);
+ memset(&precheck, 0, sizeof(precheck));
+ errno = 0;
+ precheck.petp_leader_rc =
+ pmc_read(command.petc_leader, &precheck.petp_leader_value);
+ precheck.petp_leader_errno = errno;
+ errno = 0;
+ precheck.petp_sibling_rc =
+ pmc_read(command.petc_sibling, &precheck.petp_sibling_value);
+ precheck.petp_sibling_errno = errno;
+ if (write_full(precheck_fd, &precheck, sizeof(precheck)) != 0)
+ relay_failure(result_fd, errno);
+ close(precheck_fd);
+
+ length = snprintf(fd_arg, sizeof(fd_arg), "%d", result_fd);
+ if (length < 0 || length >= (int)sizeof(fd_arg))
+ relay_failure(result_fd, EINVAL);
+ length = snprintf(leader_arg, sizeof(leader_arg), "%ju",
+ (uintmax_t)command.petc_leader);
+ if (length < 0 || length >= (int)sizeof(leader_arg))
+ relay_failure(result_fd, EINVAL);
+ length = snprintf(sibling_arg, sizeof(sibling_arg), "%ju",
+ (uintmax_t)command.petc_sibling);
+ if (length < 0 || length >= (int)sizeof(sibling_arg))
+ relay_failure(result_fd, EINVAL);
+ execl(helper_path, "pmc_exec_credential_helper", fd_arg, leader_arg,
+ sibling_arg, NULL);
+ error = errno;
+ relay_failure(result_fd, error);
+ return (127);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct pmc_exec_credential_result result;
+ pmc_value_t value;
+ pmc_id_t leader, sibling;
+ int result_fd;
+
+ if (argc > 1 && strcmp(argv[1], PMC_EXEC_CREDENTIAL_RELAY_MODE) == 0)
+ return (run_relay(argc, argv));
+ if (argc != 4 || parse_fd(argv[1], &result_fd) != 0 ||
+ parse_pmc_id(argv[2], &leader) != 0 ||
+ parse_pmc_id(argv[3], &sibling) != 0)
+ return (2);
+
+ result = (struct pmc_exec_credential_result){
+ .per_version = PMC_EXEC_CREDENTIAL_RESULT_VERSION,
+ .per_stage = PMC_EXEC_CREDENTIAL_STAGE_CREDENTIALS,
+ .per_pmc_init_rc = -2,
+ .per_leader_rc = -2,
+ .per_sibling_rc = -2,
+ };
+ if (getresuid(&result.per_ruid, &result.per_euid,
+ &result.per_suid) != 0 ||
+ getresgid(&result.per_rgid, &result.per_egid,
+ &result.per_sgid) != 0) {
+ result.per_stage_errno = errno;
+ goto out;
+ }
+
+ result.per_stage = PMC_EXEC_CREDENTIAL_STAGE_GROUPS;
+ result.per_ngroups = getgroups(0, NULL);
+ if (result.per_ngroups < 0) {
+ result.per_stage_errno = errno;
+ goto out;
+ }
+ if (result.per_ngroups > PMC_EXEC_CREDENTIAL_MAX_GROUPS) {
+ result.per_stage_errno = EOVERFLOW;
+ goto out;
+ }
+ if (result.per_ngroups != 0 &&
+ getgroups(result.per_ngroups, result.per_groups) == -1) {
+ result.per_stage_errno = errno;
+ goto out;
+ }
+
+ result.per_stage = PMC_EXEC_CREDENTIAL_STAGE_PMC_INIT;
+ errno = 0;
+ result.per_pmc_init_rc = pmc_init();
+ result.per_pmc_init_errno = errno;
+ if (result.per_pmc_init_rc != 0) {
+ result.per_stage_errno = result.per_pmc_init_errno;
+ goto out;
+ }
+
+ result.per_stage = PMC_EXEC_CREDENTIAL_STAGE_READS;
+ value = 0;
+ errno = 0;
+ result.per_leader_rc = pmc_read(leader, &value);
+ result.per_leader_errno = errno;
+ result.per_leader_value = value;
+ value = 0;
+ errno = 0;
+ result.per_sibling_rc = pmc_read(sibling, &value);
+ result.per_sibling_errno = errno;
+ result.per_sibling_value = value;
+ result.per_stage = PMC_EXEC_CREDENTIAL_STAGE_DONE;
+
+out:
+ if (write_full(result_fd, &result, sizeof(result)) != 0)
+ return (3);
+ return (result.per_stage == PMC_EXEC_CREDENTIAL_STAGE_DONE ? 0 : 1);
+}
diff --git a/tests/sys/pmc/pmc_exec_test.c b/tests/sys/pmc/pmc_exec_test.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_exec_test.c
@@ -0,0 +1,1088 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ */
+
+/*
+ * This file tests exec authorization for grouped process PMCs.
+ * The test triggers a credential change during exec.
+ *
+ * A setgid exec adds a group. The PMC owner's credential does not
+ * have this group. So pmc_can_attach() must reject the new credential.
+ * The direct target is the control case.
+ * The inherited cases show that reauthorization uses the authoritative
+ * group-target set.
+ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <pmc.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#include "pmc_exec_credential_common.h"
+
+#define TEST_EVENT "instructions"
+#define TEST_OWNER_USER "nobody"
+#define TEST_FOREIGN_GROUP "wheel"
+#define TEST_HELPER_NAME "pmc_exec_credential_helper"
+#define TEST_HELPER_PATH_RECORD "pmc_exec_credential_helper.path"
+#define TEST_SUID_DIR_ENV "PMC_EXEC_TEST_SUID_DIR"
+#define TEST_MAX_ROW_OCCUPIERS 64
+
+#define TEST_SYSCTL_PREFIX "kern.hwpmc.test."
+#define TEST_HOLD_GROUP_RESIDENT \
+ TEST_SYSCTL_PREFIX "hold_group_resident"
+#define TEST_HOLD_GROUP_RESIDENT_ACK \
+ TEST_SYSCTL_PREFIX "hold_group_resident_ack"
+#define TEST_HOLD_GROUP_EVICTED \
+ TEST_SYSCTL_PREFIX "hold_group_evicted"
+#define TEST_HOLD_GROUP_EVICTED_ACK \
+ TEST_SYSCTL_PREFIX "hold_group_evicted_ack"
+#define TEST_LIVE_PMC_TARGETS \
+ TEST_SYSCTL_PREFIX "live_pmc_targets"
+#define TEST_LIVE_GROUP_TARGETS \
+ TEST_SYSCTL_PREFIX "live_group_targets"
+#define TEST_LIVE_TARGET_PROCESSES \
+ TEST_SYSCTL_PREFIX "live_target_processes"
+#define TEST_LIVE_RESIDUAL_ENTRIES \
+ TEST_SYSCTL_PREFIX "live_residual_entries"
+#define TEST_LIVE_ROTATION_REFS \
+ TEST_SYSCTL_PREFIX "live_rotation_refs"
+#define TEST_LIVE_RUN_REFS \
+ TEST_SYSCTL_PREFIX "live_run_refs"
+
+#define OWNER_COMMAND_START 'S'
+#define OWNER_COMMAND_EXEC 'E'
+#define OWNER_COMMAND_CLEANUP 'C'
+
+enum owner_message_kind {
+ OWNER_MESSAGE_ERROR,
+ OWNER_MESSAGE_PREPARED,
+ OWNER_MESSAGE_STARTED,
+ OWNER_MESSAGE_TARGET_DONE,
+ OWNER_MESSAGE_DONE
+};
+
+enum owner_stage {
+ OWNER_STAGE_NONE,
+ OWNER_STAGE_SETGROUPS,
+ OWNER_STAGE_SETGID,
+ OWNER_STAGE_SETUID,
+ OWNER_STAGE_PMC_INIT,
+ OWNER_STAGE_OCCUPANCY_ALLOCATE,
+ OWNER_STAGE_TARGET_PIPE,
+ OWNER_STAGE_TARGET_FORK,
+ OWNER_STAGE_TARGET_READY,
+ OWNER_STAGE_LEADER_ALLOCATE,
+ OWNER_STAGE_SIBLING_ALLOCATE,
+ OWNER_STAGE_GROUP_CREATE,
+ OWNER_STAGE_GROUP_ADD_LEADER,
+ OWNER_STAGE_GROUP_ADD_SIBLING,
+ OWNER_STAGE_GROUP_COMMIT,
+ OWNER_STAGE_GROUP_ATTACH,
+ OWNER_STAGE_WAIT_START,
+ OWNER_STAGE_GROUP_START,
+ OWNER_STAGE_WAIT_EXEC,
+ OWNER_STAGE_TARGET_COMMAND,
+ OWNER_STAGE_TARGET_WAIT,
+ OWNER_STAGE_WAIT_CLEANUP,
+ OWNER_STAGE_GROUP_RELEASE,
+ OWNER_STAGE_OCCUPANCY_RELEASE
+};
+
+struct owner_message {
+ uint32_t om_kind;
+ uint32_t om_stage;
+ int om_rc;
+ int om_errno;
+ uint32_t om_group_id;
+ pmc_id_t om_leader;
+ pmc_id_t om_sibling;
+ pid_t om_target_pid;
+ int om_target_status;
+};
+
+struct row_occupancy {
+ pmc_id_t ro_ids[TEST_MAX_ROW_OCCUPIERS];
+ u_int ro_n;
+};
+
+struct live_target_counts {
+ uint64_t ltc_pmc_targets;
+ uint64_t ltc_group_targets;
+ uint64_t ltc_target_processes;
+ uint64_t ltc_residual_entries;
+ uint64_t ltc_rotation_refs;
+ uint64_t ltc_run_refs;
+};
+
+static bool
+is_amd(void)
+{
+ char vendor[64];
+ size_t len;
+
+ len = sizeof(vendor);
+ if (sysctlbyname("kern.hwpmc.cpuid", vendor, &len, NULL, 0) != 0)
+ return (false);
+ return (strstr(vendor, "AuthenticAMD") != NULL ||
+ strstr(vendor, "HygonGenuine") != NULL);
+}
+
+static void
+require_hwpmc(void)
+{
+
+ if (pmc_init() != 0)
+ atf_tc_skip("hwpmc(4) is not available: %s", strerror(errno));
+ if (!is_amd())
+ atf_tc_skip("PMC grouping is supported only on AMD CPUs");
+}
+
+static int
+sysctl_read_u32(const char *name, u_int *value)
+{
+ size_t length;
+
+ length = sizeof(*value);
+ return (sysctlbyname(name, value, &length, NULL, 0));
+}
+
+static int
+sysctl_write_u32(const char *name, u_int value)
+{
+
+ return (sysctlbyname(name, NULL, NULL, &value, sizeof(value)));
+}
+
+static int
+sysctl_read_u64(const char *name, uint64_t *value)
+{
+ size_t length;
+
+ length = sizeof(*value);
+ return (sysctlbyname(name, value, &length, NULL, 0));
+}
+
+static void
+require_hwpmc_test_support(void)
+{
+ uint64_t value;
+ int error;
+
+ errno = 0;
+ error = sysctl_read_u64(TEST_LIVE_PMC_TARGETS, &value);
+ if (error == 0)
+ return;
+ if (errno == ENOENT)
+ atf_tc_skip("requires an HWPMCDEBUG module with test support");
+ atf_tc_fail("reading %s failed: errno %d (%s)",
+ TEST_LIVE_PMC_TARGETS, errno, strerror(errno));
+}
+
+static void
+read_live_target_counts(struct live_target_counts *counts)
+{
+
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_PMC_TARGETS,
+ &counts->ltc_pmc_targets) == 0,
+ "reading %s failed: %s", TEST_LIVE_PMC_TARGETS, strerror(errno));
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_GROUP_TARGETS,
+ &counts->ltc_group_targets) == 0,
+ "reading %s failed: %s", TEST_LIVE_GROUP_TARGETS, strerror(errno));
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_TARGET_PROCESSES,
+ &counts->ltc_target_processes) == 0,
+ "reading %s failed: %s", TEST_LIVE_TARGET_PROCESSES,
+ strerror(errno));
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RESIDUAL_ENTRIES,
+ &counts->ltc_residual_entries) == 0,
+ "reading %s failed: %s", TEST_LIVE_RESIDUAL_ENTRIES,
+ strerror(errno));
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_ROTATION_REFS,
+ &counts->ltc_rotation_refs) == 0,
+ "reading %s failed: %s", TEST_LIVE_ROTATION_REFS,
+ strerror(errno));
+ ATF_REQUIRE_MSG(sysctl_read_u64(TEST_LIVE_RUN_REFS,
+ &counts->ltc_run_refs) == 0,
+ "reading %s failed: %s", TEST_LIVE_RUN_REFS, strerror(errno));
+}
+
+static void
+check_live_counts_equal(const struct live_target_counts *actual,
+ const struct live_target_counts *expected)
+{
+
+ ATF_CHECK_MSG(actual->ltc_pmc_targets == expected->ltc_pmc_targets &&
+ actual->ltc_group_targets == expected->ltc_group_targets &&
+ actual->ltc_target_processes == expected->ltc_target_processes &&
+ actual->ltc_residual_entries == expected->ltc_residual_entries &&
+ actual->ltc_rotation_refs == expected->ltc_rotation_refs &&
+ actual->ltc_run_refs == expected->ltc_run_refs,
+ "cleanup changed live counts: pmc_target %ju/%ju "
+ "group_target %ju/%ju process %ju/%ju residual %ju/%ju "
+ "rotation_ref %ju/%ju run_ref %ju/%ju",
+ (uintmax_t)actual->ltc_pmc_targets,
+ (uintmax_t)expected->ltc_pmc_targets,
+ (uintmax_t)actual->ltc_group_targets,
+ (uintmax_t)expected->ltc_group_targets,
+ (uintmax_t)actual->ltc_target_processes,
+ (uintmax_t)expected->ltc_target_processes,
+ (uintmax_t)actual->ltc_residual_entries,
+ (uintmax_t)expected->ltc_residual_entries,
+ (uintmax_t)actual->ltc_rotation_refs,
+ (uintmax_t)expected->ltc_rotation_refs,
+ (uintmax_t)actual->ltc_run_refs,
+ (uintmax_t)expected->ltc_run_refs);
+}
+
+static void
+reset_exec_test_hooks(void)
+{
+
+ (void)sysctl_write_u32(TEST_HOLD_GROUP_RESIDENT, 0);
+ (void)sysctl_write_u32(TEST_HOLD_GROUP_EVICTED, 0);
+}
+
+static void
+require_test_sysctl_write(const char *name, u_int value)
+{
+
+ ATF_REQUIRE_MSG(sysctl_write_u32(name, value) == 0,
+ "writing %s=%u failed: errno %d (%s)", name, value, errno,
+ strerror(errno));
+}
+
+static void
+wait_test_sysctl_ack(const char *name, u_int expected)
+{
+ u_int value;
+ int i;
+
+ for (i = 0; i < 5000; i++) {
+ ATF_REQUIRE_MSG(sysctl_read_u32(name, &value) == 0,
+ "reading %s failed: errno %d (%s)", name, errno,
+ strerror(errno));
+ if (value == expected)
+ return;
+ usleep(1000);
+ }
+ atf_tc_fail("%s did not acknowledge value %u", name, expected);
+}
+
+static int
+read_byte(int fd, char *value)
+{
+ ssize_t n;
+
+ do {
+ n = read(fd, value, 1);
+ } while (n == -1 && errno == EINTR);
+ if (n == 0)
+ errno = EPIPE;
+ return (n == 1 ? 0 : -1);
+}
+
+static int
+write_byte(int fd, char value)
+{
+ ssize_t n;
+
+ do {
+ n = write(fd, &value, 1);
+ } while (n == -1 && errno == EINTR);
+ return (n == 1 ? 0 : -1);
+}
+
+static ssize_t
+read_full(int fd, void *buffer, size_t length)
+{
+ char *cursor;
+ ssize_t n;
+ size_t done;
+
+ cursor = buffer;
+ done = 0;
+ while (done < length) {
+ n = read(fd, cursor + done, length - done);
+ if (n == -1 && errno == EINTR)
+ continue;
+ if (n <= 0)
+ return (n == 0 ? (ssize_t)done : -1);
+ done += (size_t)n;
+ }
+ return ((ssize_t)done);
+}
+
+static int
+write_full(int fd, const void *buffer, size_t length)
+{
+ const char *cursor;
+ ssize_t n;
+ size_t done;
+
+ cursor = buffer;
+ done = 0;
+ while (done < length) {
+ n = write(fd, cursor + done, length - done);
+ if (n == -1 && errno == EINTR)
+ continue;
+ if (n <= 0)
+ return (-1);
+ done += (size_t)n;
+ }
+ return (0);
+}
+
+static const char *
+owner_stage_name(uint32_t stage)
+{
+
+ switch (stage) {
+ case OWNER_STAGE_SETGROUPS:
+ return ("setgroups");
+ case OWNER_STAGE_SETGID:
+ return ("setgid");
+ case OWNER_STAGE_SETUID:
+ return ("setuid");
+ case OWNER_STAGE_PMC_INIT:
+ return ("pmc_init");
+ case OWNER_STAGE_OCCUPANCY_ALLOCATE:
+ return ("row occupancy allocation");
+ case OWNER_STAGE_TARGET_PIPE:
+ return ("target pipe setup");
+ case OWNER_STAGE_TARGET_FORK:
+ return ("target fork");
+ case OWNER_STAGE_TARGET_READY:
+ return ("target readiness");
+ case OWNER_STAGE_LEADER_ALLOCATE:
+ return ("leader allocation");
+ case OWNER_STAGE_SIBLING_ALLOCATE:
+ return ("sibling allocation");
+ case OWNER_STAGE_GROUP_CREATE:
+ return ("group creation");
+ case OWNER_STAGE_GROUP_ADD_LEADER:
+ return ("leader group add");
+ case OWNER_STAGE_GROUP_ADD_SIBLING:
+ return ("sibling group add");
+ case OWNER_STAGE_GROUP_COMMIT:
+ return ("group commit");
+ case OWNER_STAGE_GROUP_ATTACH:
+ return ("group attach");
+ case OWNER_STAGE_WAIT_START:
+ return ("start command");
+ case OWNER_STAGE_GROUP_START:
+ return ("group start");
+ case OWNER_STAGE_WAIT_EXEC:
+ return ("exec command");
+ case OWNER_STAGE_TARGET_COMMAND:
+ return ("target exec command");
+ case OWNER_STAGE_TARGET_WAIT:
+ return ("target wait");
+ case OWNER_STAGE_WAIT_CLEANUP:
+ return ("cleanup command");
+ case OWNER_STAGE_GROUP_RELEASE:
+ return ("group release");
+ case OWNER_STAGE_OCCUPANCY_RELEASE:
+ return ("row occupancy release");
+ default:
+ return ("unknown owner stage");
+ }
+}
+
+static void
+owner_send_message(int fd, enum owner_message_kind kind,
+ enum owner_stage stage, int rc, int error, uint32_t group_id,
+ pmc_id_t leader, pmc_id_t sibling, pid_t target_pid, int target_status)
+{
+ struct owner_message message;
+
+ memset(&message, 0, sizeof(message));
+ message.om_kind = kind;
+ message.om_stage = stage;
+ message.om_rc = rc;
+ message.om_errno = error;
+ message.om_group_id = group_id;
+ message.om_leader = leader;
+ message.om_sibling = sibling;
+ message.om_target_pid = target_pid;
+ message.om_target_status = target_status;
+ if (write_full(fd, &message, sizeof(message)) != 0)
+ _exit(120);
+}
+
+static void
+owner_fail(int fd, enum owner_stage stage, int rc, int error)
+{
+
+ owner_send_message(fd, OWNER_MESSAGE_ERROR, stage, rc, error, 0,
+ PMC_ID_INVALID, PMC_ID_INVALID, -1, 0);
+ _exit(100 + stage);
+}
+
+static void
+target_exec_failure(int result_fd, int error)
+{
+ struct pmc_exec_credential_result result;
+
+ memset(&result, 0, sizeof(result));
+ result.per_version = PMC_EXEC_CREDENTIAL_RESULT_VERSION;
+ result.per_stage = PMC_EXEC_CREDENTIAL_STAGE_NONE;
+ result.per_stage_errno = error;
+ (void)write_full(result_fd, &result, sizeof(result));
+ _exit(127);
+}
+
+static void
+target_precheck_and_exec(const char *helper_path, int precheck_fd,
+ int result_fd, pmc_id_t leader, pmc_id_t sibling)
+{
+ struct pmc_exec_target_precheck precheck;
+ char fd_arg[32], leader_arg[32], sibling_arg[32];
+ int error, length;
+
+ memset(&precheck, 0, sizeof(precheck));
+ errno = 0;
+ precheck.petp_leader_rc =
+ pmc_read(leader, &precheck.petp_leader_value);
+ precheck.petp_leader_errno = errno;
+ errno = 0;
+ precheck.petp_sibling_rc =
+ pmc_read(sibling, &precheck.petp_sibling_value);
+ precheck.petp_sibling_errno = errno;
+ if (write_full(precheck_fd, &precheck, sizeof(precheck)) != 0)
+ target_exec_failure(result_fd, errno);
+ close(precheck_fd);
+
+ length = snprintf(fd_arg, sizeof(fd_arg), "%d", result_fd);
+ if (length < 0 || length >= (int)sizeof(fd_arg))
+ target_exec_failure(result_fd, EINVAL);
+ length = snprintf(leader_arg, sizeof(leader_arg), "%ju",
+ (uintmax_t)leader);
+ if (length < 0 || length >= (int)sizeof(leader_arg))
+ target_exec_failure(result_fd, EINVAL);
+ length = snprintf(sibling_arg, sizeof(sibling_arg), "%ju",
+ (uintmax_t)sibling);
+ if (length < 0 || length >= (int)sizeof(sibling_arg))
+ target_exec_failure(result_fd, EINVAL);
+ execl(helper_path, TEST_HELPER_NAME, fd_arg, leader_arg, sibling_arg,
+ NULL);
+ error = errno;
+ target_exec_failure(result_fd, error);
+}
+
+static void
+target_exec_relay(const char *source_helper_path, const char *helper_path,
+ int ready_fd, int command_fd, int precheck_fd, int result_fd)
+{
+ char command_fd_arg[32], precheck_fd_arg[32], ready_fd_arg[32];
+ char result_fd_arg[32];
+ int error, length;
+
+ length = snprintf(ready_fd_arg, sizeof(ready_fd_arg), "%d", ready_fd);
+ if (length < 0 || length >= (int)sizeof(ready_fd_arg))
+ target_exec_failure(result_fd, EINVAL);
+ length = snprintf(command_fd_arg, sizeof(command_fd_arg), "%d",
+ command_fd);
+ if (length < 0 || length >= (int)sizeof(command_fd_arg))
+ target_exec_failure(result_fd, EINVAL);
+ length = snprintf(precheck_fd_arg, sizeof(precheck_fd_arg), "%d",
+ precheck_fd);
+ if (length < 0 || length >= (int)sizeof(precheck_fd_arg))
+ target_exec_failure(result_fd, EINVAL);
+ length = snprintf(result_fd_arg, sizeof(result_fd_arg), "%d", result_fd);
+ if (length < 0 || length >= (int)sizeof(result_fd_arg))
+ target_exec_failure(result_fd, EINVAL);
+ execl(source_helper_path, TEST_HELPER_NAME,
+ PMC_EXEC_CREDENTIAL_RELAY_MODE, ready_fd_arg, command_fd_arg,
+ precheck_fd_arg, result_fd_arg, helper_path, NULL);
+ error = errno;
+ target_exec_failure(result_fd, error);
+}
+
+static pid_t
+owner_spawn_direct_target(int owner_message_fd, int precheck_fd,
+ int result_fd, const char *source_helper_path, const char *helper_path,
+ int *command_fd)
+{
+ int command_pipe[2], ready_pipe[2];
+ pid_t target;
+ char ready;
+
+ if (pipe(command_pipe) != 0 || pipe(ready_pipe) != 0)
+ owner_fail(owner_message_fd, OWNER_STAGE_TARGET_PIPE, -1, errno);
+ target = fork();
+ if (target == -1)
+ owner_fail(owner_message_fd, OWNER_STAGE_TARGET_FORK, -1, errno);
+ if (target == 0) {
+ close(command_pipe[1]);
+ close(ready_pipe[0]);
+ target_exec_relay(source_helper_path, helper_path,
+ ready_pipe[1], command_pipe[0], precheck_fd, result_fd);
+ }
+
+ close(command_pipe[0]);
+ close(ready_pipe[1]);
+ if (read_byte(ready_pipe[0], &ready) != 0)
+ owner_fail(owner_message_fd, OWNER_STAGE_TARGET_READY, -1,
+ errno);
+ if (ready != 'R')
+ owner_fail(owner_message_fd, OWNER_STAGE_TARGET_READY, -1,
+ EPROTO);
+ close(ready_pipe[0]);
+ *command_fd = command_pipe[1];
+ return (target);
+}
+
+static void
+owner_fill_occupancy(int owner_message_fd, struct row_occupancy *occupancy)
+{
+ pmc_id_t pmcid;
+ int error;
+
+ memset(occupancy, 0, sizeof(*occupancy));
+ while (occupancy->ro_n < nitems(occupancy->ro_ids)) {
+ errno = 0;
+ if (pmc_allocate(TEST_EVENT, PMC_MODE_TC, 0, PMC_CPU_ANY,
+ &pmcid, 0) == 0) {
+ occupancy->ro_ids[occupancy->ro_n++] = pmcid;
+ continue;
+ }
+ error = errno;
+ if (error == EINVAL && occupancy->ro_n != 0)
+ return;
+ owner_fail(owner_message_fd, OWNER_STAGE_OCCUPANCY_ALLOCATE,
+ -1, error);
+ }
+ owner_fail(owner_message_fd, OWNER_STAGE_OCCUPANCY_ALLOCATE, -1,
+ EOVERFLOW);
+}
+
+static void
+owner_release_occupancy(int owner_message_fd,
+ struct row_occupancy *occupancy)
+{
+ u_int i;
+
+ for (i = 0; i < occupancy->ro_n; i++) {
+ errno = 0;
+ if (pmc_release(occupancy->ro_ids[i]) != 0)
+ owner_fail(owner_message_fd,
+ OWNER_STAGE_OCCUPANCY_RELEASE, -1, errno);
+ }
+ occupancy->ro_n = 0;
+}
+
+static void
+owner_process(int command_fd, int message_fd, int precheck_fd, int result_fd,
+ const char *source_helper_path, const char *helper_path, uid_t owner_uid,
+ gid_t owner_gid, bool inherited, bool evicted)
+{
+ struct row_occupancy occupancy;
+ struct pmc_exec_target_command target_command;
+ uint32_t group_id;
+ pmc_id_t leader, sibling;
+ pid_t target, waited;
+ char command;
+ int status, target_command_fd;
+ uint32_t flags;
+
+ memset(&occupancy, 0, sizeof(occupancy));
+ target = -1;
+ target_command_fd = -1;
+ leader = PMC_ID_INVALID;
+ sibling = PMC_ID_INVALID;
+
+ errno = 0;
+ if (setgroups(1, &owner_gid) != 0)
+ owner_fail(message_fd, OWNER_STAGE_SETGROUPS, -1, errno);
+ errno = 0;
+ if (setgid(owner_gid) != 0)
+ owner_fail(message_fd, OWNER_STAGE_SETGID, -1, errno);
+ errno = 0;
+ if (setuid(owner_uid) != 0)
+ owner_fail(message_fd, OWNER_STAGE_SETUID, -1, errno);
+ errno = 0;
+ if (pmc_init() != 0)
+ owner_fail(message_fd, OWNER_STAGE_PMC_INIT, -1, errno);
+
+ if (!inherited) {
+ target = owner_spawn_direct_target(message_fd, precheck_fd,
+ result_fd, source_helper_path, helper_path,
+ &target_command_fd);
+ close(precheck_fd);
+ close(result_fd);
+ }
+ if (evicted)
+ owner_fill_occupancy(message_fd, &occupancy);
+
+ flags = PMC_F_DESCENDANTS | PMC_F_GROUP_MUX;
+ errno = 0;
+ if (pmc_allocate_group(TEST_EVENT, PMC_MODE_TC, flags, PMC_CPU_ANY,
+ &leader, 0) != 0)
+ owner_fail(message_fd, OWNER_STAGE_LEADER_ALLOCATE, -1, errno);
+ errno = 0;
+ if (pmc_allocate_group(TEST_EVENT, PMC_MODE_TC, 0, PMC_CPU_ANY,
+ &sibling, 0) != 0)
+ owner_fail(message_fd, OWNER_STAGE_SIBLING_ALLOCATE, -1, errno);
+ errno = 0;
+ if (pmc_group_create(&group_id) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_CREATE, -1, errno);
+ errno = 0;
+ if (pmc_group_add(group_id, leader, 1) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_ADD_LEADER, -1, errno);
+ errno = 0;
+ if (pmc_group_add(group_id, sibling, 0) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_ADD_SIBLING, -1,
+ errno);
+ errno = 0;
+ if (pmc_group_commit(group_id) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_COMMIT, -1, errno);
+ errno = 0;
+ if (pmc_attach(leader, inherited ? getpid() : target) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_ATTACH, -1, errno);
+
+ owner_send_message(message_fd, OWNER_MESSAGE_PREPARED,
+ OWNER_STAGE_NONE, 0, 0, group_id, leader, sibling, target, 0);
+ if (read_byte(command_fd, &command) != 0)
+ owner_fail(message_fd, OWNER_STAGE_WAIT_START, -1, errno);
+ if (command != OWNER_COMMAND_START)
+ owner_fail(message_fd, OWNER_STAGE_WAIT_START, -1, EPROTO);
+ errno = 0;
+ if (pmc_start(leader) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_START, -1, errno);
+ owner_send_message(message_fd, OWNER_MESSAGE_STARTED,
+ OWNER_STAGE_NONE, 0, 0, group_id, leader, sibling, target, 0);
+
+ if (read_byte(command_fd, &command) != 0)
+ owner_fail(message_fd, OWNER_STAGE_WAIT_EXEC, -1, errno);
+ if (command != OWNER_COMMAND_EXEC)
+ owner_fail(message_fd, OWNER_STAGE_WAIT_EXEC, -1, EPROTO);
+ if (inherited) {
+ target = fork();
+ if (target == -1)
+ owner_fail(message_fd, OWNER_STAGE_TARGET_FORK, -1,
+ errno);
+ if (target == 0)
+ target_precheck_and_exec(helper_path, precheck_fd,
+ result_fd, leader, sibling);
+ close(precheck_fd);
+ close(result_fd);
+ } else {
+ target_command.petc_leader = leader;
+ target_command.petc_sibling = sibling;
+ if (write_full(target_command_fd, &target_command,
+ sizeof(target_command)) != 0)
+ owner_fail(message_fd, OWNER_STAGE_TARGET_COMMAND, -1,
+ errno);
+ close(target_command_fd);
+ }
+
+ errno = 0;
+ waited = waitpid(target, &status, 0);
+ if (waited != target)
+ owner_fail(message_fd, OWNER_STAGE_TARGET_WAIT, (int)waited,
+ errno);
+ owner_send_message(message_fd, OWNER_MESSAGE_TARGET_DONE,
+ OWNER_STAGE_NONE, 0, 0, group_id, leader, sibling, target, status);
+
+ if (read_byte(command_fd, &command) != 0)
+ owner_fail(message_fd, OWNER_STAGE_WAIT_CLEANUP, -1, errno);
+ if (command != OWNER_COMMAND_CLEANUP)
+ owner_fail(message_fd, OWNER_STAGE_WAIT_CLEANUP, -1, EPROTO);
+ errno = 0;
+ if (pmc_release(leader) != 0)
+ owner_fail(message_fd, OWNER_STAGE_GROUP_RELEASE, -1, errno);
+ owner_release_occupancy(message_fd, &occupancy);
+ owner_send_message(message_fd, OWNER_MESSAGE_DONE, OWNER_STAGE_NONE,
+ 0, 0, group_id, leader, sibling, target, status);
+ _exit(0);
+}
+
+static void
+save_helper_path(const char *path)
+{
+ int fd;
+
+ fd = open(TEST_HELPER_PATH_RECORD, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open %s failed: errno %d (%s)",
+ TEST_HELPER_PATH_RECORD, errno, strerror(errno));
+ ATF_REQUIRE_MSG(write_full(fd, path, strlen(path)) == 0,
+ "writing %s failed: errno %d (%s)", TEST_HELPER_PATH_RECORD,
+ errno, strerror(errno));
+ ATF_REQUIRE_MSG(close(fd) == 0, "closing %s failed: errno %d (%s)",
+ TEST_HELPER_PATH_RECORD, errno, strerror(errno));
+}
+
+static void
+cleanup_helper_path(void)
+{
+ char path[PATH_MAX];
+ ssize_t n;
+ int fd;
+
+ fd = open(TEST_HELPER_PATH_RECORD, O_RDONLY);
+ if (fd == -1)
+ return;
+ n = read(fd, path, sizeof(path) - 1);
+ (void)close(fd);
+ if (n > 0) {
+ path[n] = '\0';
+ (void)unlink(path);
+ }
+ (void)unlink(TEST_HELPER_PATH_RECORD);
+}
+
+static void
+install_setgid_helper(const char *source_path, const char *destination_dir,
+ gid_t foreign_gid, char helper_path[PATH_MAX])
+{
+ struct stat st;
+ struct statfs fs;
+ char buffer[8192];
+ ssize_t nread;
+ int destination_fd, length, source_fd;
+
+ source_fd = open(source_path, O_RDONLY);
+ ATF_REQUIRE_MSG(source_fd != -1,
+ "opening helper %s failed: errno %d (%s)", source_path, errno,
+ strerror(errno));
+
+ length = snprintf(helper_path, PATH_MAX,
+ "%s/.pmc-exec-credential.XXXXXX", destination_dir);
+ ATF_REQUIRE_MSG(length >= 0 && length < PATH_MAX,
+ "helper destination path is too long");
+ destination_fd = mkstemp(helper_path);
+ ATF_REQUIRE_MSG(destination_fd != -1,
+ "mkstemp for setgid helper failed: errno %d (%s)", errno,
+ strerror(errno));
+ save_helper_path(helper_path);
+ ATF_REQUIRE_MSG(fstatfs(destination_fd, &fs) == 0,
+ "fstatfs for %s failed: errno %d (%s)", helper_path, errno,
+ strerror(errno));
+ if ((fs.f_flags & MNT_NOSUID) != 0) {
+ (void)close(source_fd);
+ (void)close(destination_fd);
+ cleanup_helper_path();
+ atf_tc_skip("%s is on a nosuid filesystem", helper_path);
+ }
+
+ while ((nread = read(source_fd, buffer, sizeof(buffer))) != 0) {
+ ATF_REQUIRE_MSG(nread > 0,
+ "reading helper %s failed: errno %d (%s)", source_path,
+ errno, strerror(errno));
+ ATF_REQUIRE_MSG(write_full(destination_fd, buffer,
+ (size_t)nread) == 0,
+ "copying helper to %s failed: errno %d (%s)", helper_path,
+ errno, strerror(errno));
+ }
+ ATF_REQUIRE_MSG(close(source_fd) == 0,
+ "closing helper %s failed: errno %d (%s)", source_path, errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(fchown(destination_fd, 0, foreign_gid) == 0,
+ "fchown helper %s failed: errno %d (%s)", helper_path, errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(fchmod(destination_fd, 02555) == 0,
+ "fchmod helper %s failed: errno %d (%s)", helper_path, errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(fsync(destination_fd) == 0,
+ "fsync helper %s failed: errno %d (%s)", helper_path, errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(close(destination_fd) == 0,
+ "closing helper %s failed: errno %d (%s)", helper_path, errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(stat(helper_path, &st) == 0,
+ "stat helper %s failed: errno %d (%s)", helper_path, errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(S_ISREG(st.st_mode) && st.st_gid == foreign_gid &&
+ (st.st_mode & 07777) == 02555,
+ "helper %s has mode %#o uid %u gid %u, expected setgid gid %u",
+ helper_path, st.st_mode, st.st_uid, st.st_gid, foreign_gid);
+}
+
+static void
+require_owner_message(int fd, enum owner_message_kind expected,
+ struct owner_message *message)
+{
+ ssize_t nread;
+
+ memset(message, 0, sizeof(*message));
+ nread = read_full(fd, message, sizeof(*message));
+ ATF_REQUIRE_MSG(nread == (ssize_t)sizeof(*message),
+ "owner message read returned %zd of %zu bytes", nread,
+ sizeof(*message));
+ ATF_REQUIRE_MSG(message->om_kind != OWNER_MESSAGE_ERROR,
+ "owner failed at %s: rc=%d errno=%d (%s)",
+ owner_stage_name(message->om_stage), message->om_rc,
+ message->om_errno, strerror(message->om_errno));
+ ATF_REQUIRE_MSG(message->om_kind == (uint32_t)expected,
+ "owner message kind %u, expected %u", message->om_kind, expected);
+}
+
+static void
+check_helper_credentials(const struct pmc_exec_credential_result *result,
+ uid_t owner_uid, gid_t owner_gid, gid_t foreign_gid)
+{
+
+ ATF_REQUIRE_MSG(
+ result->per_version == PMC_EXEC_CREDENTIAL_RESULT_VERSION,
+ "helper result version %u, expected %u", result->per_version,
+ PMC_EXEC_CREDENTIAL_RESULT_VERSION);
+ ATF_REQUIRE_MSG(result->per_stage == PMC_EXEC_CREDENTIAL_STAGE_DONE,
+ "helper stopped at stage %u: errno %d (%s)", result->per_stage,
+ result->per_stage_errno, strerror(result->per_stage_errno));
+ ATF_REQUIRE_MSG(result->per_pmc_init_rc == 0,
+ "helper pmc_init returned %d errno %d (%s)",
+ result->per_pmc_init_rc, result->per_pmc_init_errno,
+ strerror(result->per_pmc_init_errno));
+ ATF_REQUIRE_MSG(result->per_ruid == owner_uid &&
+ result->per_euid == owner_uid && result->per_suid == owner_uid,
+ "helper uid credentials are r/e/s %u/%u/%u, expected %u",
+ result->per_ruid, result->per_euid, result->per_suid, owner_uid);
+ ATF_REQUIRE_MSG(result->per_rgid == owner_gid &&
+ result->per_egid == foreign_gid &&
+ result->per_sgid == foreign_gid,
+ "helper gid credentials are r/e/s %u/%u/%u, expected %u/%u/%u",
+ result->per_rgid, result->per_egid, result->per_sgid, owner_gid,
+ foreign_gid, foreign_gid);
+ ATF_REQUIRE_MSG(result->per_ngroups == 1 &&
+ result->per_groups[0] == owner_gid,
+ "helper supplementary groups are count=%d first=%u, expected %u",
+ result->per_ngroups, result->per_groups[0], owner_gid);
+}
+
+static void
+run_credential_exec_case(const atf_tc_t *tc, bool inherited, bool evicted)
+{
+ struct pmc_exec_credential_result result;
+ struct live_target_counts final, initial;
+ struct owner_message message;
+ struct pmc_exec_target_precheck precheck;
+ struct passwd *owner;
+ struct group *foreign;
+ char helper_path[PATH_MAX], source_helper_path[PATH_MAX];
+ const char *destination_dir, *hold_ack_name, *hold_name, *srcdir;
+ pid_t owner_pid, waited;
+ uid_t owner_uid;
+ gid_t foreign_gid, owner_gid;
+ ssize_t result_nread, precheck_nread;
+ int command_pipe[2], message_pipe[2], precheck_pipe[2], result_pipe[2];
+ int length, owner_status, wait_errno;
+
+ require_hwpmc();
+ require_hwpmc_test_support();
+ reset_exec_test_hooks();
+ read_live_target_counts(&initial);
+
+ owner = getpwnam(TEST_OWNER_USER);
+ ATF_REQUIRE_MSG(owner != NULL, "required user %s is missing",
+ TEST_OWNER_USER);
+ owner_uid = owner->pw_uid;
+ owner_gid = owner->pw_gid;
+ foreign = getgrnam(TEST_FOREIGN_GROUP);
+ ATF_REQUIRE_MSG(foreign != NULL, "required group %s is missing",
+ TEST_FOREIGN_GROUP);
+ foreign_gid = foreign->gr_gid;
+ ATF_REQUIRE_MSG(owner_uid != 0 && owner_gid != foreign_gid,
+ "test credentials do not create a foreign group: uid=%u gid=%u "
+ "foreign gid=%u", owner_uid, owner_gid, foreign_gid);
+ srcdir = atf_tc_get_config_var(tc, "srcdir");
+ length = snprintf(source_helper_path, sizeof(source_helper_path),
+ "%s/%s", srcdir, TEST_HELPER_NAME);
+ ATF_REQUIRE_MSG(length >= 0 && length < (int)sizeof(source_helper_path),
+ "helper source path is too long");
+ destination_dir = getenv(TEST_SUID_DIR_ENV);
+ if (destination_dir == NULL || destination_dir[0] == '\0')
+ destination_dir = srcdir;
+ install_setgid_helper(source_helper_path, destination_dir, foreign_gid,
+ helper_path);
+
+ ATF_REQUIRE_MSG(pipe(command_pipe) == 0,
+ "owner command pipe failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pipe(message_pipe) == 0,
+ "owner message pipe failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pipe(precheck_pipe) == 0,
+ "target precheck pipe failed: errno %d (%s)", errno,
+ strerror(errno));
+ ATF_REQUIRE_MSG(pipe(result_pipe) == 0,
+ "helper result pipe failed: errno %d (%s)", errno,
+ strerror(errno));
+
+ owner_pid = fork();
+ ATF_REQUIRE_MSG(owner_pid != -1,
+ "owner fork failed: errno %d (%s)", errno, strerror(errno));
+ if (owner_pid == 0) {
+ close(command_pipe[1]);
+ close(message_pipe[0]);
+ close(precheck_pipe[0]);
+ close(result_pipe[0]);
+ owner_process(command_pipe[0], message_pipe[1],
+ precheck_pipe[1], result_pipe[1], source_helper_path,
+ helper_path, owner_uid, owner_gid, inherited, evicted);
+ }
+
+ close(command_pipe[0]);
+ close(message_pipe[1]);
+ close(precheck_pipe[1]);
+ close(result_pipe[1]);
+ require_owner_message(message_pipe[0], OWNER_MESSAGE_PREPARED,
+ &message);
+
+ hold_name = evicted ? TEST_HOLD_GROUP_EVICTED :
+ TEST_HOLD_GROUP_RESIDENT;
+ hold_ack_name = evicted ? TEST_HOLD_GROUP_EVICTED_ACK :
+ TEST_HOLD_GROUP_RESIDENT_ACK;
+ require_test_sysctl_write(hold_name, message.om_group_id);
+ ATF_REQUIRE_MSG(write_byte(command_pipe[1], OWNER_COMMAND_START) == 0,
+ "sending owner start command failed: errno %d (%s)", errno,
+ strerror(errno));
+ require_owner_message(message_pipe[0], OWNER_MESSAGE_STARTED,
+ &message);
+ wait_test_sysctl_ack(hold_ack_name, message.om_group_id);
+
+ ATF_REQUIRE_MSG(write_byte(command_pipe[1], OWNER_COMMAND_EXEC) == 0,
+ "sending owner exec command failed: errno %d (%s)", errno,
+ strerror(errno));
+ memset(&precheck, 0, sizeof(precheck));
+ precheck_nread = read_full(precheck_pipe[0], &precheck,
+ sizeof(precheck));
+ memset(&result, 0, sizeof(result));
+ result_nread = read_full(result_pipe[0], &result, sizeof(result));
+ require_owner_message(message_pipe[0], OWNER_MESSAGE_TARGET_DONE,
+ &message);
+
+ require_test_sysctl_write(hold_name, 0);
+ ATF_REQUIRE_MSG(write_byte(command_pipe[1],
+ OWNER_COMMAND_CLEANUP) == 0,
+ "sending owner cleanup command failed: errno %d (%s)", errno,
+ strerror(errno));
+ require_owner_message(message_pipe[0], OWNER_MESSAGE_DONE, &message);
+ close(command_pipe[1]);
+ close(message_pipe[0]);
+ close(precheck_pipe[0]);
+ close(result_pipe[0]);
+ errno = 0;
+ waited = waitpid(owner_pid, &owner_status, 0);
+ wait_errno = errno;
+ read_live_target_counts(&final);
+ reset_exec_test_hooks();
+ cleanup_helper_path();
+
+ ATF_REQUIRE_MSG(precheck_nread == (ssize_t)sizeof(precheck),
+ "target precheck read returned %zd of %zu bytes", precheck_nread,
+ sizeof(precheck));
+ ATF_REQUIRE_MSG(precheck.petp_leader_rc == 0,
+ "leader was not readable before exec: errno %d (%s)",
+ precheck.petp_leader_errno, strerror(precheck.petp_leader_errno));
+ ATF_REQUIRE_MSG(precheck.petp_sibling_rc == 0,
+ "sibling was not readable before exec: errno %d (%s)",
+ precheck.petp_sibling_errno, strerror(precheck.petp_sibling_errno));
+ ATF_REQUIRE_MSG(result_nread == (ssize_t)sizeof(result),
+ "helper result read returned %zd of %zu bytes", result_nread,
+ sizeof(result));
+ ATF_REQUIRE_MSG(WIFEXITED(message.om_target_status) &&
+ WEXITSTATUS(message.om_target_status) == 0,
+ "credential helper exited abnormally: status %#x",
+ message.om_target_status);
+ ATF_REQUIRE_MSG(waited == owner_pid,
+ "waitpid for owner failed: errno %d (%s)", wait_errno,
+ strerror(wait_errno));
+ ATF_REQUIRE_MSG(WIFEXITED(owner_status) &&
+ WEXITSTATUS(owner_status) == 0,
+ "owner exited abnormally: status %#x", owner_status);
+ check_helper_credentials(&result, owner_uid, owner_gid, foreign_gid);
+ check_live_counts_equal(&final, &initial);
+
+ ATF_CHECK_MSG(result.per_leader_rc == -1 &&
+ result.per_leader_errno == ESRCH,
+ "leader read after unauthorized exec returned rc=%d errno=%d "
+ "(%s), expected -1/ESRCH", result.per_leader_rc,
+ result.per_leader_errno, strerror(result.per_leader_errno));
+ ATF_CHECK_MSG(result.per_sibling_rc == -1 &&
+ result.per_sibling_errno == ESRCH,
+ "sibling read after unauthorized exec returned rc=%d errno=%d "
+ "(%s), expected -1/ESRCH", result.per_sibling_rc,
+ result.per_sibling_errno, strerror(result.per_sibling_errno));
+}
+
+ATF_TC_WITH_CLEANUP(credential_exec_direct_target);
+ATF_TC_HEAD(credential_exec_direct_target, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(credential_exec_direct_target, tc)
+{
+
+ run_credential_exec_case(tc, false, false);
+}
+ATF_TC_CLEANUP(credential_exec_direct_target, tc)
+{
+
+ reset_exec_test_hooks();
+ cleanup_helper_path();
+}
+
+ATF_TC_WITH_CLEANUP(credential_exec_inherited_resident);
+ATF_TC_HEAD(credential_exec_inherited_resident, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(credential_exec_inherited_resident, tc)
+{
+
+ run_credential_exec_case(tc, true, false);
+}
+ATF_TC_CLEANUP(credential_exec_inherited_resident, tc)
+{
+
+ reset_exec_test_hooks();
+ cleanup_helper_path();
+}
+
+ATF_TC_WITH_CLEANUP(credential_exec_inherited_evicted);
+ATF_TC_HEAD(credential_exec_inherited_evicted, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(credential_exec_inherited_evicted, tc)
+{
+
+ run_credential_exec_case(tc, true, true);
+}
+ATF_TC_CLEANUP(credential_exec_inherited_evicted, tc)
+{
+
+ reset_exec_test_hooks();
+ cleanup_helper_path();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, credential_exec_direct_target);
+ ATF_TP_ADD_TC(tp, credential_exec_inherited_resident);
+ ATF_TP_ADD_TC(tp, credential_exec_inherited_evicted);
+ return (atf_no_error());
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Sep 11, 2:12 AM (19 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38678429
Default Alt Text
D59373.diff (42 KB)
Attached To
Mode
D59373: tests: cover PMU group authorization across exec
Attached
Detach File
Event Timeline
Log In to Comment