From patchwork Sun Mar 7 11:30:29 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Wood X-Patchwork-Id: 395256 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A298FC433E9 for ; Sun, 7 Mar 2021 13:43:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6D28465107 for ; Sun, 7 Mar 2021 13:43:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231393AbhCGNnP (ORCPT ); Sun, 7 Mar 2021 08:43:15 -0500 Received: from mout.gmx.net ([212.227.17.21]:48565 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230272AbhCGNm5 (ORCPT ); Sun, 7 Mar 2021 08:42:57 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1615124521; bh=yBo7hGCSzN1cx8Cw6x+gmnMXafaZYnF6cZpWUcCnFjw=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:In-Reply-To:References; b=e+VGYaPh1V8utWRjSzAaZf7vesjkpi4NR9TeuP1sdttN0YSFAQkir2NPMMX/60t4D hDf+2UujimHyf+ipspGXkl5S6sGB95CBEF9sPblz9ALIYR1E3GtABSRCR51LTp+tLH gYuX+5ddOidCaP2WSSePV1/kpKe0lZK24pvj0VvU= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from localhost.localdomain ([83.52.229.153]) by mail.gmx.net (mrgmx104 [212.227.17.174]) with ESMTPSA (Nemesis) id 1MY6Cb-1lFokG212D-00YNOi; Sun, 07 Mar 2021 14:42:01 +0100 From: John Wood To: Kees Cook , Jann Horn , Randy Dunlap , Jonathan Corbet , James Morris , Shuah Khan Cc: John Wood , "Serge E. Hallyn" , Greg Kroah-Hartman , Andi Kleen , kernel test robot , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, kernel-hardening@lists.openwall.com Subject: [PATCH v6 6/8] selftests/brute: Add tests for the Brute LSM Date: Sun, 7 Mar 2021 12:30:29 +0100 Message-Id: <20210307113031.11671-7-john.wood@gmx.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210307113031.11671-1-john.wood@gmx.com> References: <20210307113031.11671-1-john.wood@gmx.com> MIME-Version: 1.0 X-Provags-ID: V03:K1:mvsxY7Ykuor4unsAnNlbbu6SaElApBmWwMDgxWVYAVGrNsbkNwB twwjS+0aY/v1rceXHNaW+TVHs5pSECLlSTzG71xBBNgC8cszd9QuVtnhlEYztEKOr8Jv58Y S09yd+BrusjdSJgHyfJ9+adidmb5SWl7nbb9f0THB2MSBH4yTBcY4SHYAt6nMDKQkinXd8v Ojg/ExjwXqTBMQhRwMAMA== X-UI-Out-Filterresults: notjunk:1; V03:K0:0sqsvWMK2H0=:cdWoY0iZPtfGybVenbtWtp 7vg6VPWaMTbFdshKHt0gCwWOlx0alCoMGgqpVufZkWNeJhL9QG5PkC6lCF4AMSIy4Eol8phVE nvqXAYnly6a/QooFeGl5zlmVBBcJrcProYKwjadp/Ie5ybJtCSyM54qYieXCXLcRmgRJV17U6 8kBq4pyyCAyDlCDm6qHo6Cvt8g+BPwHzR/Gkm3QKidZeAOAzOdJQEcA43Ftr6Bb/kS2wugCuv qei/MQDdBcPFh5BGIv3fB5ROYZS4/K1hiy21jG2fNLTONu5yohlCl5rFkKaqtsSZdTPbiamq9 9VaLFCEZYWQrLFhohmrxI8lQIB/qxDwbDCEX7AtoyMxzBZSVK8licraT22KpAtT5r2Dx6w+7c pobaXDv/pBfe+iUt6DT4/jSchMotJT2+I39iPHzPYWcAIaBXRdf5CtA+lTAO/zKPSyC/u25JR mIZndYNWL3kHqVur/ywtQOotUTQKgGbkJo1n9B+GR5ctzM51pD72/ugp/MaFqOZGzGKNspSZI 8LmoCEtqgRQyBmTrdoxvVYjPROrx5Qw8t/Pv66JiNNozD+W2pKvWLtoW7gvK3uQnwQJNzVSr3 KEf8xZ7BUp2zEdblDC+MHdkLJUCBfTp0wPu/8b/Gpb1CluwHKnP+0CW0BHU6FTervjIpxIDHg XLBeE7e9wCZeGsg3ikPs+JJv80tt9nCqo1xQJqhV1Nbqpq9Z7KOzOtjHvPUK5iGMmqeW6xWo2 VJh5GSL9VjSzeewb3/26B80Kakiy2jkf3PJnD2djt6+/m1QbbbCUM6YJxnlwB6RdYkP0zQn2b TzhBEyebj1Y0xOBIjNt6HEETo9JHwfjUpw0mFW8XSV2NsNrCcljceATmeeEMJp6FARGc053gN PjVAnGg9YVQ0ttfUAnqA== Precedence: bulk List-ID: X-Mailing-List: linux-kselftest@vger.kernel.org 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 --- 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 -- 2.25.1 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 +#include +#include +#include +#include +#include + +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 \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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 \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 +} + +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