Add selftests for the printk virtualization feature. The new tests cover ve_printk, ve_printk_ratelimited, net_ve_ratelimited, and log buffer overflow handling.
https://virtuozzo.atlassian.net/browse/VSTOR-114252 Signed-off-by: Aleksei Oladko <[email protected]> --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/ve_printk/.gitignore | 3 + tools/testing/selftests/ve_printk/Makefile | 8 + tools/testing/selftests/ve_printk/test_segf.c | 12 + tools/testing/selftests/ve_printk/test_trap.c | 5 + .../selftests/ve_printk/ve_printk_test.c | 593 ++++++++++++++++++ 6 files changed, 622 insertions(+) create mode 100644 tools/testing/selftests/ve_printk/.gitignore create mode 100644 tools/testing/selftests/ve_printk/Makefile create mode 100644 tools/testing/selftests/ve_printk/test_segf.c create mode 100644 tools/testing/selftests/ve_printk/test_trap.c create mode 100644 tools/testing/selftests/ve_printk/ve_printk_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 363d031a16f7..7334fb207676 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -113,6 +113,7 @@ TARGETS += tty TARGETS += uevent TARGETS += user_events TARGETS += vDSO +TARGETS += ve_printk TARGETS += mm TARGETS += x86 TARGETS += zram diff --git a/tools/testing/selftests/ve_printk/.gitignore b/tools/testing/selftests/ve_printk/.gitignore new file mode 100644 index 000000000000..a4ad6620b803 --- /dev/null +++ b/tools/testing/selftests/ve_printk/.gitignore @@ -0,0 +1,3 @@ +ve_printk_test +test_segf +test_trap diff --git a/tools/testing/selftests/ve_printk/Makefile b/tools/testing/selftests/ve_printk/Makefile new file mode 100644 index 000000000000..e3edcbacda1e --- /dev/null +++ b/tools/testing/selftests/ve_printk/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for ve_printk selftests. +CFLAGS = -g -I../../../../usr/include/ -Wall -O2 + +TEST_GEN_PROGS += ve_printk_test +TEST_GEN_FILES += test_segf test_trap + +include ../lib.mk diff --git a/tools/testing/selftests/ve_printk/test_segf.c b/tools/testing/selftests/ve_printk/test_segf.c new file mode 100644 index 000000000000..cdc89068ca06 --- /dev/null +++ b/tools/testing/selftests/ve_printk/test_segf.c @@ -0,0 +1,12 @@ +#include <stdio.h> +#include <unistd.h> + +int main(void) +{ + int *p = (int *)0xffffffffffffff00; + + printf("%d\n", getpid()); + fflush(stdout); + *p = 1; + return 0; +} diff --git a/tools/testing/selftests/ve_printk/test_trap.c b/tools/testing/selftests/ve_printk/test_trap.c new file mode 100644 index 000000000000..b774e2b9484c --- /dev/null +++ b/tools/testing/selftests/ve_printk/test_trap.c @@ -0,0 +1,5 @@ +int main(void) +{ + __asm__("int3"); + return 0; +} diff --git a/tools/testing/selftests/ve_printk/ve_printk_test.c b/tools/testing/selftests/ve_printk/ve_printk_test.c new file mode 100644 index 000000000000..78475ff71faa --- /dev/null +++ b/tools/testing/selftests/ve_printk/ve_printk_test.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <linux/sched.h> +#include <time.h> +#include <sched.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <linux/limits.h> +#include <errno.h> + +#include "../kselftest_harness.h" + +#define __STACK_SIZE (8 * 1024 * 1024) +#define CTID_MIN 108 +#define CTID_MAX 200 +#define SEGFAULT_PROG "test_segf" +#define TRAP_PROG "test_trap" +#define TEST_RATELIMIT_BURST 10 +#define TEST_RATELIMIT 5 + +static int has_substr(char *buf, const char *str) +{ + char *token; + char *str_ptr = buf; + + while ((token = strsep(&str_ptr, ",")) != NULL) { + if (!strcmp(token, str)) + return 1; + } + return 0; +} + +static int get_mount_path(const char *fstype, const char *subsys, char *out, int size) +{ + FILE *fp; + int n; + char buf[PATH_MAX]; + char target[4096]; + char ops[4096]; + char format[4096]; + int ret = 1; + + snprintf(format, sizeof(format), "%%*s %%4095s %s %%4095s", fstype); + + fp = fopen("/proc/mounts", "r"); + if (fp == NULL) + return -1; + + while (fgets(buf, sizeof(buf), fp)) { + n = sscanf(buf, format, target, ops); + if (n != 2) + continue; + if (subsys == NULL || has_substr(ops, subsys)) { + strncpy(out, target, size); + out[size-1] = '\0'; + ret = 0; + break; + } + } + fclose(fp); + + return ret; +} + +FIXTURE(ve_printk) +{ + char cgv2_path[PATH_MAX]; + char cgve_path[PATH_MAX]; + int ctid; +}; + +FIXTURE_SETUP(ve_printk) +{ + char path[PATH_MAX * 2]; + + ASSERT_EQ(get_mount_path("cgroup2", NULL, self->cgv2_path, sizeof(self->cgv2_path)), 0); + ASSERT_EQ(get_mount_path("cgroup", "ve", self->cgve_path, sizeof(self->cgve_path)), 0); + + self->ctid = CTID_MIN; + while (self->ctid < CTID_MAX) { + snprintf(path, sizeof(path), "%s/%d", self->cgve_path, self->ctid); + if (access(path, F_OK) && errno == ENOENT) { + snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid); + if (access(path, F_OK)) { + break; + } + } + self->ctid++; + } + ASSERT_LT(self->ctid, CTID_MAX); + + snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid); + ASSERT_EQ(mkdir(path, 0755), 0); + snprintf(path, sizeof(path), "%s/%d", self->cgve_path, self->ctid); + ASSERT_EQ(mkdir(path, 0755), 0); + snprintf(path, sizeof(path), "echo %d > %s/%d/ve.veid", + self->ctid, self->cgve_path, self->ctid); + ASSERT_EQ(system(path), 0); +}; + +FIXTURE_TEARDOWN(ve_printk) +{ + char path[PATH_MAX * 2]; + + snprintf(path, sizeof(path), "%s/%d/vz.slice", self->cgv2_path, self->ctid); + rmdir(path); + snprintf(path, sizeof(path), "%s/%d", self->cgv2_path, self->ctid); + rmdir(path); + snprintf(path, sizeof(path), "%s/%d", self->cgve_path, self->ctid); + rmdir(path); +} + +int setup_timens(void) +{ + int ret, fd; + + if (access("/proc/self/timens_offsets", F_OK)) + return 0; + + if (unshare(CLONE_NEWTIME)) { + return -1; + } + + fd = open("/proc/self/ns/time_for_children", O_RDONLY); + if (fd < 0) { + return -1; + } + + ret = setns(fd, CLONE_NEWTIME); + + close(fd); + return ret; +} + +struct fargs { + char *cgve; + int ctid; + int test; +}; + +enum { + TEST_LOG, + TEST_LOGV0, + TEST_LOGBOTH, + TEST_PR_RATELIMIT, + TEST_OVERFLOW, +}; + +int ve_printk_test_logct(void) +{ + FILE *pdmesg; + char buf[1024]; + char str[256]; + FILE *f; + int pid; + int ret = 2; + + f = popen("./" SEGFAULT_PROG, "r"); + if (!f) + return -1; + + if (fscanf(f, "%d", &pid) != 1) { + pclose(f); + return -1; + } + pclose(f); + + pdmesg = popen("dmesg", "r"); + if (!pdmesg) + return -1; + + snprintf(str, sizeof(str), "%s[%d]: segfault at", SEGFAULT_PROG, pid); + while (fgets(buf, sizeof(buf), pdmesg)) { + if (ret == 2 && strstr(buf, str)) { + ret = 1; + } + if (ret == 1 && strstr(buf, "Code: ")) { + ret = 0; + break; + } + } + pclose(pdmesg); + return ret; +} + +int ve_printk_test_logve0(void) +{ + FILE *pdmesg; + char buf[1024]; + int ret = 0; + + /* do_trap -> show_signal -> print_vma_addr -> ve_print_vma_addr(VE0_LOG, ... */ + system("./" TRAP_PROG); + + pdmesg = popen("dmesg", "r"); + if (!pdmesg) + return -1; + + while (fgets(buf, sizeof(buf), pdmesg)) { + if (strstr(buf, "traps: test_trap[") && + strstr(buf, " in test_trap[")) { + ret = 1; + break; + } + } + pclose(pdmesg); + return ret; +} + +int ve_printk_test_logboth(void) +{ + FILE *pdmesg; + char buf[1024]; + int ret = TEST_RATELIMIT_BURST; + + system("ip link set up dev lo"); + /* + * Reduce the connection tracking table size to 2. After two ping calls the + * table will be full, and all new packets will trigger + * net_veboth_ratelimited + */ + system("echo 2 > /proc/sys/net/nf_conntrack_max"); + system("iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT"); + system("ping -q -c 1 -w 1 127.0.0.1 > /dev/null"); + system("ping -q -c 1 -w 1 127.0.0.1 > /dev/null"); + /* net_veboth_ratelimited */ + system("ping -q -c 100 -w 1 -i 0.0001 127.0.0.1 > /dev/null"); + + pdmesg = popen("dmesg", "r"); + if (!pdmesg) + return -1; + + while (fgets(buf, sizeof(buf), pdmesg)) { + if (strstr(buf, "nf_conntrack table full, dropping packet")) { + ret--; + } + } + pclose(pdmesg); + return ret; +} + +int ve_printk_test_ratelimit(void) +{ + FILE *pdmesg; + char buf[1024]; + int ret = TEST_RATELIMIT_BURST + 1; + int supressed = 1; + int i; + + /* reject_tg_check -> ve_printk_ratelimited(VE_LOG, ... */ + for (i = 0; i < 2 * TEST_RATELIMIT_BURST; i++) { + system("iptables -A INPUT -s 10.20.30.40 -j REJECT --reject-with tcp-reset &> /dev/null"); + } + sleep(TEST_RATELIMIT + 1); + system("iptables -A INPUT -s 10.20.30.40 -j REJECT --reject-with tcp-reset &> /dev/null"); + + pdmesg = popen("dmesg", "r"); + if (!pdmesg) + return -1; + + while (fgets(buf, sizeof(buf), pdmesg)) { + if (strstr(buf, "TCP_RESET invalid for non-tcp")) { + ret--; + } + if (strstr(buf, "reject_tg_check") && strstr(buf, "callbacks suppressed")) + supressed--; + } + pclose(pdmesg); + + return ret + supressed; +} + +int ve_printk_test_overflow(void) +{ + int i; + + /* + * Ten program segfaults are enough to overflow the container's log buffer + * Run 15 to ensure the buffer is actually overflowed. + */ + for (i = 0; i < 15; i++) { + system("./" SEGFAULT_PROG " > /dev/null"); + /* bypass printk_ratelimit */ + if (i == 9) + sleep(5); + } + return ve_printk_test_logct(); +} + +int child_func(void *arg) +{ + int ret; + int fd; + struct fargs *args = (struct fargs *)arg; + char cg_state[PATH_MAX]; + + ret = setup_timens(); + if (ret) + return ret; + + snprintf(cg_state, sizeof(cg_state), "%s/%d/ve.state", args->cgve, args->ctid); + + (void)umount2("/proc", MNT_DETACH); + ret = mount("proc", "/proc", "proc", 0, NULL); + if (ret < 0) + return ret; + + fd = open("/proc/self/uid_map", O_WRONLY); + if (fd < 0) + return -1; + write(fd, "0 0 1\n", 6); + close(fd); + + fd = open("/proc/self/gid_map", O_WRONLY); + if (fd < 0) + return -1; + write(fd, "0 0 1\n", 6); + close(fd); + + fd = open(cg_state, O_WRONLY); + if (fd < 0) { + return -1; + } + + if (write(fd, "START", strlen("START")) < 0) { + close(fd); + return -1; + } + + close(fd); + + switch(args->test) { + case TEST_LOG: + ret = ve_printk_test_logct(); + break; + case TEST_LOGV0: + ret = ve_printk_test_logve0(); + break; + case TEST_LOGBOTH: + ret = ve_printk_test_logboth(); + break; + case TEST_PR_RATELIMIT: + ret = ve_printk_test_ratelimit(); + break; + case TEST_OVERFLOW: + ret = ve_printk_test_overflow(); + break; + default: + ret = -1; + } + return ret; +} + +int enter_cgroup(const char *cgroup, int ctid) +{ + char cg_path[PATH_MAX]; + char pid_str[64]; + int fd; + int ret = 0; + + if (ctid) + snprintf(cg_path, sizeof(cg_path), "%s/%d/cgroup.procs", cgroup, ctid); + else + snprintf(cg_path, sizeof(cg_path), "%s/cgroup.procs", cgroup); + snprintf(pid_str, sizeof(pid_str), "%d", getpid()); + fd = open(cg_path, O_WRONLY); + if (fd < 0) { + return -1; + } + + if (write(fd, pid_str, strlen(pid_str)) < 0) { + ret = -1; + } + + close(fd); + return ret; +} + +int run_vzct(char *cgv2, char *cgve, int ctid, int testid) +{ + char *stack; + char *stack_top; + pid_t pid; + int flags = CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | + CLONE_NEWPID | CLONE_NEWUSER | CLONE_NEWNET | + CLONE_NEWCGROUP | SIGCHLD; + struct fargs args = { + .cgve = cgve, + .ctid = ctid, + .test = testid, + }; + int status; + int ret; + + stack = malloc(__STACK_SIZE); + if (!stack) + return -1; + + ret = enter_cgroup(cgv2, ctid); + if (ret < 0) + return -1; + ret = enter_cgroup(cgve, ctid); + if (ret < 0) + return -1; + + stack_top = stack + __STACK_SIZE; + pid = clone(child_func, stack_top, flags, &args); + if (pid < 0) { + return -1; + } + + ret = waitpid(pid, &status, 0); + if (ret < 0) + return -1; + ret = -1; + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + } + enter_cgroup(cgv2, 0); + enter_cgroup(cgve, 0); + + return ret; +} + +FILE *open_dmesg(void) +{ + int fd; + FILE *fdmesg; + char buf[1024]; + + fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); + if (fd < 0) + return NULL; + fdmesg = fdopen(fd, "r"); + if (!fdmesg) + return NULL; + + while (fgets(buf, sizeof(buf), fdmesg)) ; + return fdmesg; +} + +int restore_param(const char *path, int val) +{ + int fd; + char str[16]; + + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + snprintf(str, sizeof(str), "%d", val); + write(fd, str, strlen(str)); + close(fd); + + return 0; +} + +int set_param(const char *path, int val, int *old) +{ + FILE *f; + + f = fopen(path, "r"); + if (!f) + return -1; + if (fscanf(f, "%u", old) != 1) { + fclose(f); + return -1; + } + fclose(f); + if (*old == val) + return 0; + return restore_param(path, val); +} + +TEST_F(ve_printk, ve_log) +{ + int ret; + FILE *fdmesg; + char buf[1024]; + int old; + + ASSERT_EQ(set_param("/proc/sys/debug/exception-trace", 1, &old), 0); + fdmesg = open_dmesg(); + ASSERT_NE(fdmesg, NULL); + + ret = run_vzct(self->cgv2_path, self->cgve_path, self->ctid, TEST_LOG); + ASSERT_EQ(ret, 0); + + while (fgets(buf, sizeof(buf), fdmesg)) { + ASSERT_EQ(strstr(buf, SEGFAULT_PROG) && strstr(buf, "segfault at"), false); + EXPECT_EQ(strstr(buf, "Code: "), NULL); + } + fclose(fdmesg); + restore_param("/proc/sys/debug/exception-trace", old); +} + +TEST_F(ve_printk, ve0_log) +{ + int ret; + FILE *fdmesg; + char buf[1024]; + int ex_trace; + + ASSERT_EQ(set_param("/proc/sys/debug/exception-trace", 1, &ex_trace), 0); + fdmesg = open_dmesg(); + ASSERT_NE(fdmesg, NULL); + + ret = run_vzct(self->cgv2_path, self->cgve_path, self->ctid, TEST_LOGV0); + ASSERT_EQ(ret, 0); + + while (fgets(buf, sizeof(buf), fdmesg)) { + if (strstr(buf, "traps: test_trap[") && + strstr(buf, " in test_trap[")) { + ret = 1; + break; + } + } + fclose(fdmesg); + restore_param("/proc/sys/debug/exception-trace", ex_trace); + ASSERT_EQ(ret, 1); +} + +TEST_F(ve_printk, ve_log_both) +{ + int ret; + FILE *fdmesg; + char buf[1024]; + int old_ratelimit; + + ASSERT_EQ(set_param("/proc/sys/net/core/message_burst", + TEST_RATELIMIT_BURST, &old_ratelimit), 0); + fdmesg = open_dmesg(); + ASSERT_NE(fdmesg, NULL); + + ret = run_vzct(self->cgv2_path, self->cgve_path, self->ctid, TEST_LOGBOTH); + ASSERT_EQ(ret, 0); + + while (fgets(buf, sizeof(buf), fdmesg)) { + if (strstr(buf, "nf_conntrack table full, dropping packet")) { + ret++; + } + } + fclose(fdmesg); + restore_param("/proc/sys/kernel/printk_ratelimit_burst", old_ratelimit); + ASSERT_EQ(ret, TEST_RATELIMIT_BURST); +} + +TEST_F(ve_printk, ve_printk_ratelimited) +{ + int ret; + FILE *fdmesg; + char buf[1024]; + int old_ratelimit_burst; + int old_ratelimit; + + ASSERT_EQ(set_param("/proc/sys/net/core/message_burst", + TEST_RATELIMIT_BURST, &old_ratelimit_burst), 0); + ASSERT_EQ(set_param("/proc/sys/net/core/message_cost", + TEST_RATELIMIT, &old_ratelimit), 0); + fdmesg = open_dmesg(); + ASSERT_NE(fdmesg, NULL); + + ret = run_vzct(self->cgv2_path, self->cgve_path, self->ctid, TEST_PR_RATELIMIT); + ASSERT_EQ(ret, 0); + + while (fgets(buf, sizeof(buf), fdmesg)) { + ASSERT_EQ(strstr(buf, "reject_tg_check") && strstr(buf, "callbacks suppressed"), false); + ASSERT_EQ(strstr(buf, "TCP_RESET invalid for non-tcp"), NULL); + } + fclose(fdmesg); + restore_param("/proc/sys/kernel/printk_ratelimit_burst", old_ratelimit_burst); + restore_param("/proc/sys/net/core/message_cost", old_ratelimit); +} + +TEST_F(ve_printk, ve_printk_overflow) +{ + int ret; + int ex_trace; + + ASSERT_EQ(set_param("/proc/sys/debug/exception-trace", 1, &ex_trace), 0); + + ret = run_vzct(self->cgv2_path, self->cgve_path, self->ctid, TEST_OVERFLOW); + ASSERT_EQ(ret, 0); + restore_param("/proc/sys/debug/exception-trace", ex_trace); + +} + +TEST_HARNESS_MAIN -- 2.43.0 _______________________________________________ Devel mailing list [email protected] https://lists.openvz.org/mailman/listinfo/devel
