Commit 71ae5fc8 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'linux-kselftest-5.2-rc1' of...

Merge tag 'linux-kselftest-5.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull Kselftest updates from Shuah Khan:

 - fixes to seccomp test, and kselftest framework

 - cleanups to remove duplicate header defines

 - fixes to efivarfs "make clean" target

 - cgroup cleanup path

 - Moving the IMA kexec_load selftest to selftests/kexec work from Mimi
   Johar and Petr Vorel

 - A framework to kselftest for writing kernel test modules addition
   from Tobin C. Harding

* tag 'linux-kselftest-5.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: (29 commits)
  selftests: build and run gpio when output directory is the src dir
  selftests/ipc: Fix msgque compiler warnings
  selftests/efivarfs: clean up test files from test_create*()
  selftests: fix headers_install circular dependency
  selftests/kexec: update get_secureboot_mode
  selftests/kexec: make kexec_load test independent of IMA being enabled
  selftests/kexec: check kexec_load and kexec_file_load are enabled
  selftests/kexec: Add missing '=y' to config options
  selftests/kexec: kexec_file_load syscall test
  selftests/kexec: define "require_root_privileges"
  selftests/kexec: define common logging functions
  selftests/kexec: define a set of common functions
  selftests/kexec: cleanup the kexec selftest
  selftests/kexec: move the IMA kexec_load selftest to selftests/kexec
  selftests/harness: Add 30 second timeout per test
  selftests/seccomp: Handle namespace failures gracefully
  selftests: cgroup: fix cleanup path in test_memcg_subtree_control()
  selftests: efivarfs: remove the test_create_read file if it was exist
  rseq/selftests: Adapt number of threads to the number of detected cpus
  lib: Add test module for strscpy_pad
  ...
parents 81ff5d2c d917fb87
...@@ -14,6 +14,10 @@ in safe mode with a limited scope. In limited mode, cpu-hotplug test is ...@@ -14,6 +14,10 @@ in safe mode with a limited scope. In limited mode, cpu-hotplug test is
run on a single cpu as opposed to all hotplug capable cpus, and memory run on a single cpu as opposed to all hotplug capable cpus, and memory
hotplug test is run on 2% of hotplug capable memory instead of 10%. hotplug test is run on 2% of hotplug capable memory instead of 10%.
kselftest runs as a userspace process. Tests that can be written/run in
userspace may wish to use the `Test Harness`_. Tests that need to be
run in kernel space may wish to use a `Test Module`_.
Running the selftests (hotplug tests are run in limited mode) Running the selftests (hotplug tests are run in limited mode)
============================================================= =============================================================
...@@ -161,11 +165,97 @@ Contributing new tests (details) ...@@ -161,11 +165,97 @@ Contributing new tests (details)
e.g: tools/testing/selftests/android/config e.g: tools/testing/selftests/android/config
Test Module
===========
Kselftest tests the kernel from userspace. Sometimes things need
testing from within the kernel, one method of doing this is to create a
test module. We can tie the module into the kselftest framework by
using a shell script test runner. ``kselftest_module.sh`` is designed
to facilitate this process. There is also a header file provided to
assist writing kernel modules that are for use with kselftest:
- ``tools/testing/kselftest/kselftest_module.h``
- ``tools/testing/kselftest/kselftest_module.sh``
How to use
----------
Here we show the typical steps to create a test module and tie it into
kselftest. We use kselftests for lib/ as an example.
1. Create the test module
2. Create the test script that will run (load/unload) the module
e.g. ``tools/testing/selftests/lib/printf.sh``
3. Add line to config file e.g. ``tools/testing/selftests/lib/config``
4. Add test script to makefile e.g. ``tools/testing/selftests/lib/Makefile``
5. Verify it works:
.. code-block:: sh
# Assumes you have booted a fresh build of this kernel tree
cd /path/to/linux/tree
make kselftest-merge
make modules
sudo make modules_install
make TARGETS=lib kselftest
Example Module
--------------
A bare bones test module might look like this:
.. code-block:: c
// SPDX-License-Identifier: GPL-2.0+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include "../tools/testing/selftests/kselftest_module.h"
KSTM_MODULE_GLOBALS();
/*
* Kernel module for testing the foobinator
*/
static int __init test_function()
{
...
}
static void __init selftest(void)
{
KSTM_CHECK_ZERO(do_test_case("", 0));
}
KSTM_MODULE_LOADERS(test_foo);
MODULE_AUTHOR("John Developer <jd@fooman.org>");
MODULE_LICENSE("GPL");
Example test script
-------------------
.. code-block:: sh
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0+
$(dirname $0)/../kselftest_module.sh "foo" test_foo
Test Harness Test Harness
============ ============
The kselftest_harness.h file contains useful helpers to build tests. The tests The kselftest_harness.h file contains useful helpers to build tests. The
from tools/testing/selftests/seccomp/seccomp_bpf.c can be used as example. test harness is for userspace testing, for kernel space testing see `Test
Module`_ above.
The tests from tools/testing/selftests/seccomp/seccomp_bpf.c can be used as
example.
Example Example
------- -------
......
...@@ -31,6 +31,10 @@ size_t strlcpy(char *, const char *, size_t); ...@@ -31,6 +31,10 @@ size_t strlcpy(char *, const char *, size_t);
#ifndef __HAVE_ARCH_STRSCPY #ifndef __HAVE_ARCH_STRSCPY
ssize_t strscpy(char *, const char *, size_t); ssize_t strscpy(char *, const char *, size_t);
#endif #endif
/* Wraps calls to strscpy()/memset(), no arch specific code required */
ssize_t strscpy_pad(char *dest, const char *src, size_t count);
#ifndef __HAVE_ARCH_STRCAT #ifndef __HAVE_ARCH_STRCAT
extern char * strcat(char *, const char *); extern char * strcat(char *, const char *);
#endif #endif
......
...@@ -1769,6 +1769,9 @@ config TEST_HEXDUMP ...@@ -1769,6 +1769,9 @@ config TEST_HEXDUMP
config TEST_STRING_HELPERS config TEST_STRING_HELPERS
tristate "Test functions located in the string_helpers module at runtime" tristate "Test functions located in the string_helpers module at runtime"
config TEST_STRSCPY
tristate "Test strscpy*() family of functions at runtime"
config TEST_KSTRTOX config TEST_KSTRTOX
tristate "Test kstrto*() family of functions at runtime" tristate "Test kstrto*() family of functions at runtime"
......
...@@ -81,6 +81,7 @@ obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o ...@@ -81,6 +81,7 @@ obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
obj-$(CONFIG_TEST_PRINTF) += test_printf.o obj-$(CONFIG_TEST_PRINTF) += test_printf.o
obj-$(CONFIG_TEST_BITMAP) += test_bitmap.o obj-$(CONFIG_TEST_BITMAP) += test_bitmap.o
obj-$(CONFIG_TEST_STRSCPY) += test_strscpy.o
obj-$(CONFIG_TEST_BITFIELD) += test_bitfield.o obj-$(CONFIG_TEST_BITFIELD) += test_bitfield.o
obj-$(CONFIG_TEST_UUID) += test_uuid.o obj-$(CONFIG_TEST_UUID) += test_uuid.o
obj-$(CONFIG_TEST_XARRAY) += test_xarray.o obj-$(CONFIG_TEST_XARRAY) += test_xarray.o
......
...@@ -159,11 +159,9 @@ EXPORT_SYMBOL(strlcpy); ...@@ -159,11 +159,9 @@ EXPORT_SYMBOL(strlcpy);
* @src: Where to copy the string from * @src: Where to copy the string from
* @count: Size of destination buffer * @count: Size of destination buffer
* *
* Copy the string, or as much of it as fits, into the dest buffer. * Copy the string, or as much of it as fits, into the dest buffer. The
* The routine returns the number of characters copied (not including * behavior is undefined if the string buffers overlap. The destination
* the trailing NUL) or -E2BIG if the destination buffer wasn't big enough. * buffer is always NUL terminated, unless it's zero-sized.
* The behavior is undefined if the string buffers overlap.
* The destination buffer is always NUL terminated, unless it's zero-sized.
* *
* Preferred to strlcpy() since the API doesn't require reading memory * Preferred to strlcpy() since the API doesn't require reading memory
* from the src string beyond the specified "count" bytes, and since * from the src string beyond the specified "count" bytes, and since
...@@ -173,8 +171,10 @@ EXPORT_SYMBOL(strlcpy); ...@@ -173,8 +171,10 @@ EXPORT_SYMBOL(strlcpy);
* *
* Preferred to strncpy() since it always returns a valid string, and * Preferred to strncpy() since it always returns a valid string, and
* doesn't unnecessarily force the tail of the destination buffer to be * doesn't unnecessarily force the tail of the destination buffer to be
* zeroed. If the zeroing is desired, it's likely cleaner to use strscpy() * zeroed. If zeroing is desired please use strscpy_pad().
* with an overflow test, then just memset() the tail of the dest buffer. *
* Return: The number of characters copied (not including the trailing
* %NUL) or -E2BIG if the destination buffer wasn't big enough.
*/ */
ssize_t strscpy(char *dest, const char *src, size_t count) ssize_t strscpy(char *dest, const char *src, size_t count)
{ {
...@@ -237,6 +237,39 @@ ssize_t strscpy(char *dest, const char *src, size_t count) ...@@ -237,6 +237,39 @@ ssize_t strscpy(char *dest, const char *src, size_t count)
EXPORT_SYMBOL(strscpy); EXPORT_SYMBOL(strscpy);
#endif #endif
/**
* strscpy_pad() - Copy a C-string into a sized buffer
* @dest: Where to copy the string to
* @src: Where to copy the string from
* @count: Size of destination buffer
*
* Copy the string, or as much of it as fits, into the dest buffer. The
* behavior is undefined if the string buffers overlap. The destination
* buffer is always %NUL terminated, unless it's zero-sized.
*
* If the source string is shorter than the destination buffer, zeros
* the tail of the destination buffer.
*
* For full explanation of why you may want to consider using the
* 'strscpy' functions please see the function docstring for strscpy().
*
* Return: The number of characters copied (not including the trailing
* %NUL) or -E2BIG if the destination buffer wasn't big enough.
*/
ssize_t strscpy_pad(char *dest, const char *src, size_t count)
{
ssize_t written;
written = strscpy(dest, src, count);
if (written < 0 || written == count - 1)
return written;
memset(dest + written + 1, 0, count - written - 1);
return written;
}
EXPORT_SYMBOL(strscpy_pad);
#ifndef __HAVE_ARCH_STRCAT #ifndef __HAVE_ARCH_STRCAT
/** /**
* strcat - Append one %NUL-terminated string to another * strcat - Append one %NUL-terminated string to another
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h> #include <linux/string.h>
#include "../tools/testing/selftests/kselftest_module.h"
static unsigned total_tests __initdata; static unsigned total_tests __initdata;
static unsigned failed_tests __initdata; static unsigned failed_tests __initdata;
...@@ -361,7 +363,7 @@ static void noinline __init test_mem_optimisations(void) ...@@ -361,7 +363,7 @@ static void noinline __init test_mem_optimisations(void)
} }
} }
static int __init test_bitmap_init(void) static void __init selftest(void)
{ {
test_zero_clear(); test_zero_clear();
test_fill_set(); test_fill_set();
...@@ -369,22 +371,8 @@ static int __init test_bitmap_init(void) ...@@ -369,22 +371,8 @@ static int __init test_bitmap_init(void)
test_bitmap_arr32(); test_bitmap_arr32();
test_bitmap_parselist(); test_bitmap_parselist();
test_mem_optimisations(); test_mem_optimisations();
if (failed_tests == 0)
pr_info("all %u tests passed\n", total_tests);
else
pr_warn("failed %u out of %u tests\n",
failed_tests, total_tests);
return failed_tests ? -EINVAL : 0;
} }
static void __exit test_bitmap_cleanup(void) KSTM_MODULE_LOADERS(test_bitmap);
{
}
module_init(test_bitmap_init);
module_exit(test_bitmap_cleanup);
MODULE_AUTHOR("david decotigny <david.decotigny@googlers.com>"); MODULE_AUTHOR("david decotigny <david.decotigny@googlers.com>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
#include <linux/gfp.h> #include <linux/gfp.h>
#include <linux/mm.h> #include <linux/mm.h>
#include "../tools/testing/selftests/kselftest_module.h"
#define BUF_SIZE 256 #define BUF_SIZE 256
#define PAD_SIZE 16 #define PAD_SIZE 16
#define FILL_CHAR '$' #define FILL_CHAR '$'
...@@ -590,12 +592,11 @@ test_pointer(void) ...@@ -590,12 +592,11 @@ test_pointer(void)
flags(); flags();
} }
static int __init static void __init selftest(void)
test_printf_init(void)
{ {
alloced_buffer = kmalloc(BUF_SIZE + 2*PAD_SIZE, GFP_KERNEL); alloced_buffer = kmalloc(BUF_SIZE + 2*PAD_SIZE, GFP_KERNEL);
if (!alloced_buffer) if (!alloced_buffer)
return -ENOMEM; return;
test_buffer = alloced_buffer + PAD_SIZE; test_buffer = alloced_buffer + PAD_SIZE;
test_basic(); test_basic();
...@@ -604,16 +605,8 @@ test_printf_init(void) ...@@ -604,16 +605,8 @@ test_printf_init(void)
test_pointer(); test_pointer();
kfree(alloced_buffer); kfree(alloced_buffer);
if (failed_tests == 0)
pr_info("all %u tests passed\n", total_tests);
else
pr_warn("failed %u out of %u tests\n", failed_tests, total_tests);
return failed_tests ? -EINVAL : 0;
} }
module_init(test_printf_init); KSTM_MODULE_LOADERS(test_printf);
MODULE_AUTHOR("Rasmus Villemoes <linux@rasmusvillemoes.dk>"); MODULE_AUTHOR("Rasmus Villemoes <linux@rasmusvillemoes.dk>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/string.h>
#include "../tools/testing/selftests/kselftest_module.h"
/*
* Kernel module for testing 'strscpy' family of functions.
*/
KSTM_MODULE_GLOBALS();
/*
* tc() - Run a specific test case.
* @src: Source string, argument to strscpy_pad()
* @count: Size of destination buffer, argument to strscpy_pad()
* @expected: Expected return value from call to strscpy_pad()
* @terminator: 1 if there should be a terminating null byte 0 otherwise.
* @chars: Number of characters from the src string expected to be
* written to the dst buffer.
* @pad: Number of pad characters expected (in the tail of dst buffer).
* (@pad does not include the null terminator byte.)
*
* Calls strscpy_pad() and verifies the return value and state of the
* destination buffer after the call returns.
*/
static int __init tc(char *src, int count, int expected,
int chars, int terminator, int pad)
{
int nr_bytes_poison;
int max_expected;
int max_count;
int written;
char buf[6];
int index, i;
const char POISON = 'z';
total_tests++;
if (!src) {
pr_err("null source string not supported\n");
return -1;
}
memset(buf, POISON, sizeof(buf));
/* Future proofing test suite, validate args */
max_count = sizeof(buf) - 2; /* Space for null and to verify overflow */
max_expected = count - 1; /* Space for the null */
if (count > max_count) {
pr_err("count (%d) is too big (%d) ... aborting", count, max_count);
return -1;
}
if (expected > max_expected) {
pr_warn("expected (%d) is bigger than can possibly be returned (%d)",
expected, max_expected);
}
written = strscpy_pad(buf, src, count);
if ((written) != (expected)) {
pr_err("%d != %d (written, expected)\n", written, expected);
goto fail;
}
if (count && written == -E2BIG) {
if (strncmp(buf, src, count - 1) != 0) {
pr_err("buffer state invalid for -E2BIG\n");
goto fail;
}
if (buf[count - 1] != '\0') {
pr_err("too big string is not null terminated correctly\n");
goto fail;
}
}
for (i = 0; i < chars; i++) {
if (buf[i] != src[i]) {
pr_err("buf[i]==%c != src[i]==%c\n", buf[i], src[i]);
goto fail;
}
}
if (terminator) {
if (buf[count - 1] != '\0') {
pr_err("string is not null terminated correctly\n");
goto fail;
}
}
for (i = 0; i < pad; i++) {
index = chars + terminator + i;
if (buf[index] != '\0') {
pr_err("padding missing at index: %d\n", i);
goto fail;
}
}
nr_bytes_poison = sizeof(buf) - chars - terminator - pad;
for (i = 0; i < nr_bytes_poison; i++) {
index = sizeof(buf) - 1 - i; /* Check from the end back */
if (buf[index] != POISON) {
pr_err("poison value missing at index: %d\n", i);
goto fail;
}
}
return 0;
fail:
failed_tests++;
return -1;
}
static void __init selftest(void)
{
/*
* tc() uses a destination buffer of size 6 and needs at
* least 2 characters spare (one for null and one to check for
* overflow). This means we should only call tc() with
* strings up to a maximum of 4 characters long and 'count'
* should not exceed 4. To test with longer strings increase
* the buffer size in tc().
*/
/* tc(src, count, expected, chars, terminator, pad) */
KSTM_CHECK_ZERO(tc("a", 0, -E2BIG, 0, 0, 0));
KSTM_CHECK_ZERO(tc("", 0, -E2BIG, 0, 0, 0));
KSTM_CHECK_ZERO(tc("a", 1, -E2BIG, 0, 1, 0));
KSTM_CHECK_ZERO(tc("", 1, 0, 0, 1, 0));
KSTM_CHECK_ZERO(tc("ab", 2, -E2BIG, 1, 1, 0));
KSTM_CHECK_ZERO(tc("a", 2, 1, 1, 1, 0));
KSTM_CHECK_ZERO(tc("", 2, 0, 0, 1, 1));
KSTM_CHECK_ZERO(tc("abc", 3, -E2BIG, 2, 1, 0));
KSTM_CHECK_ZERO(tc("ab", 3, 2, 2, 1, 0));
KSTM_CHECK_ZERO(tc("a", 3, 1, 1, 1, 1));
KSTM_CHECK_ZERO(tc("", 3, 0, 0, 1, 2));
KSTM_CHECK_ZERO(tc("abcd", 4, -E2BIG, 3, 1, 0));
KSTM_CHECK_ZERO(tc("abc", 4, 3, 3, 1, 0));
KSTM_CHECK_ZERO(tc("ab", 4, 2, 2, 1, 1));
KSTM_CHECK_ZERO(tc("a", 4, 1, 1, 1, 2));
KSTM_CHECK_ZERO(tc("", 4, 0, 0, 1, 3));
}
KSTM_MODULE_LOADERS(test_strscpy);
MODULE_AUTHOR("Tobin C. Harding <tobin@kernel.org>");
MODULE_LICENSE("GPL");
...@@ -15,11 +15,11 @@ TARGETS += firmware ...@@ -15,11 +15,11 @@ TARGETS += firmware
TARGETS += ftrace TARGETS += ftrace
TARGETS += futex TARGETS += futex
TARGETS += gpio TARGETS += gpio
TARGETS += ima
TARGETS += intel_pstate TARGETS += intel_pstate
TARGETS += ipc TARGETS += ipc
TARGETS += ir TARGETS += ir
TARGETS += kcmp TARGETS += kcmp
TARGETS += kexec
TARGETS += kvm TARGETS += kvm
TARGETS += lib TARGETS += lib
TARGETS += livepatch TARGETS += livepatch
...@@ -75,12 +75,15 @@ ifneq ($(KBUILD_SRC),) ...@@ -75,12 +75,15 @@ ifneq ($(KBUILD_SRC),)
override LDFLAGS = override LDFLAGS =
endif endif
BUILD := $(O) ifneq ($(O),)
ifndef BUILD BUILD := $(O)
BUILD := $(KBUILD_OUTPUT) else
endif ifneq ($(KBUILD_OUTPUT),)
ifndef BUILD BUILD := $(KBUILD_OUTPUT)
BUILD := $(shell pwd) else
BUILD := $(shell pwd)
DEFAULT_INSTALL_HDR_PATH := 1
endif
endif endif
# KSFT_TAP_LEVEL is used from KSFT framework to prevent nested TAP header # KSFT_TAP_LEVEL is used from KSFT framework to prevent nested TAP header
...@@ -89,8 +92,50 @@ endif ...@@ -89,8 +92,50 @@ endif
# with system() call. Export it here to cover override RUN_TESTS defines. # with system() call. Export it here to cover override RUN_TESTS defines.
export KSFT_TAP_LEVEL=`echo 1` export KSFT_TAP_LEVEL=`echo 1`
# Prepare for headers install
top_srcdir ?= ../../..
include $(top_srcdir)/scripts/subarch.include
ARCH ?= $(SUBARCH)
export KSFT_KHDR_INSTALL_DONE := 1
export BUILD export BUILD
all:
# build and run gpio when output directory is the src dir.
# gpio has dependency on tools/gpio and builds tools/gpio
# objects in the src directory in all cases making the src
# repo dirty even when objects are relocated.
ifneq (1,$(DEFAULT_INSTALL_HDR_PATH))
TMP := $(filter-out gpio, $(TARGETS))
TARGETS := $(TMP)
endif
# set default goal to all, so make without a target runs all, even when
# all isn't the first target in the file.
.DEFAULT_GOAL := all
# Install headers here once for all tests. KSFT_KHDR_INSTALL_DONE
# is used to avoid running headers_install from lib.mk.
# Invoke headers install with --no-builtin-rules to avoid circular
# dependency in "make kselftest" case. In this case, second level
# make inherits builtin-rules which will use the rule generate
# Makefile.o and runs into
# "Circular Makefile.o <- prepare dependency dropped."
# and headers_install fails and test compile fails.
#
# O= KBUILD_OUTPUT cases don't run into this error, since main Makefile
# invokes them as sub-makes and --no-builtin-rules is not necessary,
# but doesn't cause any failures. Keep it simple and use the same
# flags in both cases.
# Local build cases: "make kselftest", "make -C" - headers are installed
# in the default INSTALL_HDR_PATH usr/include.
khdr:
ifeq (1,$(DEFAULT_INSTALL_HDR_PATH))
make --no-builtin-rules ARCH=$(ARCH) -C $(top_srcdir) headers_install
else
make --no-builtin-rules INSTALL_HDR_PATH=$$BUILD/usr \
ARCH=$(ARCH) -C $(top_srcdir) headers_install
endif
all: khdr
@for TARGET in $(TARGETS); do \ @for TARGET in $(TARGETS); do \
BUILD_TARGET=$$BUILD/$$TARGET; \ BUILD_TARGET=$$BUILD/$$TARGET; \
mkdir $$BUILD_TARGET -p; \ mkdir $$BUILD_TARGET -p; \
...@@ -173,4 +218,4 @@ clean: ...@@ -173,4 +218,4 @@ clean:
make OUTPUT=$$BUILD_TARGET -C $$TARGET clean;\ make OUTPUT=$$BUILD_TARGET -C $$TARGET clean;\
done; done;
.PHONY: all run_tests hotplug run_hotplug clean_hotplug run_pstore_crash install clean .PHONY: khdr all run_tests hotplug run_hotplug clean_hotplug run_pstore_crash install clean
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
*/ */
static int test_memcg_subtree_control(const char *root) static int test_memcg_subtree_control(const char *root)
{ {
char *parent, *child, *parent2, *child2; char *parent, *child, *parent2 = NULL, *child2 = NULL;
int ret = KSFT_FAIL; int ret = KSFT_FAIL;
char buf[PAGE_SIZE]; char buf[PAGE_SIZE];
...@@ -34,50 +34,54 @@ static int test_memcg_subtree_control(const char *root) ...@@ -34,50 +34,54 @@ static int test_memcg_subtree_control(const char *root)
parent = cg_name(root, "memcg_test_0"); parent = cg_name(root, "memcg_test_0");
child = cg_name(root, "memcg_test_0/memcg_test_1"); child = cg_name(root, "memcg_test_0/memcg_test_1");
if (!parent || !child) if (!parent || !child)
goto cleanup; goto cleanup_free;
if (cg_create(parent)) if (cg_create(parent))
goto cleanup; goto cleanup_free;
if (cg_write(parent, "cgroup.subtree_control", "+memory")) if (cg_write(parent, "cgroup.subtree_control", "+memory"))
goto cleanup; goto cleanup_parent;
if (cg_create(child)) if (cg_create(child))
goto cleanup; goto cleanup_parent;
if (cg_read_strstr(child, "cgroup.controllers", "memory")) if (cg_read_strstr(child, "cgroup.controllers", "memory"))
goto cleanup; goto cleanup_child;
/* Create two nested cgroups without enabling memory controller */ /* Create two nested cgroups without enabling memory controller */
parent2 = cg_name(root, "memcg_test_1"); parent2 = cg_name(root, "memcg_test_1");
child2 = cg_name(root, "memcg_test_1/memcg_test_1"); child2 = cg_name(root, "memcg_test_1/memcg_test_1");
if (!parent2 || !child2) if (!parent2 || !child2)
goto cleanup; goto cleanup_free2;
if (cg_create(parent2)) if (cg_create(parent2))
goto cleanup; goto cleanup_free2;
if (cg_create(child2)) if (cg_create(child2))
goto cleanup; goto cleanup_parent2;
if (cg_read(child2, "cgroup.controllers", buf, sizeof(buf))) if (cg_read(child2, "cgroup.controllers", buf, sizeof(buf)))
goto cleanup; goto cleanup_all;
if (!cg_read_strstr(child2, "cgroup.controllers", "memory")) if (!cg_read_strstr(child2, "cgroup.controllers", "memory"))
goto cleanup; goto cleanup_all;
ret = KSFT_PASS; ret = KSFT_PASS;
cleanup: cleanup_all:
cg_destroy(child);
cg_destroy(parent);
free(parent);
free(child);
cg_destroy(child2); cg_destroy(child2);
cleanup_parent2:
cg_destroy(parent2); cg_destroy(parent2);
cleanup_free2:
free(parent2); free(parent2);
free(child2); free(child2);
cleanup_child:
cg_destroy(child);
cleanup_parent:
cg_destroy(parent);
cleanup_free:
free(parent);
free(child);
return ret; return ret;
} }
......
...@@ -7,6 +7,12 @@ test_guid=210be57c-9849-4fc7-a635-e6382d1aec27 ...@@ -7,6 +7,12 @@ test_guid=210be57c-9849-4fc7-a635-e6382d1aec27
# Kselftest framework requirement - SKIP code is 4. # Kselftest framework requirement - SKIP code is 4.
ksft_skip=4 ksft_skip=4
file_cleanup()
{
chattr -i $1
rm -f $1
}
check_prereqs() check_prereqs()
{ {
local msg="skip all tests:" local msg="skip all tests:"
...@@ -58,8 +64,10 @@ test_create() ...@@ -58,8 +64,10 @@ test_create()
if [ $(stat -c %s $file) -ne 5 ]; then if [ $(stat -c %s $file) -ne 5 ]; then
echo "$file has invalid size" >&2 echo "$file has invalid size" >&2
file_cleanup $file
exit 1 exit 1
fi fi
file_cleanup $file
} }
test_create_empty() test_create_empty()
...@@ -72,12 +80,14 @@ test_create_empty() ...@@ -72,12 +80,14 @@ test_create_empty()
echo "$file can not be created without writing" >&2 echo "$file can not be created without writing" >&2
exit 1 exit 1
fi fi
file_cleanup $file
} }
test_create_read() test_create_read()
{ {
local file=$efivarfs_mount/$FUNCNAME-$test_guid local file=$efivarfs_mount/$FUNCNAME-$test_guid
./create-read $file ./create-read $file
file_cleanup $file
} }
test_delete() test_delete()
...@@ -92,11 +102,7 @@ test_delete() ...@@ -92,11 +102,7 @@ test_delete()
exit 1 exit 1
fi fi
rm $file 2>/dev/null file_cleanup $file
if [ $? -ne 0 ]; then
chattr -i $file
rm $file
fi
if [ -e $file ]; then if [ -e $file ]; then
echo "$file couldn't be deleted" >&2 echo "$file couldn't be deleted" >&2
...@@ -150,11 +156,7 @@ test_valid_filenames() ...@@ -150,11 +156,7 @@ test_valid_filenames()
echo "$file could not be created" >&2 echo "$file could not be created" >&2
ret=1 ret=1
else else
rm $file 2>/dev/null file_cleanup $file
if [ $? -ne 0 ]; then
chattr -i $file
rm $file
fi
fi fi
done done
...@@ -187,11 +189,7 @@ test_invalid_filenames() ...@@ -187,11 +189,7 @@ test_invalid_filenames()
if [ -e $file ]; then if [ -e $file ]; then
echo "Creating $file should have failed" >&2 echo "Creating $file should have failed" >&2
rm $file 2>/dev/null file_cleanup $file
if [ $? -ne 0 ]; then
chattr -i $file
rm $file
fi
ret=1 ret=1
fi fi
done done
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
......
CONFIG_IMA_APPRAISE
CONFIG_IMA_ARCH_POLICY
CONFIG_SECURITYFS
CONFIG_KEXEC_VERIFY_SIG
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0+
# Loading a kernel image via the kexec_load syscall should fail
# when the kerne is CONFIG_KEXEC_VERIFY_SIG enabled and the system
# is booted in secureboot mode.
TEST="$0"
EFIVARFS="/sys/firmware/efi/efivars"
rc=0
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
# kexec requires root privileges
if [ $UID != 0 ]; then
echo "$TEST: must be run as root" >&2
exit $ksft_skip
fi
# Make sure that efivars is mounted in the normal location
if ! grep -q "^\S\+ $EFIVARFS efivarfs" /proc/mounts; then
echo "$TEST: efivars is not mounted on $EFIVARFS" >&2
exit $ksft_skip
fi
# Get secureboot mode
file="$EFIVARFS/SecureBoot-*"
if [ ! -e $file ]; then
echo "$TEST: unknown secureboot mode" >&2
exit $ksft_skip
fi
secureboot=`hexdump $file | awk '{print substr($4,length($4),1)}'`
# kexec_load should fail in secure boot mode
KERNEL_IMAGE="/boot/vmlinuz-`uname -r`"
kexec -l $KERNEL_IMAGE &>> /dev/null
if [ $? == 0 ]; then
kexec -u
if [ "$secureboot" == "1" ]; then
echo "$TEST: kexec_load succeeded [FAIL]"
rc=1
else
echo "$TEST: kexec_load succeeded [PASS]"
fi
else
if [ "$secureboot" == "1" ]; then
echo "$TEST: kexec_load failed [PASS]"
else
echo "$TEST: kexec_load failed [FAIL]"
rc=1
fi
fi
exit $rc
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <linux/msg.h> #include <sys/msg.h>
#include <fcntl.h> #include <fcntl.h>
#include "../kselftest.h" #include "../kselftest.h"
...@@ -73,7 +74,7 @@ int restore_queue(struct msgque_data *msgque) ...@@ -73,7 +74,7 @@ int restore_queue(struct msgque_data *msgque)
return 0; return 0;
destroy: destroy:
if (msgctl(id, IPC_RMID, 0)) if (msgctl(id, IPC_RMID, NULL))
printf("Failed to destroy queue: %d\n", -errno); printf("Failed to destroy queue: %d\n", -errno);
return ret; return ret;
} }
...@@ -120,7 +121,7 @@ int check_and_destroy_queue(struct msgque_data *msgque) ...@@ -120,7 +121,7 @@ int check_and_destroy_queue(struct msgque_data *msgque)
ret = 0; ret = 0;
err: err:
if (msgctl(msgque->msq_id, IPC_RMID, 0)) { if (msgctl(msgque->msq_id, IPC_RMID, NULL)) {
printf("Failed to destroy queue: %d\n", -errno); printf("Failed to destroy queue: %d\n", -errno);
return -errno; return -errno;
} }
...@@ -129,7 +130,7 @@ int check_and_destroy_queue(struct msgque_data *msgque) ...@@ -129,7 +130,7 @@ int check_and_destroy_queue(struct msgque_data *msgque)
int dump_queue(struct msgque_data *msgque) int dump_queue(struct msgque_data *msgque)
{ {
struct msqid64_ds ds; struct msqid_ds ds;
int kern_id; int kern_id;
int i, ret; int i, ret;
...@@ -245,7 +246,7 @@ int main(int argc, char **argv) ...@@ -245,7 +246,7 @@ int main(int argc, char **argv)
return ksft_exit_pass(); return ksft_exit_pass();
err_destroy: err_destroy:
if (msgctl(msgque.msq_id, IPC_RMID, 0)) { if (msgctl(msgque.msq_id, IPC_RMID, NULL)) {
printf("Failed to destroy queue: %d\n", -errno); printf("Failed to destroy queue: %d\n", -errno);
return ksft_exit_fail(); return ksft_exit_fail();
} }
......
# Makefile for kexec_load # Makefile for kexec tests
uname_M := $(shell uname -m 2>/dev/null || echo not) uname_M := $(shell uname -m 2>/dev/null || echo not)
ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/)
ifeq ($(ARCH),x86) ifeq ($(ARCH),x86)
TEST_PROGS := test_kexec_load.sh TEST_PROGS := test_kexec_load.sh test_kexec_file_load.sh
TEST_FILES := kexec_common_lib.sh
include ../lib.mk include ../lib.mk
......
CONFIG_IMA_APPRAISE=y
CONFIG_IMA_ARCH_POLICY=y
CONFIG_SECURITYFS=y
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
# Kselftest framework defines: ksft_pass=0, ksft_fail=1, ksft_skip=4
VERBOSE="${VERBOSE:-1}"
IKCONFIG="/tmp/config-`uname -r`"
KERNEL_IMAGE="/boot/vmlinuz-`uname -r`"
SECURITYFS=$(grep "securityfs" /proc/mounts | awk '{print $2}')
log_info()
{
[ $VERBOSE -ne 0 ] && echo "[INFO] $1"
}
# The ksefltest framework requirement returns 0 for PASS.
log_pass()
{
[ $VERBOSE -ne 0 ] && echo "$1 [PASS]"
exit 0
}
# The ksefltest framework requirement returns 1 for FAIL.
log_fail()
{
[ $VERBOSE -ne 0 ] && echo "$1 [FAIL]"
exit 1
}
# The ksefltest framework requirement returns 4 for SKIP.
log_skip()
{
[ $VERBOSE -ne 0 ] && echo "$1"
exit 4
}
# Check efivar SecureBoot-$(the UUID) and SetupMode-$(the UUID).
# (Based on kdump-lib.sh)
get_efivarfs_secureboot_mode()
{
local efivarfs="/sys/firmware/efi/efivars"
local secure_boot_file=""
local setup_mode_file=""
local secureboot_mode=0
local setup_mode=0
# Make sure that efivar_fs is mounted in the normal location
if ! grep -q "^\S\+ $efivarfs efivarfs" /proc/mounts; then
log_info "efivars is not mounted on $efivarfs"
return 0;
fi
secure_boot_file=$(find "$efivarfs" -name SecureBoot-* 2>/dev/null)
setup_mode_file=$(find "$efivarfs" -name SetupMode-* 2>/dev/null)
if [ -f "$secure_boot_file" ] && [ -f "$setup_mode_file" ]; then
secureboot_mode=$(hexdump -v -e '/1 "%d\ "' \
"$secure_boot_file"|cut -d' ' -f 5)
setup_mode=$(hexdump -v -e '/1 "%d\ "' \
"$setup_mode_file"|cut -d' ' -f 5)
if [ $secureboot_mode -eq 1 ] && [ $setup_mode -eq 0 ]; then
log_info "secure boot mode enabled (CONFIG_EFIVAR_FS)"
return 1;
fi
fi
return 0;
}
get_efi_var_secureboot_mode()
{
local efi_vars
local secure_boot_file
local setup_mode_file
local secureboot_mode
local setup_mode
if [ ! -d "$efi_vars" ]; then
log_skip "efi_vars is not enabled\n"
fi
secure_boot_file=$(find "$efi_vars" -name SecureBoot-* 2>/dev/null)
setup_mode_file=$(find "$efi_vars" -name SetupMode-* 2>/dev/null)
if [ -f "$secure_boot_file/data" ] && \
[ -f "$setup_mode_file/data" ]; then
secureboot_mode=`od -An -t u1 "$secure_boot_file/data"`
setup_mode=`od -An -t u1 "$setup_mode_file/data"`
if [ $secureboot_mode -eq 1 ] && [ $setup_mode -eq 0 ]; then
log_info "secure boot mode enabled (CONFIG_EFI_VARS)"
return 1;
fi
fi
return 0;
}
# Check efivar SecureBoot-$(the UUID) and SetupMode-$(the UUID).
# The secure boot mode can be accessed either as the last integer
# of "od -An -t u1 /sys/firmware/efi/efivars/SecureBoot-*" or from
# "od -An -t u1 /sys/firmware/efi/vars/SecureBoot-*/data". The efi
# SetupMode can be similarly accessed.
# Return 1 for SecureBoot mode enabled and SetupMode mode disabled.
get_secureboot_mode()
{
local secureboot_mode=0
get_efivarfs_secureboot_mode
secureboot_mode=$?
# fallback to using the efi_var files
if [ $secureboot_mode -eq 0 ]; then
get_efi_var_secureboot_mode
secureboot_mode=$?
fi
if [ $secureboot_mode -eq 0 ]; then
log_info "secure boot mode not enabled"
fi
return $secureboot_mode;
}
require_root_privileges()
{
if [ $(id -ru) -ne 0 ]; then
log_skip "requires root privileges"
fi
}
# Look for config option in Kconfig file.
# Return 1 for found and 0 for not found.
kconfig_enabled()
{
local config="$1"
local msg="$2"
grep -E -q $config $IKCONFIG
if [ $? -eq 0 ]; then
log_info "$msg"
return 1
fi
return 0
}
# Attempt to get the kernel config first via proc, and then by
# extracting it from the kernel image or the configs.ko using
# scripts/extract-ikconfig.
# Return 1 for found.
get_kconfig()
{
local proc_config="/proc/config.gz"
local module_dir="/lib/modules/`uname -r`"
local configs_module="$module_dir/kernel/kernel/configs.ko"
if [ ! -f $proc_config ]; then
modprobe configs > /dev/null 2>&1
fi
if [ -f $proc_config ]; then
cat $proc_config | gunzip > $IKCONFIG 2>/dev/null
if [ $? -eq 0 ]; then
return 1
fi
fi
local extract_ikconfig="$module_dir/source/scripts/extract-ikconfig"
if [ ! -f $extract_ikconfig ]; then
log_skip "extract-ikconfig not found"
fi
$extract_ikconfig $KERNEL_IMAGE > $IKCONFIG 2>/dev/null
if [ $? -eq 1 ]; then
if [ ! -f $configs_module ]; then
log_skip "CONFIG_IKCONFIG not enabled"
fi
$extract_ikconfig $configs_module > $IKCONFIG
if [ $? -eq 1 ]; then
log_skip "CONFIG_IKCONFIG not enabled"
fi
fi
return 1
}
# Make sure that securityfs is mounted
mount_securityfs()
{
if [ -z $SECURITYFS ]; then
SECURITYFS=/sys/kernel/security
mount -t securityfs security $SECURITYFS
fi
if [ ! -d "$SECURITYFS" ]; then
log_fail "$SECURITYFS :securityfs is not mounted"
fi
}
# The policy rule format is an "action" followed by key-value pairs. This
# function supports up to two key-value pairs, in any order.
# For example: action func=<keyword> [appraise_type=<type>]
# Return 1 for found and 0 for not found.
check_ima_policy()
{
local action="$1"
local keypair1="$2"
local keypair2="$3"
local ret=0
mount_securityfs
local ima_policy=$SECURITYFS/ima/policy
if [ ! -e $ima_policy ]; then
log_fail "$ima_policy not found"
fi
if [ -n $keypair2 ]; then
grep -e "^$action.*$keypair1" "$ima_policy" | \
grep -q -e "$keypair2"
else
grep -q -e "^$action.*$keypair1" "$ima_policy"
fi
# invert "grep -q" result, returning 1 for found.
[ $? -eq 0 ] && ret=1
return $ret
}
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
# Loading a kernel image via the kexec_file_load syscall can verify either
# the IMA signature stored in the security.ima xattr or the PE signature,
# both signatures depending on the IMA policy, or none.
#
# To determine whether the kernel image is signed, this test depends
# on pesign and getfattr. This test also requires the kernel to be
# built with CONFIG_IKCONFIG enabled and either CONFIG_IKCONFIG_PROC
# enabled or access to the extract-ikconfig script.
TEST="KEXEC_FILE_LOAD"
. ./kexec_common_lib.sh
trap "{ rm -f $IKCONFIG ; }" EXIT
# Some of the IMA builtin policies may require the kexec kernel image to
# be signed, but these policy rules may be replaced with a custom
# policy. Only CONFIG_IMA_APPRAISE_REQUIRE_KEXEC_SIGS persists after
# loading a custom policy. Check if it is enabled, before reading the
# IMA runtime sysfs policy file.
# Return 1 for IMA signature required and 0 for not required.
is_ima_sig_required()
{
local ret=0
kconfig_enabled "CONFIG_IMA_APPRAISE_REQUIRE_KEXEC_SIGS=y" \
"IMA kernel image signature required"
if [ $? -eq 1 ]; then
log_info "IMA signature required"
return 1
fi
# The architecture specific or a custom policy may require the
# kexec kernel image be signed. Policy rules are walked
# sequentially. As a result, a policy rule may be defined, but
# might not necessarily be used. This test assumes if a policy
# rule is specified, that is the intent.
if [ $ima_read_policy -eq 1 ]; then
check_ima_policy "appraise" "func=KEXEC_KERNEL_CHECK" \
"appraise_type=imasig"
ret=$?
[ $ret -eq 1 ] && log_info "IMA signature required";
fi
return $ret
}
# The kexec_file_load_test() is complicated enough, require pesign.
# Return 1 for PE signature found and 0 for not found.
check_for_pesig()
{
which pesign > /dev/null 2>&1 || log_skip "pesign not found"
pesign -i $KERNEL_IMAGE --show-signature | grep -q "No signatures"
local ret=$?
if [ $ret -eq 1 ]; then
log_info "kexec kernel image PE signed"
else
log_info "kexec kernel image not PE signed"
fi
return $ret
}
# The kexec_file_load_test() is complicated enough, require getfattr.
# Return 1 for IMA signature found and 0 for not found.
check_for_imasig()
{
local ret=0
which getfattr > /dev/null 2>&1
if [ $? -eq 1 ]; then
log_skip "getfattr not found"
fi
line=$(getfattr -n security.ima -e hex --absolute-names $KERNEL_IMAGE 2>&1)
echo $line | grep -q "security.ima=0x03"
if [ $? -eq 0 ]; then
ret=1
log_info "kexec kernel image IMA signed"
else
log_info "kexec kernel image not IMA signed"
fi
return $ret
}
kexec_file_load_test()
{
local succeed_msg="kexec_file_load succeeded"
local failed_msg="kexec_file_load failed"
local key_msg="try enabling the CONFIG_INTEGRITY_PLATFORM_KEYRING"
line=$(kexec --load --kexec-file-syscall $KERNEL_IMAGE 2>&1)
if [ $? -eq 0 ]; then
kexec --unload --kexec-file-syscall
# In secureboot mode with an architecture specific
# policy, make sure either an IMA or PE signature exists.
if [ $secureboot -eq 1 ] && [ $arch_policy -eq 1 ] && \
[ $ima_signed -eq 0 ] && [ $pe_signed -eq 0 ]; then
log_fail "$succeed_msg (missing sig)"
fi
if [ $kexec_sig_required -eq 1 -o $pe_sig_required -eq 1 ] \
&& [ $pe_signed -eq 0 ]; then
log_fail "$succeed_msg (missing PE sig)"
fi
if [ $ima_sig_required -eq 1 ] && [ $ima_signed -eq 0 ]; then
log_fail "$succeed_msg (missing IMA sig)"
fi
if [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 1 ] \
&& [ $ima_sig_required -eq 0 ] && [ $ima_signed -eq 0 ] \
&& [ $ima_read_policy -eq 0 ]; then
log_fail "$succeed_msg (possibly missing IMA sig)"
fi
if [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 0 ]; then
log_info "No signature verification required"
elif [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 1 ] \
&& [ $ima_sig_required -eq 0 ] && [ $ima_signed -eq 0 ] \
&& [ $ima_read_policy -eq 1 ]; then
log_info "No signature verification required"
fi
log_pass "$succeed_msg"
fi
# Check the reason for the kexec_file_load failure
echo $line | grep -q "Required key not available"
if [ $? -eq 0 ]; then
if [ $platform_keyring -eq 0 ]; then
log_pass "$failed_msg (-ENOKEY), $key_msg"
else
log_pass "$failed_msg (-ENOKEY)"
fi
fi
if [ $kexec_sig_required -eq 1 -o $pe_sig_required -eq 1 ] \
&& [ $pe_signed -eq 0 ]; then
log_pass "$failed_msg (missing PE sig)"
fi
if [ $ima_sig_required -eq 1 ] && [ $ima_signed -eq 0 ]; then
log_pass "$failed_msg (missing IMA sig)"
fi
if [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 1 ] \
&& [ $ima_sig_required -eq 0 ] && [ $ima_read_policy -eq 0 ] \
&& [ $ima_signed -eq 0 ]; then
log_pass "$failed_msg (possibly missing IMA sig)"
fi
log_pass "$failed_msg"
return 0
}
# kexec requires root privileges
require_root_privileges
# get the kernel config
get_kconfig
kconfig_enabled "CONFIG_KEXEC_FILE=y" "kexec_file_load is enabled"
if [ $? -eq 0 ]; then
log_skip "kexec_file_load is not enabled"
fi
# Determine which kernel config options are enabled
kconfig_enabled "CONFIG_IMA_APPRAISE=y" "IMA enabled"
ima_appraise=$?
kconfig_enabled "CONFIG_IMA_ARCH_POLICY=y" \
"architecture specific policy enabled"
arch_policy=$?
kconfig_enabled "CONFIG_INTEGRITY_PLATFORM_KEYRING=y" \
"platform keyring enabled"
platform_keyring=$?
kconfig_enabled "CONFIG_IMA_READ_POLICY=y" "reading IMA policy permitted"
ima_read_policy=$?
kconfig_enabled "CONFIG_KEXEC_SIG_FORCE=y" \
"kexec signed kernel image required"
kexec_sig_required=$?
kconfig_enabled "CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y" \
"PE signed kernel image required"
pe_sig_required=$?
is_ima_sig_required
ima_sig_required=$?
get_secureboot_mode
secureboot=$?
# Are there pe and ima signatures
check_for_pesig
pe_signed=$?
check_for_imasig
ima_signed=$?
# Test loading the kernel image via kexec_file_load syscall
kexec_file_load_test
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
# Prevent loading a kernel image via the kexec_load syscall when
# signatures are required. (Dependent on CONFIG_IMA_ARCH_POLICY.)
TEST="$0"
. ./kexec_common_lib.sh
# kexec requires root privileges
require_root_privileges
# get the kernel config
get_kconfig
kconfig_enabled "CONFIG_KEXEC=y" "kexec_load is enabled"
if [ $? -eq 0 ]; then
log_skip "kexec_load is not enabled"
fi
kconfig_enabled "CONFIG_IMA_APPRAISE=y" "IMA enabled"
ima_appraise=$?
kconfig_enabled "CONFIG_IMA_ARCH_POLICY=y" \
"IMA architecture specific policy enabled"
arch_policy=$?
get_secureboot_mode
secureboot=$?
# kexec_load should fail in secure boot mode and CONFIG_IMA_ARCH_POLICY enabled
kexec --load $KERNEL_IMAGE > /dev/null 2>&1
if [ $? -eq 0 ]; then
kexec --unload
if [ $secureboot -eq 1 ] && [ $arch_policy -eq 1 ]; then
log_fail "kexec_load succeeded"
elif [ $ima_appraise -eq 0 -o $arch_policy -eq 0 ]; then
log_info "Either IMA or the IMA arch policy is not enabled"
fi
log_pass "kexec_load succeeded"
else
if [ $secureboot -eq 1 ] && [ $arch_policy -eq 1 ] ; then
log_pass "kexec_load failed"
else
log_fail "kexec_load failed"
fi
fi
...@@ -696,6 +696,7 @@ void __run_test(struct __test_metadata *t) ...@@ -696,6 +696,7 @@ void __run_test(struct __test_metadata *t)
t->passed = 1; t->passed = 1;
t->trigger = 0; t->trigger = 0;
printf("[ RUN ] %s\n", t->name); printf("[ RUN ] %s\n", t->name);
alarm(30);
child_pid = fork(); child_pid = fork();
if (child_pid < 0) { if (child_pid < 0) {
printf("ERROR SPAWNING TEST CHILD\n"); printf("ERROR SPAWNING TEST CHILD\n");
...@@ -744,6 +745,7 @@ void __run_test(struct __test_metadata *t) ...@@ -744,6 +745,7 @@ void __run_test(struct __test_metadata *t)
} }
} }
printf("[ %4s ] %s\n", (t->passed ? "OK" : "FAIL"), t->name); printf("[ %4s ] %s\n", (t->passed ? "OK" : "FAIL"), t->name);
alarm(0);
} }
static int test_harness_run(int __attribute__((unused)) argc, static int test_harness_run(int __attribute__((unused)) argc,
......
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef __KSELFTEST_MODULE_H
#define __KSELFTEST_MODULE_H
#include <linux/module.h>
/*
* Test framework for writing test modules to be loaded by kselftest.
* See Documentation/dev-tools/kselftest.rst for an example test module.
*/
#define KSTM_MODULE_GLOBALS() \
static unsigned int total_tests __initdata; \
static unsigned int failed_tests __initdata
#define KSTM_CHECK_ZERO(x) do { \
total_tests++; \
if (x) { \
pr_warn("TC failed at %s:%d\n", __func__, __LINE__); \
failed_tests++; \
} \
} while (0)
static inline int kstm_report(unsigned int total_tests, unsigned int failed_tests)
{
if (failed_tests == 0)
pr_info("all %u tests passed\n", total_tests);
else
pr_warn("failed %u out of %u tests\n", failed_tests, total_tests);
return failed_tests ? -EINVAL : 0;
}
#define KSTM_MODULE_LOADERS(__module) \
static int __init __module##_init(void) \
{ \
pr_info("loaded.\n"); \
selftest(); \
return kstm_report(total_tests, failed_tests); \
} \
static void __exit __module##_exit(void) \
{ \
pr_info("unloaded.\n"); \
} \
module_init(__module##_init); \
module_exit(__module##_exit)
#endif /* __KSELFTEST_MODULE_H */
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0+
#
# Runs an individual test module.
#
# kselftest expects a separate executable for each test, this can be
# created by adding a script like this:
#
# #!/bin/sh
# SPDX-License-Identifier: GPL-2.0+
# $(dirname $0)/../kselftest_module.sh "description" module_name
#
# Example: tools/testing/selftests/lib/printf.sh
desc="" # Output prefix.
module="" # Filename (without the .ko).
args="" # modprobe arguments.
modprobe="/sbin/modprobe"
main() {
parse_args "$@"
assert_root
assert_have_module
run_module
}
parse_args() {
script=${0##*/}
if [ $# -lt 2 ]; then
echo "Usage: $script <description> <module_name> [FAIL]"
exit 1
fi
desc="$1"
shift || true
module="$1"
shift || true
args="$@"
}
assert_root() {
if [ ! -w /dev ]; then
skip "please run as root"
fi
}
assert_have_module() {
if ! $modprobe -q -n $module; then
skip "module $module is not found"
fi
}
run_module() {
if $modprobe -q $module $args; then
$modprobe -q -r $module
say "ok"
else
fail ""
fi
}
say() {
echo "$desc: $1"
}
fail() {
say "$1 [FAIL]" >&2
exit 1
}
skip() {
say "$1 [SKIP]" >&2
# Kselftest framework requirement - SKIP code is 4.
exit 4
}
#
# Main script
#
main "$@"
...@@ -3,7 +3,16 @@ ...@@ -3,7 +3,16 @@
CC := $(CROSS_COMPILE)gcc CC := $(CROSS_COMPILE)gcc
ifeq (0,$(MAKELEVEL)) ifeq (0,$(MAKELEVEL))
OUTPUT := $(shell pwd) ifneq ($(O),)
OUTPUT := $(O)
else
ifneq ($(KBUILD_OUTPUT),)
OUTPUT := $(KBUILD_OUTPUT)
else
OUTPUT := $(shell pwd)
DEFAULT_INSTALL_HDR_PATH := 1
endif
endif
endif endif
# The following are built by lib.mk common compile rules. # The following are built by lib.mk common compile rules.
...@@ -21,9 +30,34 @@ top_srcdir ?= ../../../.. ...@@ -21,9 +30,34 @@ top_srcdir ?= ../../../..
include $(top_srcdir)/scripts/subarch.include include $(top_srcdir)/scripts/subarch.include
ARCH ?= $(SUBARCH) ARCH ?= $(SUBARCH)
# set default goal to all, so make without a target runs all, even when
# all isn't the first target in the file.
.DEFAULT_GOAL := all
# Invoke headers install with --no-builtin-rules to avoid circular
# dependency in "make kselftest" case. In this case, second level
# make inherits builtin-rules which will use the rule generate
# Makefile.o and runs into
# "Circular Makefile.o <- prepare dependency dropped."
# and headers_install fails and test compile fails.
# O= KBUILD_OUTPUT cases don't run into this error, since main Makefile
# invokes them as sub-makes and --no-builtin-rules is not necessary,
# but doesn't cause any failures. Keep it simple and use the same
# flags in both cases.
# Note that the support to install headers from lib.mk is necessary
# when test Makefile is run directly with "make -C".
# When local build is done, headers are installed in the default
# INSTALL_HDR_PATH usr/include.
.PHONY: khdr .PHONY: khdr
khdr: khdr:
make ARCH=$(ARCH) -C $(top_srcdir) headers_install ifndef KSFT_KHDR_INSTALL_DONE
ifeq (1,$(DEFAULT_INSTALL_HDR_PATH))
make --no-builtin-rules ARCH=$(ARCH) -C $(top_srcdir) headers_install
else
make --no-builtin-rules INSTALL_HDR_PATH=$$OUTPUT/usr \
ARCH=$(ARCH) -C $(top_srcdir) headers_install
endif
endif
all: khdr $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) all: khdr $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES)
else else
......
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests" # No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
all: all:
TEST_PROGS := printf.sh bitmap.sh prime_numbers.sh TEST_PROGS := printf.sh bitmap.sh prime_numbers.sh strscpy.sh
include ../lib.mk include ../lib.mk
#!/bin/sh #!/bin/sh
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
$(dirname $0)/../kselftest_module.sh "bitmap" test_bitmap
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
# Runs bitmap infrastructure tests using test_bitmap kernel module
if ! /sbin/modprobe -q -n test_bitmap; then
echo "bitmap: module test_bitmap is not found [SKIP]"
exit $ksft_skip
fi
if /sbin/modprobe -q test_bitmap; then
/sbin/modprobe -q -r test_bitmap
echo "bitmap: ok"
else
echo "bitmap: [FAIL]"
exit 1
fi
CONFIG_TEST_PRINTF=m CONFIG_TEST_PRINTF=m
CONFIG_TEST_BITMAP=m CONFIG_TEST_BITMAP=m
CONFIG_PRIME_NUMBERS=m CONFIG_PRIME_NUMBERS=m
CONFIG_TEST_STRSCPY=m
#!/bin/sh #!/bin/sh
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
# Checks fast/slow prime_number generation for inconsistencies # Checks fast/slow prime_number generation for inconsistencies
$(dirname $0)/../kselftest_module.sh "prime numbers" prime_numbers selftest=65536
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
if ! /sbin/modprobe -q -n prime_numbers; then
echo "prime_numbers: module prime_numbers is not found [SKIP]"
exit $ksft_skip
fi
if /sbin/modprobe -q prime_numbers selftest=65536; then
/sbin/modprobe -q -r prime_numbers
echo "prime_numbers: ok"
else
echo "prime_numbers: [FAIL]"
exit 1
fi
#!/bin/sh #!/bin/sh
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
# Runs printf infrastructure using test_printf kernel module # Tests the printf infrastructure using test_printf kernel module.
$(dirname $0)/../kselftest_module.sh "printf" test_printf
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
if ! /sbin/modprobe -q -n test_printf; then
echo "printf: module test_printf is not found [SKIP]"
exit $ksft_skip
fi
if /sbin/modprobe -q test_printf; then
/sbin/modprobe -q -r test_printf
echo "printf: ok"
else
echo "printf: [FAIL]"
exit 1
fi
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0+
$(dirname $0)/../kselftest_module.sh "strscpy*" test_strscpy
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sched.h>
#include <linux/rseq.h> #include <linux/rseq.h>
/* /*
......
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: GPL-2.0+ or MIT # SPDX-License-Identifier: GPL-2.0+ or MIT
NR_CPUS=`grep '^processor' /proc/cpuinfo | wc -l`
EXTRA_ARGS=${@} EXTRA_ARGS=${@}
OLDIFS="$IFS" OLDIFS="$IFS"
...@@ -28,15 +30,16 @@ IFS="$OLDIFS" ...@@ -28,15 +30,16 @@ IFS="$OLDIFS"
REPS=1000 REPS=1000
SLOW_REPS=100 SLOW_REPS=100
NR_THREADS=$((6*${NR_CPUS}))
function do_tests() function do_tests()
{ {
local i=0 local i=0
while [ "$i" -lt "${#TEST_LIST[@]}" ]; do while [ "$i" -lt "${#TEST_LIST[@]}" ]; do
echo "Running test ${TEST_NAME[$i]}" echo "Running test ${TEST_NAME[$i]}"
./param_test ${TEST_LIST[$i]} -r ${REPS} ${@} ${EXTRA_ARGS} || exit 1 ./param_test ${TEST_LIST[$i]} -r ${REPS} -t ${NR_THREADS} ${@} ${EXTRA_ARGS} || exit 1
echo "Running compare-twice test ${TEST_NAME[$i]}" echo "Running compare-twice test ${TEST_NAME[$i]}"
./param_test_compare_twice ${TEST_LIST[$i]} -r ${REPS} ${@} ${EXTRA_ARGS} || exit 1 ./param_test_compare_twice ${TEST_LIST[$i]} -r ${REPS} -t ${NR_THREADS} ${@} ${EXTRA_ARGS} || exit 1
let "i++" let "i++"
done done
} }
......
...@@ -3095,9 +3095,9 @@ TEST(user_notification_basic) ...@@ -3095,9 +3095,9 @@ TEST(user_notification_basic)
/* Check that we get -ENOSYS with no listener attached */ /* Check that we get -ENOSYS with no listener attached */
if (pid == 0) { if (pid == 0) {
if (user_trap_syscall(__NR_getpid, 0) < 0) if (user_trap_syscall(__NR_getppid, 0) < 0)
exit(1); exit(1);
ret = syscall(__NR_getpid); ret = syscall(__NR_getppid);
exit(ret >= 0 || errno != ENOSYS); exit(ret >= 0 || errno != ENOSYS);
} }
...@@ -3112,12 +3112,12 @@ TEST(user_notification_basic) ...@@ -3112,12 +3112,12 @@ TEST(user_notification_basic)
EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
/* Check that the basic notification machinery works */ /* Check that the basic notification machinery works */
listener = user_trap_syscall(__NR_getpid, listener = user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER); SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0); ASSERT_GE(listener, 0);
/* Installing a second listener in the chain should EBUSY */ /* Installing a second listener in the chain should EBUSY */
EXPECT_EQ(user_trap_syscall(__NR_getpid, EXPECT_EQ(user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER), SECCOMP_FILTER_FLAG_NEW_LISTENER),
-1); -1);
EXPECT_EQ(errno, EBUSY); EXPECT_EQ(errno, EBUSY);
...@@ -3126,7 +3126,7 @@ TEST(user_notification_basic) ...@@ -3126,7 +3126,7 @@ TEST(user_notification_basic)
ASSERT_GE(pid, 0); ASSERT_GE(pid, 0);
if (pid == 0) { if (pid == 0) {
ret = syscall(__NR_getpid); ret = syscall(__NR_getppid);
exit(ret != USER_NOTIF_MAGIC); exit(ret != USER_NOTIF_MAGIC);
} }
...@@ -3144,7 +3144,7 @@ TEST(user_notification_basic) ...@@ -3144,7 +3144,7 @@ TEST(user_notification_basic)
EXPECT_GT(poll(&pollfd, 1, -1), 0); EXPECT_GT(poll(&pollfd, 1, -1), 0);
EXPECT_EQ(pollfd.revents, POLLOUT); EXPECT_EQ(pollfd.revents, POLLOUT);
EXPECT_EQ(req.data.nr, __NR_getpid); EXPECT_EQ(req.data.nr, __NR_getppid);
resp.id = req.id; resp.id = req.id;
resp.error = 0; resp.error = 0;
...@@ -3176,7 +3176,7 @@ TEST(user_notification_kill_in_middle) ...@@ -3176,7 +3176,7 @@ TEST(user_notification_kill_in_middle)
TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
} }
listener = user_trap_syscall(__NR_getpid, listener = user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER); SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0); ASSERT_GE(listener, 0);
...@@ -3188,7 +3188,7 @@ TEST(user_notification_kill_in_middle) ...@@ -3188,7 +3188,7 @@ TEST(user_notification_kill_in_middle)
ASSERT_GE(pid, 0); ASSERT_GE(pid, 0);
if (pid == 0) { if (pid == 0) {
ret = syscall(__NR_getpid); ret = syscall(__NR_getppid);
exit(ret != USER_NOTIF_MAGIC); exit(ret != USER_NOTIF_MAGIC);
} }
...@@ -3298,7 +3298,7 @@ TEST(user_notification_closed_listener) ...@@ -3298,7 +3298,7 @@ TEST(user_notification_closed_listener)
TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
} }
listener = user_trap_syscall(__NR_getpid, listener = user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER); SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0); ASSERT_GE(listener, 0);
...@@ -3309,7 +3309,7 @@ TEST(user_notification_closed_listener) ...@@ -3309,7 +3309,7 @@ TEST(user_notification_closed_listener)
ASSERT_GE(pid, 0); ASSERT_GE(pid, 0);
if (pid == 0) { if (pid == 0) {
close(listener); close(listener);
ret = syscall(__NR_getpid); ret = syscall(__NR_getppid);
exit(ret != -1 && errno != ENOSYS); exit(ret != -1 && errno != ENOSYS);
} }
...@@ -3332,14 +3332,15 @@ TEST(user_notification_child_pid_ns) ...@@ -3332,14 +3332,15 @@ TEST(user_notification_child_pid_ns)
ASSERT_EQ(unshare(CLONE_NEWUSER | CLONE_NEWPID), 0); ASSERT_EQ(unshare(CLONE_NEWUSER | CLONE_NEWPID), 0);
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); listener = user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0); ASSERT_GE(listener, 0);
pid = fork(); pid = fork();
ASSERT_GE(pid, 0); ASSERT_GE(pid, 0);
if (pid == 0) if (pid == 0)
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC); exit(syscall(__NR_getppid) != USER_NOTIF_MAGIC);
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
EXPECT_EQ(req.pid, pid); EXPECT_EQ(req.pid, pid);
...@@ -3371,7 +3372,8 @@ TEST(user_notification_sibling_pid_ns) ...@@ -3371,7 +3372,8 @@ TEST(user_notification_sibling_pid_ns)
TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
} }
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); listener = user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0); ASSERT_GE(listener, 0);
pid = fork(); pid = fork();
...@@ -3384,7 +3386,7 @@ TEST(user_notification_sibling_pid_ns) ...@@ -3384,7 +3386,7 @@ TEST(user_notification_sibling_pid_ns)
ASSERT_GE(pid2, 0); ASSERT_GE(pid2, 0);
if (pid2 == 0) if (pid2 == 0)
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC); exit(syscall(__NR_getppid) != USER_NOTIF_MAGIC);
EXPECT_EQ(waitpid(pid2, &status, 0), pid2); EXPECT_EQ(waitpid(pid2, &status, 0), pid2);
EXPECT_EQ(true, WIFEXITED(status)); EXPECT_EQ(true, WIFEXITED(status));
...@@ -3393,11 +3395,11 @@ TEST(user_notification_sibling_pid_ns) ...@@ -3393,11 +3395,11 @@ TEST(user_notification_sibling_pid_ns)
} }
/* Create the sibling ns, and sibling in it. */ /* Create the sibling ns, and sibling in it. */
EXPECT_EQ(unshare(CLONE_NEWPID), 0); ASSERT_EQ(unshare(CLONE_NEWPID), 0);
EXPECT_EQ(errno, 0); ASSERT_EQ(errno, 0);
pid2 = fork(); pid2 = fork();
EXPECT_GE(pid2, 0); ASSERT_GE(pid2, 0);
if (pid2 == 0) { if (pid2 == 0) {
ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
...@@ -3405,7 +3407,7 @@ TEST(user_notification_sibling_pid_ns) ...@@ -3405,7 +3407,7 @@ TEST(user_notification_sibling_pid_ns)
* The pid should be 0, i.e. the task is in some namespace that * The pid should be 0, i.e. the task is in some namespace that
* we can't "see". * we can't "see".
*/ */
ASSERT_EQ(req.pid, 0); EXPECT_EQ(req.pid, 0);
resp.id = req.id; resp.id = req.id;
resp.error = 0; resp.error = 0;
...@@ -3435,14 +3437,15 @@ TEST(user_notification_fault_recv) ...@@ -3435,14 +3437,15 @@ TEST(user_notification_fault_recv)
ASSERT_EQ(unshare(CLONE_NEWUSER), 0); ASSERT_EQ(unshare(CLONE_NEWUSER), 0);
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); listener = user_trap_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0); ASSERT_GE(listener, 0);
pid = fork(); pid = fork();
ASSERT_GE(pid, 0); ASSERT_GE(pid, 0);
if (pid == 0) if (pid == 0)
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC); exit(syscall(__NR_getppid) != USER_NOTIF_MAGIC);
/* Do a bad recv() */ /* Do a bad recv() */
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, NULL), -1); EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, NULL), -1);
......
...@@ -32,7 +32,6 @@ ...@@ -32,7 +32,6 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/wait.h> #include <sys/wait.h>
#include "../kselftest.h" #include "../kselftest.h"
......
...@@ -8,9 +8,7 @@ ...@@ -8,9 +8,7 @@
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <errno.h> #include <errno.h>
#include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment