[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <202103172105.F88F6745@keescook>
Date: Wed, 17 Mar 2021 21:08:17 -0700
From: Kees Cook <keescook@...omium.org>
To: John Wood <john.wood@....com>
Cc: Jann Horn <jannh@...gle.com>, Randy Dunlap <rdunlap@...radead.org>,
Jonathan Corbet <corbet@....net>,
James Morris <jmorris@...ei.org>,
Shuah Khan <shuah@...nel.org>,
"Serge E. Hallyn" <serge@...lyn.com>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Andi Kleen <ak@...ux.intel.com>,
kernel test robot <oliver.sang@...el.com>,
linux-doc@...r.kernel.org, linux-kernel@...r.kernel.org,
linux-security-module@...r.kernel.org,
linux-kselftest@...r.kernel.org,
kernel-hardening@...ts.openwall.com
Subject: Re: [PATCH v6 6/8] selftests/brute: Add tests for the Brute LSM
On Sun, Mar 07, 2021 at 12:30:29PM +0100, John Wood wrote:
> Add tests to check the brute LSM functionality and cover fork/exec brute
> force attacks crossing the following privilege boundaries:
>
> 1.- setuid process
> 2.- privilege changes
> 3.- network to local
>
> Also, as a first step check that fork/exec brute force attacks without
> crossing any privilege boundariy already commented doesn't trigger the
> detection and mitigation stage.
>
> All the fork brute force attacks are carried out via the "exec" app to
> avoid the triggering of the "brute" LSM over the shell script running
> the tests.
>
> Signed-off-by: John Wood <john.wood@....com>
Yay tests!
> ---
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/brute/.gitignore | 2 +
> tools/testing/selftests/brute/Makefile | 5 +
> tools/testing/selftests/brute/config | 1 +
> tools/testing/selftests/brute/exec.c | 44 ++
> tools/testing/selftests/brute/test.c | 507 +++++++++++++++++++++++
> tools/testing/selftests/brute/test.sh | 226 ++++++++++
> 7 files changed, 786 insertions(+)
> create mode 100644 tools/testing/selftests/brute/.gitignore
> create mode 100644 tools/testing/selftests/brute/Makefile
> create mode 100644 tools/testing/selftests/brute/config
> create mode 100644 tools/testing/selftests/brute/exec.c
> create mode 100644 tools/testing/selftests/brute/test.c
> create mode 100755 tools/testing/selftests/brute/test.sh
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 6c575cf34a71..d4cf9e1c0a6d 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -2,6 +2,7 @@
> TARGETS = arm64
> TARGETS += bpf
> TARGETS += breakpoints
> +TARGETS += brute
> TARGETS += capabilities
> TARGETS += cgroup
> TARGETS += clone3
> diff --git a/tools/testing/selftests/brute/.gitignore b/tools/testing/selftests/brute/.gitignore
> new file mode 100644
> index 000000000000..1ccc45251a1b
> --- /dev/null
> +++ b/tools/testing/selftests/brute/.gitignore
> @@ -0,0 +1,2 @@
> +exec
> +test
> diff --git a/tools/testing/selftests/brute/Makefile b/tools/testing/selftests/brute/Makefile
> new file mode 100644
> index 000000000000..52662d0b484c
> --- /dev/null
> +++ b/tools/testing/selftests/brute/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +CFLAGS += -Wall -O2
> +TEST_PROGS := test.sh
> +TEST_GEN_FILES := exec test
> +include ../lib.mk
> diff --git a/tools/testing/selftests/brute/config b/tools/testing/selftests/brute/config
> new file mode 100644
> index 000000000000..3587b7bf6c23
> --- /dev/null
> +++ b/tools/testing/selftests/brute/config
> @@ -0,0 +1 @@
> +CONFIG_SECURITY_FORK_BRUTE=y
> diff --git a/tools/testing/selftests/brute/exec.c b/tools/testing/selftests/brute/exec.c
> new file mode 100644
> index 000000000000..1bbe72f6e4bd
> --- /dev/null
> +++ b/tools/testing/selftests/brute/exec.c
> @@ -0,0 +1,44 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <libgen.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +static __attribute__((noreturn)) void error_failure(const char *message)
> +{
> + perror(message);
> + exit(EXIT_FAILURE);
> +}
> +
> +#define PROG_NAME basename(argv[0])
> +
> +int main(int argc, char **argv)
> +{
> + pid_t pid;
> + int status;
> +
> + if (argc < 2) {
> + printf("Usage: %s <EXECUTABLE>\n", PROG_NAME);
> + exit(EXIT_FAILURE);
> + }
> +
> + pid = fork();
> + if (pid < 0)
> + error_failure("fork");
> +
> + /* Child process */
> + if (!pid) {
> + execve(argv[1], &argv[1], NULL);
> + error_failure("execve");
> + }
> +
> + /* Parent process */
> + pid = waitpid(pid, &status, 0);
> + if (pid < 0)
> + error_failure("waitpid");
> +
> + return EXIT_SUCCESS;
> +}
> diff --git a/tools/testing/selftests/brute/test.c b/tools/testing/selftests/brute/test.c
> new file mode 100644
> index 000000000000..44c32f446dca
> --- /dev/null
> +++ b/tools/testing/selftests/brute/test.c
> @@ -0,0 +1,507 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <libgen.h>
> +#include <pwd.h>
> +#include <signal.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/socket.h>
> +#include <sys/time.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +static const char *message = "message";
> +
> +enum mode {
> + MODE_NONE,
> + MODE_CRASH,
> + MODE_SERVER_CRASH,
> + MODE_CLIENT,
> +};
> +
> +enum crash_after {
> + CRASH_AFTER_NONE,
> + CRASH_AFTER_FORK,
> + CRASH_AFTER_EXEC,
> +};
> +
> +enum signal_from {
> + SIGNAL_FROM_NONE,
> + SIGNAL_FROM_USER,
> + SIGNAL_FROM_KERNEL,
> +};
> +
> +struct args {
> + uint32_t ip;
> + uint16_t port;
> + int counter;
> + long timeout;
> + enum mode mode;
> + enum crash_after crash_after;
> + enum signal_from signal_from;
> + unsigned char has_counter : 1;
> + unsigned char has_change_priv : 1;
> + unsigned char has_ip : 1;
> + unsigned char has_port : 1;
> + unsigned char has_timeout : 1;
> +};
> +
> +#define OPT_STRING "hm:c:s:n:Ca:p:t:"
> +
> +static void usage(const char *prog)
> +{
> + printf("Usage: %s <OPTIONS>\n", prog);
> + printf("OPTIONS:\n");
> + printf(" -h: Show this help and exit. Optional.\n");
> + printf(" -m (crash | server_crash | client): Mode. Required.\n");
> + printf("Options for crash mode:\n");
> + printf(" -c (fork | exec): Crash after. Optional.\n");
> + printf(" -s (user | kernel): Signal from. Required.\n");
> + printf(" -n counter: Number of crashes.\n");
> + printf(" Required if the option -c is used.\n");
> + printf(" Not used without the option -c.\n");
> + printf(" Range from 1 to INT_MAX.\n");
> + printf(" -C: Change privileges before crash. Optional.\n");
> + printf("Options for server_crash mode:\n");
> + printf(" -a ip: Ip v4 address to accept. Required.\n");
> + printf(" -p port: Port number. Required.\n");
> + printf(" Range from 1 to UINT16_MAX.\n");
> + printf(" -t secs: Accept timeout. Required.\n");
> + printf(" Range from 1 to LONG_MAX.\n");
> + printf(" -c (fork | exec): Crash after. Required.\n");
> + printf(" -s (user | kernel): Signal from. Required.\n");
> + printf(" -n counter: Number of crashes. Required.\n");
> + printf(" Range from 1 to INT_MAX.\n");
> + printf("Options for client mode:\n");
> + printf(" -a ip: Ip v4 address to connect. Required.\n");
> + printf(" -p port: Port number. Required.\n");
> + printf(" Range from 1 to UINT16_MAX.\n");
> + printf(" -t secs: Connect timeout. Required.\n");
> + printf(" Range from 1 to LONG_MAX.\n");
> +}
> +
> +static __attribute__((noreturn)) void info_failure(const char *message,
> + const char *prog)
> +{
> + printf("%s\n", message);
> + usage(prog);
> + exit(EXIT_FAILURE);
> +}
> +
> +static enum mode get_mode(const char *text, const char *prog)
> +{
> + if (!strcmp(text, "crash"))
> + return MODE_CRASH;
> +
> + if (!strcmp(text, "server_crash"))
> + return MODE_SERVER_CRASH;
> +
> + if (!strcmp(text, "client"))
> + return MODE_CLIENT;
> +
> + info_failure("Invalid mode option [-m].", prog);
> +}
> +
> +static enum crash_after get_crash_after(const char *text, const char *prog)
> +{
> + if (!strcmp(text, "fork"))
> + return CRASH_AFTER_FORK;
> +
> + if (!strcmp(text, "exec"))
> + return CRASH_AFTER_EXEC;
> +
> + info_failure("Invalid crash after option [-c].", prog);
> +}
> +
> +static enum signal_from get_signal_from(const char *text, const char *prog)
> +{
> + if (!strcmp(text, "user"))
> + return SIGNAL_FROM_USER;
> +
> + if (!strcmp(text, "kernel"))
> + return SIGNAL_FROM_KERNEL;
> +
> + info_failure("Invalid signal from option [-s]", prog);
> +}
> +
> +static int get_counter(const char *text, const char *prog)
> +{
> + int counter;
> +
> + counter = atoi(text);
> + if (counter > 0)
> + return counter;
> +
> + info_failure("Invalid counter option [-n].", prog);
> +}
> +
> +static __attribute__((noreturn)) void error_failure(const char *message)
> +{
> + perror(message);
> + exit(EXIT_FAILURE);
> +}
> +
> +static uint32_t get_ip(const char *text, const char *prog)
> +{
> + int ret;
> + uint32_t ip;
> +
> + ret = inet_pton(AF_INET, text, &ip);
> + if (!ret)
> + info_failure("Invalid ip option [-a].", prog);
> + else if (ret < 0)
> + error_failure("inet_pton");
> +
> + return ip;
> +}
> +
> +static uint16_t get_port(const char *text, const char *prog)
> +{
> + long port;
> +
> + port = atol(text);
> + if ((port > 0) && (port <= UINT16_MAX))
> + return htons(port);
> +
> + info_failure("Invalid port option [-p].", prog);
> +}
> +
> +static long get_timeout(const char *text, const char *prog)
> +{
> + long timeout;
> +
> + timeout = atol(text);
> + if (timeout > 0)
> + return timeout;
> +
> + info_failure("Invalid timeout option [-t].", prog);
> +}
> +
> +static void check_args(const struct args *args, const char *prog)
> +{
> + if (args->mode == MODE_CRASH && args->crash_after != CRASH_AFTER_NONE &&
> + args->signal_from != SIGNAL_FROM_NONE && args->has_counter &&
> + !args->has_ip && !args->has_port && !args->has_timeout)
> + return;
> +
> + if (args->mode == MODE_CRASH && args->signal_from != SIGNAL_FROM_NONE &&
> + args->crash_after == CRASH_AFTER_NONE && !args->has_counter &&
> + !args->has_ip && !args->has_port && !args->has_timeout)
> + return;
> +
> + if (args->mode == MODE_SERVER_CRASH && args->has_ip && args->has_port &&
> + args->has_timeout && args->crash_after != CRASH_AFTER_NONE &&
> + args->signal_from != SIGNAL_FROM_NONE && args->has_counter &&
> + !args->has_change_priv)
> + return;
> +
> + if (args->mode == MODE_CLIENT && args->has_ip && args->has_port &&
> + args->has_timeout && args->crash_after == CRASH_AFTER_NONE &&
> + args->signal_from == SIGNAL_FROM_NONE && !args->has_counter &&
> + !args->has_change_priv)
> + return;
> +
> + info_failure("Invalid use of options.", prog);
> +}
> +
> +static uid_t get_non_root_uid(void)
> +{
> + struct passwd *pwent;
> + uid_t uid;
> +
> + while (true) {
> + errno = 0;
> + pwent = getpwent();
> + if (!pwent) {
> + if (errno) {
> + perror("getpwent");
> + endpwent();
> + exit(EXIT_FAILURE);
> + }
> + break;
> + }
> +
> + if (pwent->pw_uid) {
> + uid = pwent->pw_uid;
> + endpwent();
> + return uid;
> + }
> + }
> +
> + endpwent();
> + printf("A user different of root is needed.\n");
> + exit(EXIT_FAILURE);
> +}
> +
> +static inline void do_sigsegv(void)
> +{
> + int *p = NULL;
> + *p = 0;
> +}
> +
> +static void do_sigkill(void)
> +{
> + int ret;
> +
> + ret = kill(getpid(), SIGKILL);
> + if (ret)
> + error_failure("kill");
> +}
> +
> +static void crash(enum signal_from signal_from, bool change_priv)
> +{
> + int ret;
> +
> + if (change_priv) {
> + ret = setuid(get_non_root_uid());
> + if (ret)
> + error_failure("setuid");
> + }
> +
> + if (signal_from == SIGNAL_FROM_KERNEL)
> + do_sigsegv();
> +
> + do_sigkill();
> +}
> +
> +static void execve_crash(char *const argv[])
> +{
> + execve(argv[0], argv, NULL);
> + error_failure("execve");
> +}
> +
> +static void exec_crash_user(void)
> +{
> + char *const argv[] = {
> + "./test", "-m", "crash", "-s", "user", NULL,
> + };
> +
> + execve_crash(argv);
> +}
> +
> +static void exec_crash_user_change_priv(void)
> +{
> + char *const argv[] = {
> + "./test", "-m", "crash", "-s", "user", "-C", NULL,
> + };
> +
> + execve_crash(argv);
> +}
> +
> +static void exec_crash_kernel(void)
> +{
> + char *const argv[] = {
> + "./test", "-m", "crash", "-s", "kernel", NULL,
> + };
> +
> + execve_crash(argv);
> +}
> +
> +static void exec_crash_kernel_change_priv(void)
> +{
> + char *const argv[] = {
> + "./test", "-m", "crash", "-s", "kernel", "-C", NULL,
> + };
> +
> + execve_crash(argv);
> +}
> +
> +static void exec_crash(enum signal_from signal_from, bool change_priv)
> +{
> + if (signal_from == SIGNAL_FROM_USER && !change_priv)
> + exec_crash_user();
> + if (signal_from == SIGNAL_FROM_USER && change_priv)
> + exec_crash_user_change_priv();
> + if (signal_from == SIGNAL_FROM_KERNEL && !change_priv)
> + exec_crash_kernel();
> + if (signal_from == SIGNAL_FROM_KERNEL && change_priv)
> + exec_crash_kernel_change_priv();
> +}
> +
> +static void do_crash(enum crash_after crash_after, enum signal_from signal_from,
> + int counter, bool change_priv)
> +{
> + pid_t pid;
> + int status;
> +
> + if (crash_after == CRASH_AFTER_NONE)
> + crash(signal_from, change_priv);
> +
> + while (counter > 0) {
> + pid = fork();
> + if (pid < 0)
> + error_failure("fork");
> +
> + /* Child process */
> + if (!pid) {
> + if (crash_after == CRASH_AFTER_FORK)
> + crash(signal_from, change_priv);
> +
> + exec_crash(signal_from, change_priv);
> + }
> +
> + /* Parent process */
> + counter -= 1;
> + pid = waitpid(pid, &status, 0);
> + if (pid < 0)
> + error_failure("waitpid");
> + }
> +}
> +
> +static __attribute__((noreturn)) void error_close_failure(const char *message,
> + int fd)
> +{
> + perror(message);
> + close(fd);
> + exit(EXIT_FAILURE);
> +}
> +
> +static void do_server(uint32_t ip, uint16_t port, long accept_timeout)
> +{
> + int sockfd;
> + int ret;
> + struct sockaddr_in address;
> + struct timeval timeout;
> + int newsockfd;
> +
> + sockfd = socket(AF_INET, SOCK_STREAM, 0);
> + if (sockfd < 0)
> + error_failure("socket");
> +
> + address.sin_family = AF_INET;
> + address.sin_addr.s_addr = ip;
> + address.sin_port = port;
> +
> + ret = bind(sockfd, (const struct sockaddr *)&address, sizeof(address));
> + if (ret)
> + error_close_failure("bind", sockfd);
> +
> + ret = listen(sockfd, 1);
> + if (ret)
> + error_close_failure("listen", sockfd);
> +
> + timeout.tv_sec = accept_timeout;
> + timeout.tv_usec = 0;
> + ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,
> + (const struct timeval *)&timeout, sizeof(timeout));
> + if (ret)
> + error_close_failure("setsockopt", sockfd);
> +
> + newsockfd = accept(sockfd, NULL, NULL);
> + if (newsockfd < 0)
> + error_close_failure("accept", sockfd);
> +
> + close(sockfd);
> + close(newsockfd);
> +}
> +
> +static void do_client(uint32_t ip, uint16_t port, long connect_timeout)
> +{
> + int sockfd;
> + int ret;
> + struct timeval timeout;
> + struct sockaddr_in address;
> +
> + sockfd = socket(AF_INET, SOCK_STREAM, 0);
> + if (sockfd < 0)
> + error_failure("socket");
> +
> + timeout.tv_sec = connect_timeout;
> + timeout.tv_usec = 0;
> + ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO,
> + (const struct timeval *)&timeout, sizeof(timeout));
> + if (ret)
> + error_close_failure("setsockopt", sockfd);
> +
> + address.sin_family = AF_INET;
> + address.sin_addr.s_addr = ip;
> + address.sin_port = port;
> +
> + ret = connect(sockfd, (const struct sockaddr *)&address,
> + sizeof(address));
> + if (ret)
> + error_close_failure("connect", sockfd);
> +
> + ret = write(sockfd, message, strlen(message));
> + if (ret < 0)
> + error_close_failure("write", sockfd);
> +
> + close(sockfd);
> +}
> +
> +#define PROG_NAME basename(argv[0])
> +
> +int main(int argc, char **argv)
> +{
> + int opt;
> + struct args args = {
> + .mode = MODE_NONE,
> + .crash_after = CRASH_AFTER_NONE,
> + .signal_from = SIGNAL_FROM_NONE,
> + .has_counter = false,
> + .has_change_priv = false,
> + .has_ip = false,
> + .has_port = false,
> + .has_timeout = false,
> + };
> +
> + while ((opt = getopt(argc, argv, OPT_STRING)) != -1) {
> + switch (opt) {
> + case 'h':
> + usage(PROG_NAME);
> + return EXIT_SUCCESS;
> + case 'm':
> + args.mode = get_mode(optarg, PROG_NAME);
> + break;
> + case 'c':
> + args.crash_after = get_crash_after(optarg, PROG_NAME);
> + break;
> + case 's':
> + args.signal_from = get_signal_from(optarg, PROG_NAME);
> + break;
> + case 'n':
> + args.counter = get_counter(optarg, PROG_NAME);
> + args.has_counter = true;
> + break;
> + case 'C':
> + args.has_change_priv = true;
> + break;
> + case 'a':
> + args.ip = get_ip(optarg, PROG_NAME);
> + args.has_ip = true;
> + break;
> + case 'p':
> + args.port = get_port(optarg, PROG_NAME);
> + args.has_port = true;
> + break;
> + case 't':
> + args.timeout = get_timeout(optarg, PROG_NAME);
> + args.has_timeout = true;
> + break;
> + default:
> + usage(PROG_NAME);
> + return EXIT_FAILURE;
> + }
> + }
> +
> + check_args(&args, PROG_NAME);
> +
> + if (args.mode == MODE_CRASH) {
> + do_crash(args.crash_after, args.signal_from, args.counter,
> + args.has_change_priv);
> + } else if (args.mode == MODE_SERVER_CRASH) {
> + do_server(args.ip, args.port, args.timeout);
> + do_crash(args.crash_after, args.signal_from, args.counter,
> + false);
> + } else if (args.mode == MODE_CLIENT) {
> + do_client(args.ip, args.port, args.timeout);
> + }
> +
> + return EXIT_SUCCESS;
> +}
> diff --git a/tools/testing/selftests/brute/test.sh b/tools/testing/selftests/brute/test.sh
> new file mode 100755
> index 000000000000..f53f26ae5b96
> --- /dev/null
> +++ b/tools/testing/selftests/brute/test.sh
> @@ -0,0 +1,226 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0
> +
> +TCID="test.sh"
> +
> +KSFT_PASS=0
> +KSFT_FAIL=1
> +KSFT_SKIP=4
> +
> +errno=$KSFT_PASS
> +
> +check_root()
> +{
> + local uid=$(id -u)
> + if [ $uid -ne 0 ]; then
> + echo $TCID: must be run as root >&2
> + exit $KSFT_SKIP
> + fi
> +}
> +
> +count_fork_matches()
> +{
> + dmesg | grep "brute: Fork brute force attack detected" | wc -l
This may be unstable if the dmesg scrolls past, etc. See how
lkdtm/run.sh handles this with a temp file and "comm".
> +}
> +
> +assert_equal()
> +{
> + local val1=$1
> + local val2=$2
> +
> + if [ $val1 -eq $val2 ]; then
> + echo "$TCID: $message [PASS]"
> + else
> + echo "$TCID: $message [FAIL]"
> + errno=$KSFT_FAIL
> + fi
> +}
> +
> +test_fork_user()
> +{
> + COUNTER=20
> +
> + old_count=$(count_fork_matches)
> + ./exec test -m crash -c fork -s user -n $COUNTER
> + new_count=$(count_fork_matches)
> +
> + message="Fork attack (user signals, no bounds crossed)"
> + assert_equal $old_count $new_count
> +}
> +
> +test_fork_kernel()
> +{
> + old_count=$(count_fork_matches)
> + ./exec test -m crash -c fork -s kernel -n $COUNTER
> + new_count=$(count_fork_matches)
> +
> + message="Fork attack (kernel signals, no bounds crossed)"
> + assert_equal $old_count $new_count
> +}
> +
> +count_exec_matches()
> +{
> + dmesg | grep "brute: Exec brute force attack detected" | wc -l
> +}
> +
> +test_exec_user()
> +{
> + old_count=$(count_exec_matches)
> + ./test -m crash -c exec -s user -n $COUNTER
> + new_count=$(count_exec_matches)
> +
> + message="Exec attack (user signals, no bounds crossed)"
> + assert_equal $old_count $new_count
> +}
> +
> +test_exec_kernel()
> +{
> + old_count=$(count_exec_matches)
> + ./test -m crash -c exec -s kernel -n $COUNTER
> + new_count=$(count_exec_matches)
> +
> + message="Exec attack (kernel signals, no bounds crossed)"
> + assert_equal $old_count $new_count
> +}
> +
> +assert_not_equal()
> +{
> + local val1=$1
> + local val2=$2
> +
> + if [ $val1 -ne $val2 ]; then
> + echo $TCID: $message [PASS]
> + else
> + echo $TCID: $message [FAIL]
> + errno=$KSFT_FAIL
> + fi
> +}
> +
> +test_fork_kernel_setuid()
> +{
> + old_count=$(count_fork_matches)
> + chmod u+s test
> + ./exec test -m crash -c fork -s kernel -n $COUNTER
> + chmod u-s test
> + new_count=$(count_fork_matches)
> +
> + message="Fork attack (kernel signals, setuid binary)"
> + assert_not_equal $old_count $new_count
> +}
> +
> +test_exec_kernel_setuid()
> +{
> + old_count=$(count_exec_matches)
> + chmod u+s test
> + ./test -m crash -c exec -s kernel -n $COUNTER
> + chmod u-s test
> + new_count=$(count_exec_matches)
> +
> + message="Exec attack (kernel signals, setuid binary)"
> + assert_not_equal $old_count $new_count
> +}
> +
> +test_fork_kernel_change_priv()
> +{
> + old_count=$(count_fork_matches)
> + ./exec test -m crash -c fork -s kernel -n $COUNTER -C
> + new_count=$(count_fork_matches)
> +
> + message="Fork attack (kernel signals, change privileges)"
> + assert_not_equal $old_count $new_count
> +}
> +
> +test_exec_kernel_change_priv()
> +{
> + old_count=$(count_exec_matches)
> + ./test -m crash -c exec -s kernel -n $COUNTER -C
> + new_count=$(count_exec_matches)
> +
> + message="Exec attack (kernel signals, change privileges)"
> + assert_not_equal $old_count $new_count
> +}
> +
> +network_ns_setup()
> +{
> + local vnet_name=$1
> + local veth_name=$2
> + local ip_src=$3
> + local ip_dst=$4
> +
> + ip netns add $vnet_name
> + ip link set $veth_name netns $vnet_name
> + ip -n $vnet_name addr add $ip_src/24 dev $veth_name
> + ip -n $vnet_name link set $veth_name up
> + ip -n $vnet_name route add $ip_dst/24 dev $veth_name
> +}
> +
> +network_setup()
> +{
> + VETH0_NAME=veth0
> + VNET0_NAME=vnet0
> + VNET0_IP=10.0.1.0
> + VETH1_NAME=veth1
> + VNET1_NAME=vnet1
> + VNET1_IP=10.0.2.0
> +
> + ip link add $VETH0_NAME type veth peer name $VETH1_NAME
> + network_ns_setup $VNET0_NAME $VETH0_NAME $VNET0_IP $VNET1_IP
> + network_ns_setup $VNET1_NAME $VETH1_NAME $VNET1_IP $VNET0_IP
> +}
> +
> +test_fork_kernel_network_to_local()
> +{
> + INADDR_ANY=0.0.0.0
> + PORT=65535
> + TIMEOUT=5
> +
> + old_count=$(count_fork_matches)
> + ip netns exec $VNET0_NAME ./exec test -m server_crash -a $INADDR_ANY \
> + -p $PORT -t $TIMEOUT -c fork -s kernel -n $COUNTER &
> + sleep 1
> + ip netns exec $VNET1_NAME ./test -m client -a $VNET0_IP -p $PORT \
> + -t $TIMEOUT
> + sleep 1
> + new_count=$(count_fork_matches)
> +
> + message="Fork attack (kernel signals, network to local)"
> + assert_not_equal $old_count $new_count
> +}
> +
> +test_exec_kernel_network_to_local()
> +{
> + old_count=$(count_exec_matches)
> + ip netns exec $VNET0_NAME ./test -m server_crash -a $INADDR_ANY \
> + -p $PORT -t $TIMEOUT -c exec -s kernel -n $COUNTER &
> + sleep 1
> + ip netns exec $VNET1_NAME ./test -m client -a $VNET0_IP -p $PORT \
> + -t $TIMEOUT
> + sleep 1
> + new_count=$(count_exec_matches)
> +
> + message="Exec attack (kernel signals, network to local)"
> + assert_not_equal $old_count $new_count
> +}
> +
> +network_cleanup()
> +{
> + ip netns del $VNET0_NAME >/dev/null 2>&1
> + ip netns del $VNET1_NAME >/dev/null 2>&1
> + ip link delete $VETH0_NAME >/dev/null 2>&1
> + ip link delete $VETH1_NAME >/dev/null 2>&1
> +}
> +
> +check_root
> +test_fork_user
> +test_fork_kernel
> +test_exec_user
> +test_exec_kernel
> +test_fork_kernel_setuid
> +test_exec_kernel_setuid
> +test_fork_kernel_change_priv
> +test_exec_kernel_change_priv
> +network_setup
> +test_fork_kernel_network_to_local
> +test_exec_kernel_network_to_local
> +network_cleanup
> +exit $errno
> --
> 2.25.1
>
--
Kees Cook
Powered by blists - more mailing lists