diff options
Diffstat (limited to 'tools/testing')
44 files changed, 2515 insertions, 37 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 400ee81a3043..313dc1091e6f 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -22,6 +22,7 @@ TARGETS += ir TARGETS += kcmp TARGETS += kvm TARGETS += lib +TARGETS += livepatch TARGETS += membarrier TARGETS += memfd TARGETS += memory-hotplug @@ -48,6 +49,7 @@ TARGETS += sysctl ifneq (1, $(quicktest)) TARGETS += timers endif +TARGETS += tmpfs TARGETS += user TARGETS += vm TARGETS += x86 diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config index 913a25a4a32b..bf634dda0720 100644 --- a/tools/testing/selftests/firmware/config +++ b/tools/testing/selftests/firmware/config @@ -1,6 +1,5 @@ CONFIG_TEST_FIRMWARE=y CONFIG_FW_LOADER=y CONFIG_FW_LOADER_USER_HELPER=y -CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y diff --git a/tools/testing/selftests/firmware/fw_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh index 466cf2f91ba0..a4320c4b44dc 100755 --- a/tools/testing/selftests/firmware/fw_filesystem.sh +++ b/tools/testing/selftests/firmware/fw_filesystem.sh @@ -155,8 +155,11 @@ read_firmwares() { for i in $(seq 0 3); do config_set_read_fw_idx $i - # Verify the contents match - if ! diff -q "$FW" $DIR/read_firmware 2>/dev/null ; then + # Verify the contents are what we expect. + # -Z required for now -- check for yourself, md5sum + # on $FW and DIR/read_firmware will yield the same. Even + # cmp agrees, so something is off. + if ! diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then echo "request #$i: firmware was not loaded" >&2 exit 1 fi @@ -168,7 +171,7 @@ read_firmwares_expect_nofile() for i in $(seq 0 3); do config_set_read_fw_idx $i # Ensures contents differ - if diff -q "$FW" $DIR/read_firmware 2>/dev/null ; then + if diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then echo "request $i: file was not expected to match" >&2 exit 1 fi diff --git a/tools/testing/selftests/firmware/fw_lib.sh b/tools/testing/selftests/firmware/fw_lib.sh index 6c5f1b2ffb74..1cbb12e284a6 100755 --- a/tools/testing/selftests/firmware/fw_lib.sh +++ b/tools/testing/selftests/firmware/fw_lib.sh @@ -91,7 +91,7 @@ verify_reqs() if [ "$TEST_REQS_FW_SYSFS_FALLBACK" = "yes" ]; then if [ ! "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then echo "usermode helper disabled so ignoring test" - exit $ksft_skip + exit 0 fi fi } diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest index 75244db70331..136387422b00 100755 --- a/tools/testing/selftests/ftrace/ftracetest +++ b/tools/testing/selftests/ftrace/ftracetest @@ -154,17 +154,17 @@ fi # Define text colors # Check available colors on the terminal, if any -ncolors=`tput colors 2>/dev/null` +ncolors=`tput colors 2>/dev/null || echo 0` color_reset= color_red= color_green= color_blue= # If stdout exists and number of colors is eight or more, use them -if [ -t 1 -a "$ncolors" -a "$ncolors" -ge 8 ]; then - color_reset="\e[0m" - color_red="\e[31m" - color_green="\e[32m" - color_blue="\e[34m" +if [ -t 1 -a "$ncolors" -ge 8 ]; then + color_reset="\033[0m" + color_red="\033[31m" + color_green="\033[32m" + color_blue="\033[34m" fi strip_esc() { @@ -173,8 +173,13 @@ strip_esc() { } prlog() { # messages - echo -e "$@" - [ "$LOG_FILE" ] && echo -e "$@" | strip_esc >> $LOG_FILE + newline="\n" + if [ "$1" = "-n" ] ; then + newline= + shift + fi + printf "$*$newline" + [ "$LOG_FILE" ] && printf "$*$newline" | strip_esc >> $LOG_FILE } catlog() { #file cat $1 diff --git a/tools/testing/selftests/ir/ir_loopback.c b/tools/testing/selftests/ir/ir_loopback.c index 858c19caf224..e700e09e3682 100644 --- a/tools/testing/selftests/ir/ir_loopback.c +++ b/tools/testing/selftests/ir/ir_loopback.c @@ -27,6 +27,8 @@ #define TEST_SCANCODES 10 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define SYSFS_PATH_MAX 256 +#define DNAME_PATH_MAX 256 static const struct { enum rc_proto proto; @@ -51,12 +53,16 @@ static const struct { { RC_PROTO_RC6_6A_32, "rc-6-6a-32", 0xffffffff, "rc-6" }, { RC_PROTO_RC6_MCE, "rc-6-mce", 0x00007fff, "rc-6" }, { RC_PROTO_SHARP, "sharp", 0x1fff, "sharp" }, + { RC_PROTO_IMON, "imon", 0x7fffffff, "imon" }, + { RC_PROTO_RCMM12, "rcmm-12", 0x00000fff, "rcmm" }, + { RC_PROTO_RCMM24, "rcmm-24", 0x00ffffff, "rcmm" }, + { RC_PROTO_RCMM32, "rcmm-32", 0xffffffff, "rcmm" }, }; int lirc_open(const char *rc) { struct dirent *dent; - char buf[100]; + char buf[SYSFS_PATH_MAX + DNAME_PATH_MAX]; DIR *d; int fd; @@ -74,7 +80,7 @@ int lirc_open(const char *rc) } if (!dent) - ksft_exit_fail_msg("cannot find lirc device for %s\n", rc); + ksft_exit_skip("cannot find lirc device for %s\n", rc); closedir(d); @@ -139,6 +145,11 @@ int main(int argc, char **argv) (((scancode >> 8) ^ ~scancode) & 0xff) == 0) continue; + if (rc_proto == RC_PROTO_RCMM32 && + (scancode & 0x000c0000) != 0x000c0000 && + scancode & 0x00008000) + continue; + struct lirc_scancode lsc = { .rc_proto = rc_proto, .scancode = scancode diff --git a/tools/testing/selftests/ir/ir_loopback.sh b/tools/testing/selftests/ir/ir_loopback.sh index 0a0b8dfa39be..b90dc9939f45 100755 --- a/tools/testing/selftests/ir/ir_loopback.sh +++ b/tools/testing/selftests/ir/ir_loopback.sh @@ -4,6 +4,11 @@ # Kselftest framework requirement - SKIP code is 4. ksft_skip=4 +if [ $UID != 0 ]; then + echo "Please run ir_loopback test as root [SKIP]" + exit $ksft_skip +fi + if ! /sbin/modprobe -q -n rc-loopback; then echo "ir_loopback: module rc-loopback is not found [SKIP]" exit $ksft_skip diff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h index a3edb2c8e43d..47e1d995c182 100644 --- a/tools/testing/selftests/kselftest.h +++ b/tools/testing/selftests/kselftest.h @@ -13,6 +13,7 @@ #include <stdlib.h> #include <unistd.h> #include <stdarg.h> +#include <stdio.h> /* define kselftest exit codes */ #define KSFT_PASS 0 diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index 76d654ef3234..2d90c98eeb67 100644 --- a/tools/testing/selftests/kselftest_harness.h +++ b/tools/testing/selftests/kselftest_harness.h @@ -168,8 +168,8 @@ #define __TEST_IMPL(test_name, _signal) \ static void test_name(struct __test_metadata *_metadata); \ static struct __test_metadata _##test_name##_object = \ - { name: "global." #test_name, \ - fn: &test_name, termsig: _signal }; \ + { .name = "global." #test_name, \ + .fn = &test_name, .termsig = _signal }; \ static void __attribute__((constructor)) _register_##test_name(void) \ { \ __register_test(&_##test_name##_object); \ @@ -304,9 +304,9 @@ } \ static struct __test_metadata \ _##fixture_name##_##test_name##_object = { \ - name: #fixture_name "." #test_name, \ - fn: &wrapper_##fixture_name##_##test_name, \ - termsig: signal, \ + .name = #fixture_name "." #test_name, \ + .fn = &wrapper_##fixture_name##_##test_name, \ + .termsig = signal, \ }; \ static void __attribute__((constructor)) \ _register_##fixture_name##_##test_name(void) \ diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/selftests/livepatch/Makefile new file mode 100644 index 000000000000..af4aee79bebb --- /dev/null +++ b/tools/testing/selftests/livepatch/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_GEN_PROGS := \ + test-livepatch.sh \ + test-callbacks.sh \ + test-shadow-vars.sh + +include ../lib.mk diff --git a/tools/testing/selftests/livepatch/README b/tools/testing/selftests/livepatch/README new file mode 100644 index 000000000000..b73cd0e2dd51 --- /dev/null +++ b/tools/testing/selftests/livepatch/README @@ -0,0 +1,43 @@ +==================== +Livepatch Self Tests +==================== + +This is a small set of sanity tests for the kernel livepatching. + +The test suite loads and unloads several test kernel modules to verify +livepatch behavior. Debug information is logged to the kernel's message +buffer and parsed for expected messages. (Note: the tests will clear +the message buffer between individual tests.) + + +Config +------ + +Set these config options and their prerequisites: + +CONFIG_LIVEPATCH=y +CONFIG_TEST_LIVEPATCH=m + + +Running the tests +----------------- + +Test kernel modules are built as part of lib/ (make modules) and need to +be installed (make modules_install) as the test scripts will modprobe +them. + +To run the livepatch selftests, from the top of the kernel source tree: + + % make -C tools/testing/selftests TARGETS=livepatch run_tests + + +Adding tests +------------ + +See the common functions.sh file for the existing collection of utility +functions, most importantly set_dynamic_debug() and check_result(). The +latter function greps the kernel's ring buffer for "livepatch:" and +"test_klp" strings, so tests be sure to include one of those strings for +result comparison. Other utility functions include general module +loading and livepatch loading helpers (waiting for patch transitions, +sysfs entries, etc.) diff --git a/tools/testing/selftests/livepatch/config b/tools/testing/selftests/livepatch/config new file mode 100644 index 000000000000..0dd7700464a8 --- /dev/null +++ b/tools/testing/selftests/livepatch/config @@ -0,0 +1 @@ +CONFIG_TEST_LIVEPATCH=m diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing/selftests/livepatch/functions.sh new file mode 100644 index 000000000000..30195449c63c --- /dev/null +++ b/tools/testing/selftests/livepatch/functions.sh @@ -0,0 +1,198 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com> + +# Shell functions for the rest of the scripts. + +MAX_RETRIES=600 +RETRY_INTERVAL=".1" # seconds + +# log(msg) - write message to kernel log +# msg - insightful words +function log() { + echo "$1" > /dev/kmsg +} + +# die(msg) - game over, man +# msg - dying words +function die() { + log "ERROR: $1" + echo "ERROR: $1" >&2 + exit 1 +} + +# set_dynamic_debug() - setup kernel dynamic debug +# TODO - push and pop this config? +function set_dynamic_debug() { + cat << EOF > /sys/kernel/debug/dynamic_debug/control +file kernel/livepatch/* +p +func klp_try_switch_task -p +EOF +} + +# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, +# sleep $RETRY_INTERVAL between attempts +# cmd - command and its arguments to run +function loop_until() { + local cmd="$*" + local i=0 + while true; do + eval "$cmd" && return 0 + [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 + sleep $RETRY_INTERVAL + done +} + +function is_livepatch_mod() { + local mod="$1" + + if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then + return 0 + fi + + return 1 +} + +function __load_mod() { + local mod="$1"; shift + + local msg="% modprobe $mod $*" + log "${msg%% }" + ret=$(modprobe "$mod" "$@" 2>&1) + if [[ "$ret" != "" ]]; then + die "$ret" + fi + + # Wait for module in sysfs ... + loop_until '[[ -e "/sys/module/$mod" ]]' || + die "failed to load module $mod" +} + + +# load_mod(modname, params) - load a kernel module +# modname - module name to load +# params - module parameters to pass to modprobe +function load_mod() { + local mod="$1"; shift + + is_livepatch_mod "$mod" && + die "use load_lp() to load the livepatch module $mod" + + __load_mod "$mod" "$@" +} + +# load_lp_nowait(modname, params) - load a kernel module with a livepatch +# but do not wait on until the transition finishes +# modname - module name to load +# params - module parameters to pass to modprobe +function load_lp_nowait() { + local mod="$1"; shift + + is_livepatch_mod "$mod" || + die "module $mod is not a livepatch" + + __load_mod "$mod" "$@" + + # Wait for livepatch in sysfs ... + loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' || + die "failed to load module $mod (sysfs)" +} + +# load_lp(modname, params) - load a kernel module with a livepatch +# modname - module name to load +# params - module parameters to pass to modprobe +function load_lp() { + local mod="$1"; shift + + load_lp_nowait "$mod" "$@" + + # Wait until the transition finishes ... + loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' || + die "failed to complete transition" +} + +# load_failing_mod(modname, params) - load a kernel module, expect to fail +# modname - module name to load +# params - module parameters to pass to modprobe +function load_failing_mod() { + local mod="$1"; shift + + local msg="% modprobe $mod $*" + log "${msg%% }" + ret=$(modprobe "$mod" "$@" 2>&1) + if [[ "$ret" == "" ]]; then + die "$mod unexpectedly loaded" + fi + log "$ret" +} + +# unload_mod(modname) - unload a kernel module +# modname - module name to unload +function unload_mod() { + local mod="$1" + + # Wait for module reference count to clear ... + loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || + die "failed to unload module $mod (refcnt)" + + log "% rmmod $mod" + ret=$(rmmod "$mod" 2>&1) + if [[ "$ret" != "" ]]; then + die "$ret" + fi + + # Wait for module in sysfs ... + loop_until '[[ ! -e "/sys/module/$mod" ]]' || + die "failed to unload module $mod (/sys/module)" +} + +# unload_lp(modname) - unload a kernel module with a livepatch +# modname - module name to unload +function unload_lp() { + unload_mod "$1" +} + +# disable_lp(modname) - disable a livepatch +# modname - module name to unload +function disable_lp() { + local mod="$1" + + log "% echo 0 > /sys/kernel/livepatch/$mod/enabled" + echo 0 > /sys/kernel/livepatch/"$mod"/enabled + + # Wait until the transition finishes and the livepatch gets + # removed from sysfs... + loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' || + die "failed to disable livepatch $mod" +} + +# set_pre_patch_ret(modname, pre_patch_ret) +# modname - module name to set +# pre_patch_ret - new pre_patch_ret value +function set_pre_patch_ret { + local mod="$1"; shift + local ret="$1" + + log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" + echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret + + # Wait for sysfs value to hold ... + loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || + die "failed to set pre_patch_ret parameter for $mod module" +} + +# check_result() - verify dmesg output +# TODO - better filter, out of order msgs, etc? +function check_result { + local expect="$*" + local result + + result=$(dmesg | grep -v 'tainting' | grep -e 'livepatch:' -e 'test_klp' | sed 's/^\[[ 0-9.]*\] //') + + if [[ "$expect" == "$result" ]] ; then + echo "ok" + else + echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" + die "livepatch kselftest(s) failed" + fi +} diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/testing/selftests/livepatch/test-callbacks.sh new file mode 100755 index 000000000000..e97a9dcb73c7 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-callbacks.sh @@ -0,0 +1,587 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com> + +. $(dirname $0)/functions.sh + +MOD_LIVEPATCH=test_klp_callbacks_demo +MOD_LIVEPATCH2=test_klp_callbacks_demo2 +MOD_TARGET=test_klp_callbacks_mod +MOD_TARGET_BUSY=test_klp_callbacks_busy + +set_dynamic_debug + + +# TEST: target module before livepatch +# +# Test a combination of loading a kernel module and a livepatch that +# patches a function in the first module. Load the target module +# before the livepatch module. Unload them in the same order. +# +# - On livepatch enable, before the livepatch transition starts, +# pre-patch callbacks are executed for vmlinux and $MOD_TARGET (those +# klp_objects currently loaded). After klp_objects are patched +# according to the klp_patch, their post-patch callbacks run and the +# transition completes. +# +# - Similarly, on livepatch disable, pre-patch callbacks run before the +# unpatching transition starts. klp_objects are reverted, post-patch +# callbacks execute and the transition completes. + +echo -n "TEST: target module before livepatch ... " +dmesg -C + +load_mod $MOD_TARGET +load_lp $MOD_LIVEPATCH +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + + +# TEST: module_coming notifier +# +# This test is similar to the previous test, but (un)load the livepatch +# module before the target kernel module. This tests the livepatch +# core's module_coming handler. +# +# - On livepatch enable, only pre/post-patch callbacks are executed for +# currently loaded klp_objects, in this case, vmlinux. +# +# - When a targeted module is subsequently loaded, only its +# pre/post-patch callbacks are executed. +# +# - On livepatch disable, all currently loaded klp_objects' (vmlinux and +# $MOD_TARGET) pre/post-unpatch callbacks are executed. + +echo -n "TEST: module_coming notifier ... " +dmesg -C + +load_lp $MOD_LIVEPATCH +load_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_TARGET: ${MOD_TARGET}_init +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + + +# TEST: module_going notifier +# +# Test loading the livepatch after a targeted kernel module, then unload +# the kernel module before disabling the livepatch. This tests the +# livepatch core's module_going handler. +# +# - First load a target module, then the livepatch. +# +# - When a target module is unloaded, the livepatch is only reverted +# from that klp_object ($MOD_TARGET). As such, only its pre and +# post-unpatch callbacks are executed when this occurs. +# +# - When the livepatch is disabled, pre and post-unpatch callbacks are +# run for the remaining klp_object, vmlinux. + +echo -n "TEST: module_going notifier ... " +dmesg -C + +load_mod $MOD_TARGET +load_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': patching complete +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET' +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# TEST: module_coming and module_going notifiers +# +# This test is similar to the previous test, however the livepatch is +# loaded first. This tests the livepatch core's module_coming and +# module_going handlers. +# +# - First load the livepatch. +# +# - When a targeted kernel module is subsequently loaded, only its +# pre/post-patch callbacks are executed. +# +# - When the target module is unloaded, the livepatch is only reverted +# from the $MOD_TARGET klp_object. As such, only pre and +# post-unpatch callbacks are executed when this occurs. + +echo -n "TEST: module_coming and module_going notifiers ... " +dmesg -C + +load_lp $MOD_LIVEPATCH +load_mod $MOD_TARGET +unload_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_TARGET: ${MOD_TARGET}_init +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET' +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# TEST: target module not present +# +# A simple test of loading a livepatch without one of its patch target +# klp_objects ever loaded ($MOD_TARGET). +# +# - Load the livepatch. +# +# - As expected, only pre/post-(un)patch handlers are executed for +# vmlinux. + +echo -n "TEST: target module not present ... " +dmesg -C + +load_lp $MOD_LIVEPATCH +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# TEST: pre-patch callback -ENODEV +# +# Test a scenario where a vmlinux pre-patch callback returns a non-zero +# status (ie, failure). +# +# - First load a target module. +# +# - Load the livepatch module, setting its 'pre_patch_ret' value to -19 +# (-ENODEV). When its vmlinux pre-patch callback executes, this +# status code will propagate back to the module-loading subsystem. +# The result is that the insmod command refuses to load the livepatch +# module. + +echo -n "TEST: pre-patch callback -ENODEV ... " +dmesg -C + +load_mod $MOD_TARGET +load_failing_mod $MOD_LIVEPATCH pre_patch_ret=-19 +unload_mod $MOD_TARGET + +check_result "% modprobe $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_init +% modprobe $MOD_LIVEPATCH pre_patch_ret=-19 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +test_klp_callbacks_demo: pre_patch_callback: vmlinux +livepatch: pre-patch callback failed for object 'vmlinux' +livepatch: failed to enable patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': canceling patching transition, going to unpatch +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +modprobe: ERROR: could not insert '$MOD_LIVEPATCH': No such device +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + + +# TEST: module_coming + pre-patch callback -ENODEV +# +# Similar to the previous test, setup a livepatch such that its vmlinux +# pre-patch callback returns success. However, when a targeted kernel +# module is later loaded, have the livepatch return a failing status +# code. +# +# - Load the livepatch, vmlinux pre-patch callback succeeds. +# +# - Set a trap so subsequent pre-patch callbacks to this livepatch will +# return -ENODEV. +# +# - The livepatch pre-patch callback for subsequently loaded target +# modules will return failure, so the module loader refuses to load +# the kernel module. No post-patch or pre/post-unpatch callbacks are +# executed for this klp_object. +# +# - Pre/post-unpatch callbacks are run for the vmlinux klp_object. + +echo -n "TEST: module_coming + pre-patch callback -ENODEV ... " +dmesg -C + +load_lp $MOD_LIVEPATCH +set_pre_patch_ret $MOD_LIVEPATCH -19 +load_failing_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': patching complete +% echo -19 > /sys/module/$MOD_LIVEPATCH/parameters/pre_patch_ret +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +livepatch: pre-patch callback failed for object '$MOD_TARGET' +livepatch: patch '$MOD_LIVEPATCH' failed for module '$MOD_TARGET', refusing to load module '$MOD_TARGET' +modprobe: ERROR: could not insert '$MOD_TARGET': No such device +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# TEST: multiple target modules +# +# Test loading multiple targeted kernel modules. This test-case is +# mainly for comparing with the next test-case. +# +# - Load a target "busy" kernel module which kicks off a worker function +# that immediately exits. +# +# - Proceed with loading the livepatch and another ordinary target +# module. Post-patch callbacks are executed and the transition +# completes quickly. + +echo -n "TEST: multiple target modules ... " +dmesg -C + +load_mod $MOD_TARGET_BUSY sleep_secs=0 +# give $MOD_TARGET_BUSY::busymod_work_func() a chance to run +sleep 5 +load_lp $MOD_LIVEPATCH +load_mod $MOD_TARGET +unload_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET_BUSY + +check_result "% modprobe $MOD_TARGET_BUSY sleep_secs=0 +$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init +$MOD_TARGET_BUSY: busymod_work_func, sleeping 0 seconds ... +$MOD_TARGET_BUSY: busymod_work_func exit +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_TARGET: ${MOD_TARGET}_init +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET' +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET_BUSY +$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit" + + + +# TEST: busy target module +# +# A similar test as the previous one, but force the "busy" kernel module +# to do longer work. +# +# The livepatching core will refuse to patch a task that is currently +# executing a to-be-patched function -- the consistency model stalls the +# current patch transition until this safety-check is met. Test a +# scenario where one of a livepatch's target klp_objects sits on such a +# function for a long time. Meanwhile, load and unload other target +# kernel modules while the livepatch transition is in progress. +# +# - Load the "busy" kernel module, this time make it do 10 seconds worth +# of work. +# +# - Meanwhile, the livepatch is loaded. Notice that the patch +# transition does not complete as the targeted "busy" module is +# sitting on a to-be-patched function. +# +# - Load a second target module (this one is an ordinary idle kernel +# module). Note that *no* post-patch callbacks will be executed while +# the livepatch is still in transition. +# +# - Request an unload of the simple kernel module. The patch is still +# transitioning, so its pre-unpatch callbacks are skipped. +# +# - Finally the livepatch is disabled. Since none of the patch's +# klp_object's post-patch callbacks executed, the remaining +# klp_object's pre-unpatch callbacks are skipped. + +echo -n "TEST: busy target module ... " +dmesg -C + +load_mod $MOD_TARGET_BUSY sleep_secs=10 +load_lp_nowait $MOD_LIVEPATCH +# Don't wait for transition, load $MOD_TARGET while the transition +# is still stalled in $MOD_TARGET_BUSY::busymod_work_func() +sleep 5 +load_mod $MOD_TARGET +unload_mod $MOD_TARGET +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH +unload_mod $MOD_TARGET_BUSY + +check_result "% modprobe $MOD_TARGET_BUSY sleep_secs=10 +$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init +$MOD_TARGET_BUSY: busymod_work_func, sleeping 10 seconds ... +% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': starting patching transition +% modprobe $MOD_TARGET +livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET' +$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init +$MOD_TARGET: ${MOD_TARGET}_init +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit +livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET' +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': reversing transition from patching to unpatching +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH +% rmmod $MOD_TARGET_BUSY +$MOD_TARGET_BUSY: busymod_work_func exit +$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit" + + +# TEST: multiple livepatches +# +# Test loading multiple livepatches. This test-case is mainly for comparing +# with the next test-case. +# +# - Load and unload two livepatches, pre and post (un)patch callbacks +# execute as each patch progresses through its (un)patching +# transition. + +echo -n "TEST: multiple livepatches ... " +dmesg -C + +load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH2 +disable_lp $MOD_LIVEPATCH2 +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_LIVEPATCH2 +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +$MOD_LIVEPATCH2: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +$MOD_LIVEPATCH2: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled +livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition +$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': starting unpatching transition +livepatch: '$MOD_LIVEPATCH2': completing unpatching transition +$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': unpatching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH2 +% rmmod $MOD_LIVEPATCH" + + +# TEST: atomic replace +# +# Load multiple livepatches, but the second as an 'atomic-replace' +# patch. When the latter loads, the original livepatch should be +# disabled and *none* of its pre/post-unpatch callbacks executed. On +# the other hand, when the atomic-replace livepatch is disabled, its +# pre/post-unpatch callbacks *should* be executed. +# +# - Load and unload two livepatches, the second of which has its +# .replace flag set true. +# +# - Pre and post patch callbacks are executed for both livepatches. +# +# - Once the atomic replace module is loaded, only its pre and post +# unpatch callbacks are executed. + +echo -n "TEST: atomic replace ... " +dmesg -C + +load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH2 replace=1 +disable_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH2 +unload_lp $MOD_LIVEPATCH + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH': patching complete +% modprobe $MOD_LIVEPATCH2 replace=1 +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +$MOD_LIVEPATCH2: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +$MOD_LIVEPATCH2: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled +livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition +$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': starting unpatching transition +livepatch: '$MOD_LIVEPATCH2': completing unpatching transition +$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH2': unpatching complete +% rmmod $MOD_LIVEPATCH2 +% rmmod $MOD_LIVEPATCH" + + +exit 0 diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh new file mode 100755 index 000000000000..f05268aea859 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-livepatch.sh @@ -0,0 +1,168 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com> + +. $(dirname $0)/functions.sh + +MOD_LIVEPATCH=test_klp_livepatch +MOD_REPLACE=test_klp_atomic_replace + +set_dynamic_debug + + +# TEST: basic function patching +# - load a livepatch that modifies the output from /proc/cmdline and +# verify correct behavior +# - unload the livepatch and make sure the patch was removed + +echo -n "TEST: basic function patching ... " +dmesg -C + +load_lp $MOD_LIVEPATCH + +if [[ "$(cat /proc/cmdline)" != "$MOD_LIVEPATCH: this has been live patched" ]] ; then + echo -e "FAIL\n\n" + die "livepatch kselftest(s) failed" +fi + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +if [[ "$(cat /proc/cmdline)" == "$MOD_LIVEPATCH: this has been live patched" ]] ; then + echo -e "FAIL\n\n" + die "livepatch kselftest(s) failed" +fi + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# TEST: multiple livepatches +# - load a livepatch that modifies the output from /proc/cmdline and +# verify correct behavior +# - load another livepatch and verify that both livepatches are active +# - unload the second livepatch and verify that the first is still active +# - unload the first livepatch and verify none are active + +echo -n "TEST: multiple livepatches ... " +dmesg -C + +load_lp $MOD_LIVEPATCH + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +load_lp $MOD_REPLACE replace=0 + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +disable_lp $MOD_REPLACE +unload_lp $MOD_REPLACE + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +$MOD_LIVEPATCH: this has been live patched +% modprobe $MOD_REPLACE replace=0 +livepatch: enabling patch '$MOD_REPLACE' +livepatch: '$MOD_REPLACE': initializing patching transition +livepatch: '$MOD_REPLACE': starting patching transition +livepatch: '$MOD_REPLACE': completing patching transition +livepatch: '$MOD_REPLACE': patching complete +$MOD_LIVEPATCH: this has been live patched +$MOD_REPLACE: this has been live patched +% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled +livepatch: '$MOD_REPLACE': initializing unpatching transition +livepatch: '$MOD_REPLACE': starting unpatching transition +livepatch: '$MOD_REPLACE': completing unpatching transition +livepatch: '$MOD_REPLACE': unpatching complete +% rmmod $MOD_REPLACE +$MOD_LIVEPATCH: this has been live patched +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + + +# TEST: atomic replace livepatch +# - load a livepatch that modifies the output from /proc/cmdline and +# verify correct behavior +# - load an atomic replace livepatch and verify that only the second is active +# - remove the first livepatch and verify that the atomic replace livepatch +# is still active +# - remove the atomic replace livepatch and verify that none are active + +echo -n "TEST: atomic replace livepatch ... " +dmesg -C + +load_lp $MOD_LIVEPATCH + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +load_lp $MOD_REPLACE replace=1 + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +unload_lp $MOD_LIVEPATCH + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +disable_lp $MOD_REPLACE +unload_lp $MOD_REPLACE + +grep 'live patched' /proc/cmdline > /dev/kmsg +grep 'live patched' /proc/meminfo > /dev/kmsg + +check_result "% modprobe $MOD_LIVEPATCH +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +$MOD_LIVEPATCH: this has been live patched +% modprobe $MOD_REPLACE replace=1 +livepatch: enabling patch '$MOD_REPLACE' +livepatch: '$MOD_REPLACE': initializing patching transition +livepatch: '$MOD_REPLACE': starting patching transition +livepatch: '$MOD_REPLACE': completing patching transition +livepatch: '$MOD_REPLACE': patching complete +$MOD_REPLACE: this has been live patched +% rmmod $MOD_LIVEPATCH +$MOD_REPLACE: this has been live patched +% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled +livepatch: '$MOD_REPLACE': initializing unpatching transition +livepatch: '$MOD_REPLACE': starting unpatching transition +livepatch: '$MOD_REPLACE': completing unpatching transition +livepatch: '$MOD_REPLACE': unpatching complete +% rmmod $MOD_REPLACE" + + +exit 0 diff --git a/tools/testing/selftests/livepatch/test-shadow-vars.sh b/tools/testing/selftests/livepatch/test-shadow-vars.sh new file mode 100755 index 000000000000..04a37831e204 --- /dev/null +++ b/tools/testing/selftests/livepatch/test-shadow-vars.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com> + +. $(dirname $0)/functions.sh + +MOD_TEST=test_klp_shadow_vars + +set_dynamic_debug + + +# TEST: basic shadow variable API +# - load a module that exercises the shadow variable API + +echo -n "TEST: basic shadow variable API ... " +dmesg -C + +load_mod $MOD_TEST +unload_mod $MOD_TEST + +check_result "% modprobe $MOD_TEST +$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR0 +$MOD_TEST: got expected NULL result +$MOD_TEST: shadow_ctor: PTR6 -> PTR1 +$MOD_TEST: klp_shadow_alloc(obj=PTR5, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR1 = PTR6 +$MOD_TEST: shadow_ctor: PTR8 -> PTR2 +$MOD_TEST: klp_shadow_alloc(obj=PTR9, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR2 = PTR8 +$MOD_TEST: shadow_ctor: PTR10 -> PTR3 +$MOD_TEST: klp_shadow_alloc(obj=PTR5, id=0x1235, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR3 = PTR10 +$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR6 +$MOD_TEST: got expected PTR6 -> PTR1 result +$MOD_TEST: klp_shadow_get(obj=PTR9, id=0x1234) = PTR8 +$MOD_TEST: got expected PTR8 -> PTR2 result +$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1235) = PTR10 +$MOD_TEST: got expected PTR10 -> PTR3 result +$MOD_TEST: shadow_ctor: PTR11 -> PTR4 +$MOD_TEST: klp_shadow_get_or_alloc(obj=PTR12, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR4 = PTR11 +$MOD_TEST: klp_shadow_get_or_alloc(obj=PTR12, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR4 = PTR11 +$MOD_TEST: got expected PTR11 -> PTR4 result +$MOD_TEST: shadow_dtor(obj=PTR5, shadow_data=PTR6) +$MOD_TEST: klp_shadow_free(obj=PTR5, id=0x1234, dtor=PTR13) +$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR0 +$MOD_TEST: got expected NULL result +$MOD_TEST: shadow_dtor(obj=PTR9, shadow_data=PTR8) +$MOD_TEST: klp_shadow_free(obj=PTR9, id=0x1234, dtor=PTR13) +$MOD_TEST: klp_shadow_get(obj=PTR9, id=0x1234) = PTR0 +$MOD_TEST: got expected NULL result +$MOD_TEST: shadow_dtor(obj=PTR12, shadow_data=PTR11) +$MOD_TEST: klp_shadow_free(obj=PTR12, id=0x1234, dtor=PTR13) +$MOD_TEST: klp_shadow_get(obj=PTR12, id=0x1234) = PTR0 +$MOD_TEST: got expected NULL result +$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1235) = PTR10 +$MOD_TEST: got expected PTR10 -> PTR3 result +$MOD_TEST: shadow_dtor(obj=PTR5, shadow_data=PTR10) +$MOD_TEST: klp_shadow_free_all(id=0x1235, dtor=PTR13) +$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR0 +$MOD_TEST: shadow_get() got expected NULL result +% rmmod test_klp_shadow_vars" + +exit 0 diff --git a/tools/testing/selftests/memfd/memfd_test.c b/tools/testing/selftests/memfd/memfd_test.c index 10baa1652fc2..c67d32eeb668 100644 --- a/tools/testing/selftests/memfd/memfd_test.c +++ b/tools/testing/selftests/memfd/memfd_test.c @@ -54,6 +54,22 @@ static int mfd_assert_new(const char *name, loff_t sz, unsigned int flags) return fd; } +static int mfd_assert_reopen_fd(int fd_in) +{ + int r, fd; + char path[100]; + + sprintf(path, "/proc/self/fd/%d", fd_in); + + fd = open(path, O_RDWR); + if (fd < 0) { + printf("re-open of existing fd %d failed\n", fd_in); + abort(); + } + + return fd; +} + static void mfd_fail_new(const char *name, unsigned int flags) { int r; @@ -255,6 +271,25 @@ static void mfd_assert_read(int fd) munmap(p, mfd_def_size); } +/* Test that PROT_READ + MAP_SHARED mappings work. */ +static void mfd_assert_read_shared(int fd) +{ + void *p; + + /* verify PROT_READ and MAP_SHARED *is* allowed */ + p = mmap(NULL, + mfd_def_size, + PROT_READ, + MAP_SHARED, + fd, + 0); + if (p == MAP_FAILED) { + printf("mmap() failed: %m\n"); + abort(); + } + munmap(p, mfd_def_size); +} + static void mfd_assert_write(int fd) { ssize_t l; @@ -693,6 +728,44 @@ static void test_seal_write(void) } /* + * Test SEAL_FUTURE_WRITE + * Test whether SEAL_FUTURE_WRITE actually prevents modifications. + */ +static void test_seal_future_write(void) +{ + int fd, fd2; + void *p; + + printf("%s SEAL-FUTURE-WRITE\n", memfd_str); + + fd = mfd_assert_new("kern_memfd_seal_future_write", + mfd_def_size, + MFD_CLOEXEC | MFD_ALLOW_SEALING); + + p = mfd_assert_mmap_shared(fd); + + mfd_assert_has_seals(fd, 0); + + mfd_assert_add_seals(fd, F_SEAL_FUTURE_WRITE); + mfd_assert_has_seals(fd, F_SEAL_FUTURE_WRITE); + + /* read should pass, writes should fail */ + mfd_assert_read(fd); + mfd_assert_read_shared(fd); + mfd_fail_write(fd); + + fd2 = mfd_assert_reopen_fd(fd); + /* read should pass, writes should still fail */ + mfd_assert_read(fd2); + mfd_assert_read_shared(fd2); + mfd_fail_write(fd2); + + munmap(p, mfd_def_size); + close(fd2); + close(fd); +} + +/* * Test SEAL_SHRINK * Test whether SEAL_SHRINK actually prevents shrinking */ @@ -945,6 +1018,7 @@ int main(int argc, char **argv) test_basic(); test_seal_write(); + test_seal_future_write(); test_seal_shrink(); test_seal_grow(); test_seal_resize(); diff --git a/tools/testing/selftests/powerpc/benchmarks/null_syscall.c b/tools/testing/selftests/powerpc/benchmarks/null_syscall.c index ecc14d68e101..908de689a902 100644 --- a/tools/testing/selftests/powerpc/benchmarks/null_syscall.c +++ b/tools/testing/selftests/powerpc/benchmarks/null_syscall.c @@ -25,7 +25,7 @@ unsigned long long clock_frequency; unsigned long long timebase_frequency; double timebase_multiplier; -static inline unsigned long long mftb(void) +static inline unsigned long mftb(void) { unsigned long low; diff --git a/tools/testing/selftests/powerpc/include/reg.h b/tools/testing/selftests/powerpc/include/reg.h index 52b4710469d2..96043b9b9829 100644 --- a/tools/testing/selftests/powerpc/include/reg.h +++ b/tools/testing/selftests/powerpc/include/reg.h @@ -77,6 +77,14 @@ #define TEXASR_TE 0x0000000004000000 #define TEXASR_ROT 0x0000000002000000 +/* MSR register bits */ +#define MSR_TS_S_LG 33 /* Trans Mem state: Suspended */ + +#define __MASK(X) (1UL<<(X)) + +/* macro to check TM MSR bits */ +#define MSR_TS_S __MASK(MSR_TS_S_LG) /* Transaction Suspended */ + /* Vector Instructions */ #define VSX_XX1(xs, ra, rb) (((xs) & 0x1f) << 21 | ((ra) << 16) | \ ((rb) << 11) | (((xs) >> 5))) diff --git a/tools/testing/selftests/powerpc/include/utils.h b/tools/testing/selftests/powerpc/include/utils.h index ae43a614835d..7636bf45d5d5 100644 --- a/tools/testing/selftests/powerpc/include/utils.h +++ b/tools/testing/selftests/powerpc/include/utils.h @@ -102,8 +102,10 @@ do { \ #if defined(__powerpc64__) #define UCONTEXT_NIA(UC) (UC)->uc_mcontext.gp_regs[PT_NIP] +#define UCONTEXT_MSR(UC) (UC)->uc_mcontext.gp_regs[PT_MSR] #elif defined(__powerpc__) #define UCONTEXT_NIA(UC) (UC)->uc_mcontext.uc_regs->gregs[PT_NIP] +#define UCONTEXT_MSR(UC) (UC)->uc_mcontext.uc_regs->gregs[PT_MSR] #else #error implement UCONTEXT_NIA #endif diff --git a/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c b/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c index 167135bd92a8..af1b80265076 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c @@ -11,7 +11,6 @@ #include <sys/wait.h> #include <unistd.h> #include <setjmp.h> -#include <signal.h> #include "ebb.h" diff --git a/tools/testing/selftests/powerpc/tm/.gitignore b/tools/testing/selftests/powerpc/tm/.gitignore index 208452a93e2c..951fe855f7cd 100644 --- a/tools/testing/selftests/powerpc/tm/.gitignore +++ b/tools/testing/selftests/powerpc/tm/.gitignore @@ -11,6 +11,7 @@ tm-signal-context-chk-fpu tm-signal-context-chk-gpr tm-signal-context-chk-vmx tm-signal-context-chk-vsx +tm-signal-context-force-tm tm-signal-sigreturn-nt tm-vmx-unavail tm-unavailable diff --git a/tools/testing/selftests/powerpc/tm/Makefile b/tools/testing/selftests/powerpc/tm/Makefile index 75a685359129..c0734ed0ef56 100644 --- a/tools/testing/selftests/powerpc/tm/Makefile +++ b/tools/testing/selftests/powerpc/tm/Makefile @@ -4,7 +4,8 @@ SIGNAL_CONTEXT_CHK_TESTS := tm-signal-context-chk-gpr tm-signal-context-chk-fpu TEST_GEN_PROGS := tm-resched-dscr tm-syscall tm-signal-msr-resv tm-signal-stack \ tm-vmxcopy tm-fork tm-tar tm-tmspr tm-vmx-unavail tm-unavailable tm-trap \ - $(SIGNAL_CONTEXT_CHK_TESTS) tm-sigreturn tm-signal-sigreturn-nt + $(SIGNAL_CONTEXT_CHK_TESTS) tm-sigreturn tm-signal-sigreturn-nt \ + tm-signal-context-force-tm top_srcdir = ../../../../.. include ../../lib.mk @@ -20,6 +21,7 @@ $(OUTPUT)/tm-vmx-unavail: CFLAGS += -pthread -m64 $(OUTPUT)/tm-resched-dscr: ../pmu/lib.c $(OUTPUT)/tm-unavailable: CFLAGS += -O0 -pthread -m64 -Wno-error=uninitialized -mvsx $(OUTPUT)/tm-trap: CFLAGS += -O0 -pthread -m64 +$(OUTPUT)/tm-signal-context-force-tm: CFLAGS += -pthread -m64 SIGNAL_CONTEXT_CHK_TESTS := $(patsubst %,$(OUTPUT)/%,$(SIGNAL_CONTEXT_CHK_TESTS)) $(SIGNAL_CONTEXT_CHK_TESTS): tm-signal.S diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c new file mode 100644 index 000000000000..31717625f318 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2018, Breno Leitao, Gustavo Romero, IBM Corp. + * + * This test raises a SIGUSR1 signal, and toggle the MSR[TS] + * fields at the signal handler. With MSR[TS] being set, the kernel will + * force a recheckpoint, which may cause a segfault when returning to + * user space. Since the test needs to re-run, the segfault needs to be + * caught and handled. + * + * In order to continue the test even after a segfault, the context is + * saved prior to the signal being raised, and it is restored when there is + * a segmentation fault. This happens for COUNT_MAX times. + * + * This test never fails (as returning EXIT_FAILURE). It either succeeds, + * or crash the kernel (on a buggy kernel). + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <ucontext.h> +#include <unistd.h> +#include <sys/mman.h> + +#include "tm.h" +#include "utils.h" +#include "reg.h" + +#define COUNT_MAX 5000 /* Number of interactions */ + +/* + * This test only runs on 64 bits system. Unsetting MSR_TS_S to avoid + * compilation issue on 32 bits system. There is no side effect, since the + * whole test will be skipped if it is not running on 64 bits system. + */ +#ifndef __powerpc64__ +#undef MSR_TS_S +#define MSR_TS_S 0 +#endif + +/* Setting contexts because the test will crash and we want to recover */ +ucontext_t init_context, main_context; + +static int count, first_time; + +void usr_signal_handler(int signo, siginfo_t *si, void *uc) +{ + ucontext_t *ucp = uc; + int ret; + + /* + * Allocating memory in a signal handler, and never freeing it on + * purpose, forcing the heap increase, so, the memory leak is what + * we want here. + */ + ucp->uc_link = mmap(NULL, sizeof(ucontext_t), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + if (ucp->uc_link == (void *)-1) { + perror("Mmap failed"); + exit(-1); + } + + /* Forcing the page to be allocated in a page fault */ + ret = madvise(ucp->uc_link, sizeof(ucontext_t), MADV_DONTNEED); + if (ret) { + perror("madvise failed"); + exit(-1); + } + + memcpy(&ucp->uc_link->uc_mcontext, &ucp->uc_mcontext, + sizeof(ucp->uc_mcontext)); + + /* Forcing to enable MSR[TM] */ + UCONTEXT_MSR(ucp) |= MSR_TS_S; + + /* + * A fork inside a signal handler seems to be more efficient than a + * fork() prior to the signal being raised. + */ + if (fork() == 0) { + /* + * Both child and parent will return, but, child returns + * with count set so it will exit in the next segfault. + * Parent will continue to loop. + */ + count = COUNT_MAX; + } + + /* + * If the change above does not hit the bug, it will cause a + * segmentation fault, since the ck structures are NULL. + */ +} + +void seg_signal_handler(int signo, siginfo_t *si, void *uc) +{ + if (count == COUNT_MAX) { + /* Return to tm_signal_force_msr() and exit */ + setcontext(&main_context); + } + + count++; + + /* Reexecute the test */ + setcontext(&init_context); +} + +void tm_trap_test(void) +{ + struct sigaction usr_sa, seg_sa; + stack_t ss; + + usr_sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + usr_sa.sa_sigaction = usr_signal_handler; + + seg_sa.sa_flags = SA_SIGINFO; + seg_sa.sa_sigaction = seg_signal_handler; + + /* + * Set initial context. Will get back here from + * seg_signal_handler() + */ + getcontext(&init_context); + + /* Allocated an alternative signal stack area */ + ss.ss_sp = mmap(NULL, SIGSTKSZ, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + ss.ss_size = SIGSTKSZ; + ss.ss_flags = 0; + + if (ss.ss_sp == (void *)-1) { + perror("mmap error\n"); + exit(-1); + } + + /* Force the allocation through a page fault */ + if (madvise(ss.ss_sp, SIGSTKSZ, MADV_DONTNEED)) { + perror("madvise\n"); + exit(-1); + } + + /* Setting an alternative stack to generate a page fault when + * the signal is raised. + */ + if (sigaltstack(&ss, NULL)) { + perror("sigaltstack\n"); + exit(-1); + } + + /* The signal handler will enable MSR_TS */ + sigaction(SIGUSR1, &usr_sa, NULL); + /* If it does not crash, it will segfault, avoid it to retest */ + sigaction(SIGSEGV, &seg_sa, NULL); + + raise(SIGUSR1); +} + +int tm_signal_context_force_tm(void) +{ + SKIP_IF(!have_htm()); + /* + * Skipping if not running on 64 bits system, since I think it is + * not possible to set mcontext's [MSR] with TS, due to it being 32 + * bits. + */ + SKIP_IF(!is_ppc64le()); + + /* Will get back here after COUNT_MAX interactions */ + getcontext(&main_context); + + if (!first_time++) + tm_trap_test(); + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) +{ + test_harness(tm_signal_context_force_tm, "tm_signal_context_force_tm"); +} diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore index 29bac5ef9a93..444ad39d3700 100644 --- a/tools/testing/selftests/proc/.gitignore +++ b/tools/testing/selftests/proc/.gitignore @@ -2,6 +2,7 @@ /fd-002-posix-eq /fd-003-kthread /proc-loadavg-001 +/proc-pid-vm /proc-self-map-files-001 /proc-self-map-files-002 /proc-self-syscall diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile index 434d033ee067..5163dc887aa3 100644 --- a/tools/testing/selftests/proc/Makefile +++ b/tools/testing/selftests/proc/Makefile @@ -6,6 +6,7 @@ TEST_GEN_PROGS += fd-001-lookup TEST_GEN_PROGS += fd-002-posix-eq TEST_GEN_PROGS += fd-003-kthread TEST_GEN_PROGS += proc-loadavg-001 +TEST_GEN_PROGS += proc-pid-vm TEST_GEN_PROGS += proc-self-map-files-001 TEST_GEN_PROGS += proc-self-map-files-002 TEST_GEN_PROGS += proc-self-syscall diff --git a/tools/testing/selftests/proc/proc-loadavg-001.c b/tools/testing/selftests/proc/proc-loadavg-001.c index fcff7047000d..471e2aa28077 100644 --- a/tools/testing/selftests/proc/proc-loadavg-001.c +++ b/tools/testing/selftests/proc/proc-loadavg-001.c @@ -30,7 +30,7 @@ int main(void) if (unshare(CLONE_NEWPID) == -1) { if (errno == ENOSYS || errno == EPERM) - return 2; + return 4; return 1; } diff --git a/tools/testing/selftests/proc/proc-pid-vm.c b/tools/testing/selftests/proc/proc-pid-vm.c new file mode 100644 index 000000000000..bbe8150d18aa --- /dev/null +++ b/tools/testing/selftests/proc/proc-pid-vm.c @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2019 Alexey Dobriyan <adobriyan@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* + * Fork and exec tiny 1 page executable which precisely controls its VM. + * Test /proc/$PID/maps + * Test /proc/$PID/smaps + * Test /proc/$PID/smaps_rollup + * Test /proc/$PID/statm + * + * FIXME require CONFIG_TMPFS which can be disabled + * FIXME test other values from "smaps" + * FIXME support other archs + */ +#undef NDEBUG +#include <assert.h> +#include <errno.h> +#include <sched.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <sys/uio.h> +#include <linux/kdev_t.h> + +static inline long sys_execveat(int dirfd, const char *pathname, char **argv, char **envp, int flags) +{ + return syscall(SYS_execveat, dirfd, pathname, argv, envp, flags); +} + +static void make_private_tmp(void) +{ + if (unshare(CLONE_NEWNS) == -1) { + if (errno == ENOSYS || errno == EPERM) { + exit(4); + } + exit(1); + } + if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) == -1) { + exit(1); + } + if (mount(NULL, "/tmp", "tmpfs", 0, NULL) == -1) { + exit(1); + } +} + +static pid_t pid = -1; +static void ate(void) +{ + if (pid > 0) { + kill(pid, SIGTERM); + } +} + +struct elf64_hdr { + uint8_t e_ident[16]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint64_t e_entry; + uint64_t e_phoff; + uint64_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +}; + +struct elf64_phdr { + uint32_t p_type; + uint32_t p_flags; + uint64_t p_offset; + uint64_t p_vaddr; + uint64_t p_paddr; + uint64_t p_filesz; + uint64_t p_memsz; + uint64_t p_align; +}; + +#ifdef __x86_64__ +#define PAGE_SIZE 4096 +#define VADDR (1UL << 32) +#define MAPS_OFFSET 73 + +#define syscall 0x0f, 0x05 +#define mov_rdi(x) \ + 0x48, 0xbf, \ + (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \ + ((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff + +#define mov_rsi(x) \ + 0x48, 0xbe, \ + (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \ + ((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff + +#define mov_eax(x) \ + 0xb8, (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff + +static const uint8_t payload[] = { + /* Casually unmap stack, vDSO and everything else. */ + /* munmap */ + mov_rdi(VADDR + 4096), + mov_rsi((1ULL << 47) - 4096 - VADDR - 4096), + mov_eax(11), + syscall, + + /* Ping parent. */ + /* write(0, &c, 1); */ + 0x31, 0xff, /* xor edi, edi */ + 0x48, 0x8d, 0x35, 0x00, 0x00, 0x00, 0x00, /* lea rsi, [rip] */ + 0xba, 0x01, 0x00, 0x00, 0x00, /* mov edx, 1 */ + mov_eax(1), + syscall, + + /* 1: pause(); */ + mov_eax(34), + syscall, + + 0xeb, 0xf7, /* jmp 1b */ +}; + +static int make_exe(const uint8_t *payload, size_t len) +{ + struct elf64_hdr h; + struct elf64_phdr ph; + + struct iovec iov[3] = { + {&h, sizeof(struct elf64_hdr)}, + {&ph, sizeof(struct elf64_phdr)}, + {(void *)payload, len}, + }; + int fd, fd1; + char buf[64]; + + memset(&h, 0, sizeof(h)); + h.e_ident[0] = 0x7f; + h.e_ident[1] = 'E'; + h.e_ident[2] = 'L'; + h.e_ident[3] = 'F'; + h.e_ident[4] = 2; + h.e_ident[5] = 1; + h.e_ident[6] = 1; + h.e_ident[7] = 0; + h.e_type = 2; + h.e_machine = 0x3e; + h.e_version = 1; + h.e_entry = VADDR + sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr); + h.e_phoff = sizeof(struct elf64_hdr); + h.e_shoff = 0; + h.e_flags = 0; + h.e_ehsize = sizeof(struct elf64_hdr); + h.e_phentsize = sizeof(struct elf64_phdr); + h.e_phnum = 1; + h.e_shentsize = 0; + h.e_shnum = 0; + h.e_shstrndx = 0; + + memset(&ph, 0, sizeof(ph)); + ph.p_type = 1; + ph.p_flags = (1<<2)|1; + ph.p_offset = 0; + ph.p_vaddr = VADDR; + ph.p_paddr = 0; + ph.p_filesz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + sizeof(payload); + ph.p_memsz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + sizeof(payload); + ph.p_align = 4096; + + fd = openat(AT_FDCWD, "/tmp", O_WRONLY|O_EXCL|O_TMPFILE, 0700); + if (fd == -1) { + exit(1); + } + + if (writev(fd, iov, 3) != sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len) { + exit(1); + } + + /* Avoid ETXTBSY on exec. */ + snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd); + fd1 = open(buf, O_RDONLY|O_CLOEXEC); + close(fd); + + return fd1; +} +#endif + +#ifdef __x86_64__ +int main(void) +{ + int pipefd[2]; + int exec_fd; + + atexit(ate); + + make_private_tmp(); + + /* Reserve fd 0 for 1-byte pipe ping from child. */ + close(0); + if (open("/", O_RDONLY|O_DIRECTORY|O_PATH) != 0) { + return 1; + } + + exec_fd = make_exe(payload, sizeof(payload)); + + if (pipe(pipefd) == -1) { + return 1; + } + if (dup2(pipefd[1], 0) != 0) { + return 1; + } + + pid = fork(); + if (pid == -1) { + return 1; + } + if (pid == 0) { + sys_execveat(exec_fd, "", NULL, NULL, AT_EMPTY_PATH); + return 1; + } + + char _; + if (read(pipefd[0], &_, 1) != 1) { + return 1; + } + + struct stat st; + if (fstat(exec_fd, &st) == -1) { + return 1; + } + + /* Generate "head -n1 /proc/$PID/maps" */ + char buf0[256]; + memset(buf0, ' ', sizeof(buf0)); + int len = snprintf(buf0, sizeof(buf0), + "%08lx-%08lx r-xp 00000000 %02lx:%02lx %llu", + VADDR, VADDR + PAGE_SIZE, + MAJOR(st.st_dev), MINOR(st.st_dev), + (unsigned long long)st.st_ino); + buf0[len] = ' '; + snprintf(buf0 + MAPS_OFFSET, sizeof(buf0) - MAPS_OFFSET, + "/tmp/#%llu (deleted)\n", (unsigned long long)st.st_ino); + + + /* Test /proc/$PID/maps */ + { + char buf[256]; + ssize_t rv; + int fd; + + snprintf(buf, sizeof(buf), "/proc/%u/maps", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + return 1; + } + rv = read(fd, buf, sizeof(buf)); + assert(rv == strlen(buf0)); + assert(memcmp(buf, buf0, strlen(buf0)) == 0); + } + + /* Test /proc/$PID/smaps */ + { + char buf[1024]; + ssize_t rv; + int fd; + + snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + return 1; + } + rv = read(fd, buf, sizeof(buf)); + assert(0 <= rv && rv <= sizeof(buf)); + + assert(rv >= strlen(buf0)); + assert(memcmp(buf, buf0, strlen(buf0)) == 0); + +#define RSS1 "Rss: 4 kB\n" +#define RSS2 "Rss: 0 kB\n" +#define PSS1 "Pss: 4 kB\n" +#define PSS2 "Pss: 0 kB\n" + assert(memmem(buf, rv, RSS1, strlen(RSS1)) || + memmem(buf, rv, RSS2, strlen(RSS2))); + assert(memmem(buf, rv, PSS1, strlen(PSS1)) || + memmem(buf, rv, PSS2, strlen(PSS2))); + + static const char *S[] = { + "Size: 4 kB\n", + "KernelPageSize: 4 kB\n", + "MMUPageSize: 4 kB\n", + "Anonymous: 0 kB\n", + "AnonHugePages: 0 kB\n", + "Shared_Hugetlb: 0 kB\n", + "Private_Hugetlb: 0 kB\n", + "Locked: 0 kB\n", + }; + int i; + + for (i = 0; i < sizeof(S)/sizeof(S[0]); i++) { + assert(memmem(buf, rv, S[i], strlen(S[i]))); + } + } + + /* Test /proc/$PID/smaps_rollup */ + { + char bufr[256]; + memset(bufr, ' ', sizeof(bufr)); + len = snprintf(bufr, sizeof(bufr), + "%08lx-%08lx ---p 00000000 00:00 0", + VADDR, VADDR + PAGE_SIZE); + bufr[len] = ' '; + snprintf(bufr + MAPS_OFFSET, sizeof(bufr) - MAPS_OFFSET, + "[rollup]\n"); + + char buf[1024]; + ssize_t rv; + int fd; + + snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + return 1; + } + rv = read(fd, buf, sizeof(buf)); + assert(0 <= rv && rv <= sizeof(buf)); + + assert(rv >= strlen(bufr)); + assert(memcmp(buf, bufr, strlen(bufr)) == 0); + + assert(memmem(buf, rv, RSS1, strlen(RSS1)) || + memmem(buf, rv, RSS2, strlen(RSS2))); + assert(memmem(buf, rv, PSS1, strlen(PSS1)) || + memmem(buf, rv, PSS2, strlen(PSS2))); + + static const char *S[] = { + "Anonymous: 0 kB\n", + "AnonHugePages: 0 kB\n", + "Shared_Hugetlb: 0 kB\n", + "Private_Hugetlb: 0 kB\n", + "Locked: 0 kB\n", + }; + int i; + + for (i = 0; i < sizeof(S)/sizeof(S[0]); i++) { + assert(memmem(buf, rv, S[i], strlen(S[i]))); + } + } + + /* Test /proc/$PID/statm */ + { + char buf[64]; + ssize_t rv; + int fd; + + snprintf(buf, sizeof(buf), "/proc/%u/statm", pid); + fd = open(buf, O_RDONLY); + if (fd == -1) { + return 1; + } + rv = read(fd, buf, sizeof(buf)); + assert(rv == 7 * 2); + + assert(buf[0] == '1'); /* ->total_vm */ + assert(buf[1] == ' '); + assert(buf[2] == '0' || buf[2] == '1'); /* rss */ + assert(buf[3] == ' '); + assert(buf[4] == '0' || buf[2] == '1'); /* file rss */ + assert(buf[5] == ' '); + assert(buf[6] == '1'); /* ELF executable segments */ + assert(buf[7] == ' '); + assert(buf[8] == '0'); + assert(buf[9] == ' '); + assert(buf[10] == '0'); /* ->data_vm + ->stack_vm */ + assert(buf[11] == ' '); + assert(buf[12] == '0'); + assert(buf[13] == '\n'); + } + + return 0; +} +#else +int main(void) +{ + return 4; +} +#endif diff --git a/tools/testing/selftests/proc/proc-self-map-files-002.c b/tools/testing/selftests/proc/proc-self-map-files-002.c index 85744425b08d..762cb01f2ca7 100644 --- a/tools/testing/selftests/proc/proc-self-map-files-002.c +++ b/tools/testing/selftests/proc/proc-self-map-files-002.c @@ -63,7 +63,7 @@ int main(void) p = mmap((void *)va, PAGE_SIZE, PROT_NONE, MAP_PRIVATE|MAP_FILE|MAP_FIXED, fd, 0); if (p == MAP_FAILED) { if (errno == EPERM) - return 2; + return 4; return 1; } diff --git a/tools/testing/selftests/proc/proc-self-syscall.c b/tools/testing/selftests/proc/proc-self-syscall.c index 5ab5f4810e43..9f6d000c0245 100644 --- a/tools/testing/selftests/proc/proc-self-syscall.c +++ b/tools/testing/selftests/proc/proc-self-syscall.c @@ -20,7 +20,6 @@ #include <sys/stat.h> #include <fcntl.h> #include <errno.h> -#include <unistd.h> #include <string.h> #include <stdio.h> @@ -39,7 +38,7 @@ int main(void) fd = open("/proc/self/syscall", O_RDONLY); if (fd == -1) { if (errno == ENOENT) - return 2; + return 4; return 1; } diff --git a/tools/testing/selftests/proc/proc-self-wchan.c b/tools/testing/selftests/proc/proc-self-wchan.c index a38b2fbaa7ad..b467b98a457d 100644 --- a/tools/testing/selftests/proc/proc-self-wchan.c +++ b/tools/testing/selftests/proc/proc-self-wchan.c @@ -27,7 +27,7 @@ int main(void) fd = open("/proc/self/wchan", O_RDONLY); if (fd == -1) { if (errno == ENOENT) - return 2; + return 4; return 1; } diff --git a/tools/testing/selftests/proc/read.c b/tools/testing/selftests/proc/read.c index 563e752e6eba..b3ef9e14d6cc 100644 --- a/tools/testing/selftests/proc/read.c +++ b/tools/testing/selftests/proc/read.c @@ -26,8 +26,10 @@ #include <dirent.h> #include <stdbool.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> #include <sys/stat.h> +#include <sys/vfs.h> #include <fcntl.h> #include <unistd.h> @@ -123,10 +125,22 @@ static void f(DIR *d, unsigned int level) int main(void) { DIR *d; + struct statfs sfs; d = opendir("/proc"); if (!d) + return 4; + + /* Ensure /proc is proc. */ + if (fstatfs(dirfd(d), &sfs) == -1) { + return 1; + } + if (sfs.f_type != 0x9fa0) { + fprintf(stderr, "error: unexpected f_type %lx\n", (long)sfs.f_type); return 2; + } + f(d, 0); + return 0; } diff --git a/tools/testing/selftests/safesetid/.gitignore b/tools/testing/selftests/safesetid/.gitignore new file mode 100644 index 000000000000..9c1a629bca01 --- /dev/null +++ b/tools/testing/selftests/safesetid/.gitignore @@ -0,0 +1 @@ +safesetid-test diff --git a/tools/testing/selftests/safesetid/Makefile b/tools/testing/selftests/safesetid/Makefile new file mode 100644 index 000000000000..98da7a504737 --- /dev/null +++ b/tools/testing/selftests/safesetid/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for mount selftests. +CFLAGS = -Wall -lcap -O2 + +TEST_PROGS := run_tests.sh +TEST_GEN_FILES := safesetid-test + +include ../lib.mk diff --git a/tools/testing/selftests/safesetid/config b/tools/testing/selftests/safesetid/config new file mode 100644 index 000000000000..9d44e5c2e096 --- /dev/null +++ b/tools/testing/selftests/safesetid/config @@ -0,0 +1,2 @@ +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y diff --git a/tools/testing/selftests/safesetid/safesetid-test.c b/tools/testing/selftests/safesetid/safesetid-test.c new file mode 100644 index 000000000000..892c8e8b1b8b --- /dev/null +++ b/tools/testing/selftests/safesetid/safesetid-test.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <pwd.h> +#include <string.h> +#include <syscall.h> +#include <sys/capability.h> +#include <sys/types.h> +#include <sys/mount.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdarg.h> + +#ifndef CLONE_NEWUSER +# define CLONE_NEWUSER 0x10000000 +#endif + +#define ROOT_USER 0 +#define RESTRICTED_PARENT 1 +#define ALLOWED_CHILD1 2 +#define ALLOWED_CHILD2 3 +#define NO_POLICY_USER 4 + +char* add_whitelist_policy_file = "/sys/kernel/security/safesetid/add_whitelist_policy"; + +static void die(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +static bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap) +{ + char buf[4096]; + int fd; + ssize_t written; + int buf_len; + + buf_len = vsnprintf(buf, sizeof(buf), fmt, ap); + if (buf_len < 0) { + printf("vsnprintf failed: %s\n", + strerror(errno)); + return false; + } + if (buf_len >= sizeof(buf)) { + printf("vsnprintf output truncated\n"); + return false; + } + + fd = open(filename, O_WRONLY); + if (fd < 0) { + if ((errno == ENOENT) && enoent_ok) + return true; + return false; + } + written = write(fd, buf, buf_len); + if (written != buf_len) { + if (written >= 0) { + printf("short write to %s\n", filename); + return false; + } else { + printf("write to %s failed: %s\n", + filename, strerror(errno)); + return false; + } + } + if (close(fd) != 0) { + printf("close of %s failed: %s\n", + filename, strerror(errno)); + return false; + } + return true; +} + +static bool write_file(char *filename, char *fmt, ...) +{ + va_list ap; + bool ret; + + va_start(ap, fmt); + ret = vmaybe_write_file(false, filename, fmt, ap); + va_end(ap); + + return ret; +} + +static void ensure_user_exists(uid_t uid) +{ + struct passwd p; + + FILE *fd; + char name_str[10]; + + if (getpwuid(uid) == NULL) { + memset(&p,0x00,sizeof(p)); + fd=fopen("/etc/passwd","a"); + if (fd == NULL) + die("couldn't open file\n"); + if (fseek(fd, 0, SEEK_END)) + die("couldn't fseek\n"); + snprintf(name_str, 10, "%d", uid); + p.pw_name=name_str; + p.pw_uid=uid; + p.pw_gecos="Test account"; + p.pw_dir="/dev/null"; + p.pw_shell="/bin/false"; + int value = putpwent(&p,fd); + if (value != 0) + die("putpwent failed\n"); + if (fclose(fd)) + die("fclose failed\n"); + } +} + +static void ensure_securityfs_mounted(void) +{ + int fd = open(add_whitelist_policy_file, O_WRONLY); + if (fd < 0) { + if (errno == ENOENT) { + // Need to mount securityfs + if (mount("securityfs", "/sys/kernel/security", + "securityfs", 0, NULL) < 0) + die("mounting securityfs failed\n"); + } else { + die("couldn't find securityfs for unknown reason\n"); + } + } else { + if (close(fd) != 0) { + die("close of %s failed: %s\n", + add_whitelist_policy_file, strerror(errno)); + } + } +} + +static void write_policies(void) +{ + ssize_t written; + int fd; + + fd = open(add_whitelist_policy_file, O_WRONLY); + if (fd < 0) + die("cant open add_whitelist_policy file\n"); + written = write(fd, "1:2", strlen("1:2")); + if (written != strlen("1:2")) { + if (written >= 0) { + die("short write to %s\n", add_whitelist_policy_file); + } else { + die("write to %s failed: %s\n", + add_whitelist_policy_file, strerror(errno)); + } + } + written = write(fd, "1:3", strlen("1:3")); + if (written != strlen("1:3")) { + if (written >= 0) { + die("short write to %s\n", add_whitelist_policy_file); + } else { + die("write to %s failed: %s\n", + add_whitelist_policy_file, strerror(errno)); + } + } + if (close(fd) != 0) { + die("close of %s failed: %s\n", + add_whitelist_policy_file, strerror(errno)); + } +} + +static bool test_userns(bool expect_success) +{ + uid_t uid; + char map_file_name[32]; + size_t sz = sizeof(map_file_name); + pid_t cpid; + bool success; + + uid = getuid(); + + int clone_flags = CLONE_NEWUSER; + cpid = syscall(SYS_clone, clone_flags, NULL); + if (cpid == -1) { + printf("clone failed"); + return false; + } + + if (cpid == 0) { /* Code executed by child */ + // Give parent 1 second to write map file + sleep(1); + exit(EXIT_SUCCESS); + } else { /* Code executed by parent */ + if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) { + printf("preparing file name string failed"); + return false; + } + success = write_file(map_file_name, "0 0 1", uid); + return success == expect_success; + } + + printf("should not reach here"); + return false; +} + +static void test_setuid(uid_t child_uid, bool expect_success) +{ + pid_t cpid, w; + int wstatus; + + cpid = fork(); + if (cpid == -1) { + die("fork\n"); + } + + if (cpid == 0) { /* Code executed by child */ + setuid(child_uid); + if (getuid() == child_uid) + exit(EXIT_SUCCESS); + else + exit(EXIT_FAILURE); + } else { /* Code executed by parent */ + do { + w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED); + if (w == -1) { + die("waitpid\n"); + } + + if (WIFEXITED(wstatus)) { + if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) { + if (expect_success) { + return; + } else { + die("unexpected success\n"); + } + } else { + if (expect_success) { + die("unexpected failure\n"); + } else { + return; + } + } + } else if (WIFSIGNALED(wstatus)) { + if (WTERMSIG(wstatus) == 9) { + if (expect_success) + die("killed unexpectedly\n"); + else + return; + } else { + die("unexpected signal: %d\n", wstatus); + } + } else { + die("unexpected status: %d\n", wstatus); + } + } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); + } + + die("should not reach here\n"); +} + +static void ensure_users_exist(void) +{ + ensure_user_exists(ROOT_USER); + ensure_user_exists(RESTRICTED_PARENT); + ensure_user_exists(ALLOWED_CHILD1); + ensure_user_exists(ALLOWED_CHILD2); + ensure_user_exists(NO_POLICY_USER); +} + +static void drop_caps(bool setid_retained) +{ + cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID}; + cap_t caps; + + caps = cap_get_proc(); + if (setid_retained) + cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET); + else + cap_clear(caps); + cap_set_proc(caps); + cap_free(caps); +} + +int main(int argc, char **argv) +{ + ensure_users_exist(); + ensure_securityfs_mounted(); + write_policies(); + + if (prctl(PR_SET_KEEPCAPS, 1L)) + die("Error with set keepcaps\n"); + + // First test to make sure we can write userns mappings from a user + // that doesn't have any restrictions (as long as it has CAP_SETUID); + setuid(NO_POLICY_USER); + setgid(NO_POLICY_USER); + + // Take away all but setid caps + drop_caps(true); + + // Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map + // from non-root parent process. + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)) + die("Error with set dumpable\n"); + + if (!test_userns(true)) { + die("test_userns failed when it should work\n"); + } + + setuid(RESTRICTED_PARENT); + setgid(RESTRICTED_PARENT); + + test_setuid(ROOT_USER, false); + test_setuid(ALLOWED_CHILD1, true); + test_setuid(ALLOWED_CHILD2, true); + test_setuid(NO_POLICY_USER, false); + + if (!test_userns(false)) { + die("test_userns worked when it should fail\n"); + } + + // Now take away all caps + drop_caps(false); + test_setuid(2, false); + test_setuid(3, false); + test_setuid(4, false); + + // NOTE: this test doesn't clean up users that were created in + // /etc/passwd or flush policies that were added to the LSM. + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/safesetid/safesetid-test.sh b/tools/testing/selftests/safesetid/safesetid-test.sh new file mode 100755 index 000000000000..e4fdce675c54 --- /dev/null +++ b/tools/testing/selftests/safesetid/safesetid-test.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +TCID="safesetid-test.sh" +errcode=0 + +# Kselftest framework requirement - SKIP code is 4. +ksft_skip=4 + +check_root() +{ + uid=$(id -u) + if [ $uid -ne 0 ]; then + echo $TCID: must be run as root >&2 + exit $ksft_skip + fi +} + +main_function() +{ + check_root + ./safesetid-test +} + +main_function +echo "$TCID: done" +exit $errcode diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 7e632b465ab4..f69d2ee29742 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -2611,6 +2611,7 @@ TEST_F(TSYNC, two_siblings_not_under_filter) { long ret, sib; void *status; + struct timespec delay = { .tv_nsec = 100000000 }; ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); @@ -2664,7 +2665,7 @@ TEST_F(TSYNC, two_siblings_not_under_filter) EXPECT_EQ(SIBLING_EXIT_UNKILLED, (long)status); /* Poll for actual task death. pthread_join doesn't guarantee it. */ while (!kill(self->sibling[sib].system_tid, 0)) - sleep(0.1); + nanosleep(&delay, NULL); /* Switch to the remaining sibling */ sib = !sib; @@ -2689,7 +2690,7 @@ TEST_F(TSYNC, two_siblings_not_under_filter) EXPECT_EQ(0, (long)status); /* Poll for actual task death. pthread_join doesn't guarantee it. */ while (!kill(self->sibling[sib].system_tid, 0)) - sleep(0.1); + nanosleep(&delay, NULL); ret = seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &self->apply_prog); @@ -2971,6 +2972,12 @@ TEST(get_metadata) struct seccomp_metadata md; long ret; + /* Only real root can get metadata. */ + if (geteuid()) { + XFAIL(return, "get_metadata requires real root"); + return; + } + ASSERT_EQ(0, pipe(pipefd)); pid = fork(); @@ -2985,11 +2992,11 @@ TEST(get_metadata) }; /* one with log, one without */ - ASSERT_EQ(0, seccomp(SECCOMP_SET_MODE_FILTER, + EXPECT_EQ(0, seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_LOG, &prog)); - ASSERT_EQ(0, seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog)); + EXPECT_EQ(0, seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog)); - ASSERT_EQ(0, close(pipefd[0])); + EXPECT_EQ(0, close(pipefd[0])); ASSERT_EQ(1, write(pipefd[1], "1", 1)); ASSERT_EQ(0, close(pipefd[1])); @@ -3062,6 +3069,11 @@ TEST(user_notification_basic) .filter = filter, }; + ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + ASSERT_EQ(0, ret) { + TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); + } + pid = fork(); ASSERT_GE(pid, 0); @@ -3077,7 +3089,7 @@ TEST(user_notification_basic) EXPECT_EQ(true, WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); - /* Add some no-op filters so for grins. */ + /* Add some no-op filters for grins. */ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); @@ -3143,6 +3155,11 @@ TEST(user_notification_kill_in_middle) struct seccomp_notif req = {}; struct seccomp_notif_resp resp = {}; + ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + ASSERT_EQ(0, ret) { + TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); + } + listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); ASSERT_GE(listener, 0); @@ -3190,6 +3207,11 @@ TEST(user_notification_signal) struct seccomp_notif_resp resp = {}; char c; + ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + ASSERT_EQ(0, ret) { + TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); + } + ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0); listener = user_trap_syscall(__NR_gettid, @@ -3255,6 +3277,11 @@ TEST(user_notification_closed_listener) long ret; int status, listener; + ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + ASSERT_EQ(0, ret) { + TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); + } + listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); ASSERT_GE(listener, 0); @@ -3287,7 +3314,7 @@ TEST(user_notification_child_pid_ns) struct seccomp_notif req = {}; struct seccomp_notif_resp resp = {}; - ASSERT_EQ(unshare(CLONE_NEWPID), 0); + ASSERT_EQ(unshare(CLONE_NEWUSER | CLONE_NEWPID), 0); listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); ASSERT_GE(listener, 0); @@ -3324,6 +3351,10 @@ TEST(user_notification_sibling_pid_ns) struct seccomp_notif req = {}; struct seccomp_notif_resp resp = {}; + ASSERT_EQ(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0) { + TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!"); + } + listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); ASSERT_GE(listener, 0); @@ -3386,6 +3417,8 @@ TEST(user_notification_fault_recv) struct seccomp_notif req = {}; struct seccomp_notif_resp resp = {}; + ASSERT_EQ(unshare(CLONE_NEWUSER), 0); + listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER); ASSERT_GE(listener, 0); diff --git a/tools/testing/selftests/tmpfs/.gitignore b/tools/testing/selftests/tmpfs/.gitignore new file mode 100644 index 000000000000..a96838fad74d --- /dev/null +++ b/tools/testing/selftests/tmpfs/.gitignore @@ -0,0 +1 @@ +/bug-link-o-tmpfile diff --git a/tools/testing/selftests/tmpfs/Makefile b/tools/testing/selftests/tmpfs/Makefile new file mode 100644 index 000000000000..953c81299181 --- /dev/null +++ b/tools/testing/selftests/tmpfs/Makefile @@ -0,0 +1,7 @@ +CFLAGS += -Wall -O2 +CFLAGS += -D_GNU_SOURCE + +TEST_GEN_PROGS := +TEST_GEN_PROGS += bug-link-o-tmpfile + +include ../lib.mk diff --git a/tools/testing/selftests/tmpfs/bug-link-o-tmpfile.c b/tools/testing/selftests/tmpfs/bug-link-o-tmpfile.c new file mode 100644 index 000000000000..b5c3ddb90942 --- /dev/null +++ b/tools/testing/selftests/tmpfs/bug-link-o-tmpfile.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Alexey Dobriyan <adobriyan@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* Test that open(O_TMPFILE), linkat() doesn't screw accounting. */ +#include <errno.h> +#include <sched.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mount.h> +#include <unistd.h> + +int main(void) +{ + int fd; + + if (unshare(CLONE_NEWNS) == -1) { + if (errno == ENOSYS || errno == EPERM) { + fprintf(stderr, "error: unshare, errno %d\n", errno); + return 4; + } + fprintf(stderr, "error: unshare, errno %d\n", errno); + return 1; + } + if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) == -1) { + fprintf(stderr, "error: mount '/', errno %d\n", errno); + return 1; + } + + /* Our heroes: 1 root inode, 1 O_TMPFILE inode, 1 permanent inode. */ + if (mount(NULL, "/tmp", "tmpfs", 0, "nr_inodes=3") == -1) { + fprintf(stderr, "error: mount tmpfs, errno %d\n", errno); + return 1; + } + + fd = openat(AT_FDCWD, "/tmp", O_WRONLY|O_TMPFILE, 0600); + if (fd == -1) { + fprintf(stderr, "error: open 1, errno %d\n", errno); + return 1; + } + if (linkat(fd, "", AT_FDCWD, "/tmp/1", AT_EMPTY_PATH) == -1) { + fprintf(stderr, "error: linkat, errno %d\n", errno); + return 1; + } + close(fd); + + fd = openat(AT_FDCWD, "/tmp", O_WRONLY|O_TMPFILE, 0600); + if (fd == -1) { + fprintf(stderr, "error: open 2, errno %d\n", errno); + return 1; + } + + return 0; +} diff --git a/tools/testing/selftests/vm/map_hugetlb.c b/tools/testing/selftests/vm/map_hugetlb.c index 9b777fa95f09..5a2d7b8efc40 100644 --- a/tools/testing/selftests/vm/map_hugetlb.c +++ b/tools/testing/selftests/vm/map_hugetlb.c @@ -23,6 +23,14 @@ #define MAP_HUGETLB 0x40000 /* arch specific */ #endif +#ifndef MAP_HUGE_SHIFT +#define MAP_HUGE_SHIFT 26 +#endif + +#ifndef MAP_HUGE_MASK +#define MAP_HUGE_MASK 0x3f +#endif + /* Only ia64 requires this */ #ifdef __ia64__ #define ADDR (void *)(0x8000000000000000UL) @@ -58,12 +66,29 @@ static int read_bytes(char *addr) return 0; } -int main(void) +int main(int argc, char **argv) { void *addr; int ret; + size_t length = LENGTH; + int flags = FLAGS; + int shift = 0; + + if (argc > 1) + length = atol(argv[1]) << 20; + if (argc > 2) { + shift = atoi(argv[2]); + if (shift) + flags |= (shift & MAP_HUGE_MASK) << MAP_HUGE_SHIFT; + } + + if (shift) + printf("%u kB hugepages\n", 1 << shift); + else + printf("Default size hugepages\n"); + printf("Mapping %lu Mbytes\n", (unsigned long)length >> 20); - addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, -1, 0); + addr = mmap(ADDR, length, PROTECTION, flags, -1, 0); if (addr == MAP_FAILED) { perror("mmap"); exit(1); diff --git a/tools/testing/selftests/vm/run_vmtests b/tools/testing/selftests/vm/run_vmtests index 584a91ae4a8f..951c507a27f7 100755 --- a/tools/testing/selftests/vm/run_vmtests +++ b/tools/testing/selftests/vm/run_vmtests @@ -211,4 +211,20 @@ else echo "[PASS]" fi +echo "------------------------------------" +echo "running vmalloc stability smoke test" +echo "------------------------------------" +./test_vmalloc.sh smoke +ret_val=$? + +if [ $ret_val -eq 0 ]; then + echo "[PASS]" +elif [ $ret_val -eq $ksft_skip ]; then + echo "[SKIP]" + exitcode=$ksft_skip +else + echo "[FAIL]" + exitcode=1 +fi + exit $exitcode diff --git a/tools/testing/selftests/vm/test_vmalloc.sh b/tools/testing/selftests/vm/test_vmalloc.sh new file mode 100644 index 000000000000..06d2bb109f06 --- /dev/null +++ b/tools/testing/selftests/vm/test_vmalloc.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2018 Uladzislau Rezki (Sony) <urezki@gmail.com> +# +# This is a test script for the kernel test driver to analyse vmalloc +# allocator. Therefore it is just a kernel module loader. You can specify +# and pass different parameters in order to: +# a) analyse performance of vmalloc allocations; +# b) stressing and stability check of vmalloc subsystem. + +TEST_NAME="vmalloc" +DRIVER="test_${TEST_NAME}" + +# 1 if fails +exitcode=1 + +# Kselftest framework requirement - SKIP code is 4. +ksft_skip=4 + +# +# Static templates for performance, stressing and smoke tests. +# Also it is possible to pass any supported parameters manualy. +# +PERF_PARAM="single_cpu_test=1 sequential_test_order=1 test_repeat_count=3" +SMOKE_PARAM="single_cpu_test=1 test_loop_count=10000 test_repeat_count=10" +STRESS_PARAM="test_repeat_count=20" + +check_test_requirements() +{ + uid=$(id -u) + if [ $uid -ne 0 ]; then + echo "$0: Must be run as root" + exit $ksft_skip + fi + + if ! which modprobe > /dev/null 2>&1; then + echo "$0: You need modprobe installed" + exit $ksft_skip + fi + + if ! modinfo $DRIVER > /dev/null 2>&1; then + echo "$0: You must have the following enabled in your kernel:" + echo "CONFIG_TEST_VMALLOC=m" + exit $ksft_skip + fi +} + +run_perfformance_check() +{ + echo "Run performance tests to evaluate how fast vmalloc allocation is." + echo "It runs all test cases on one single CPU with sequential order." + + modprobe $DRIVER $PERF_PARAM > /dev/null 2>&1 + echo "Done." + echo "Ccheck the kernel message buffer to see the summary." +} + +run_stability_check() +{ + echo "Run stability tests. In order to stress vmalloc subsystem we run" + echo "all available test cases on all available CPUs simultaneously." + echo "It will take time, so be patient." + + modprobe $DRIVER $STRESS_PARAM > /dev/null 2>&1 + echo "Done." + echo "Check the kernel ring buffer to see the summary." +} + +run_smoke_check() +{ + echo "Run smoke test. Note, this test provides basic coverage." + echo "Please check $0 output how it can be used" + echo "for deep performance analysis as well as stress testing." + + modprobe $DRIVER $SMOKE_PARAM > /dev/null 2>&1 + echo "Done." + echo "Check the kernel ring buffer to see the summary." +} + +usage() +{ + echo -n "Usage: $0 [ performance ] | [ stress ] | | [ smoke ] | " + echo "manual parameters" + echo + echo "Valid tests and parameters:" + echo + modinfo $DRIVER + echo + echo "Example usage:" + echo + echo "# Shows help message" + echo "./${DRIVER}.sh" + echo + echo "# Runs 1 test(id_1), repeats it 5 times on all online CPUs" + echo "./${DRIVER}.sh run_test_mask=1 test_repeat_count=5" + echo + echo -n "# Runs 4 tests(id_1|id_2|id_4|id_16) on one CPU with " + echo "sequential order" + echo -n "./${DRIVER}.sh single_cpu_test=1 sequential_test_order=1 " + echo "run_test_mask=23" + echo + echo -n "# Runs all tests on all online CPUs, shuffled order, repeats " + echo "20 times" + echo "./${DRIVER}.sh test_repeat_count=20" + echo + echo "# Performance analysis" + echo "./${DRIVER}.sh performance" + echo + echo "# Stress testing" + echo "./${DRIVER}.sh stress" + echo + exit 0 +} + +function validate_passed_args() +{ + VALID_ARGS=`modinfo $DRIVER | awk '/parm:/ {print $2}' | sed 's/:.*//'` + + # + # Something has been passed, check it. + # + for passed_arg in $@; do + key=${passed_arg//=*/} + val="${passed_arg:$((${#key}+1))}" + valid=0 + + for valid_arg in $VALID_ARGS; do + if [[ $key = $valid_arg ]] && [[ $val -gt 0 ]]; then + valid=1 + break + fi + done + + if [[ $valid -ne 1 ]]; then + echo "Error: key or value is not correct: ${key} $val" + exit $exitcode + fi + done +} + +function run_manual_check() +{ + # + # Validate passed parameters. If there is wrong one, + # the script exists and does not execute further. + # + validate_passed_args $@ + + echo "Run the test with following parameters: $@" + modprobe $DRIVER $@ > /dev/null 2>&1 + echo "Done." + echo "Check the kernel ring buffer to see the summary." +} + +function run_test() +{ + if [ $# -eq 0 ]; then + usage + else + if [[ "$1" = "performance" ]]; then + run_perfformance_check + elif [[ "$1" = "stress" ]]; then + run_stability_check + elif [[ "$1" = "smoke" ]]; then + run_smoke_check + else + run_manual_check $@ + fi + fi +} + +check_test_requirements +run_test $@ + +exit 0 |