lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ