[LTP] [PATCH v3] misc: rewrite crash02 test
Martin Doucha
mdoucha@suse.cz
Thu Jul 3 16:12:23 CEST 2025
Hi,
Reviewed-by: Martin Doucha <mdoucha@suse.cz>
On 02. 07. 25 10:28, Andrea Cervesato wrote:
> From: Andrea Cervesato <andrea.cervesato@suse.com>
>
> Rewrite the crash02 test, introducing new API but maintaining the logic
> behind it. The test generates random syscall executions with random data
> and it verifies that system didn't crash.
>
> Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
> ---
> Changes in v3:
> - simplify random number generator
> - add __SYS_clone2 to blacklist
> - set runtime according to the number of tries
> - Link to v2: https://lore.kernel.org/r/20250624-crash02_rewrite-v2-1-c4eaeee8598f@suse.com
>
> Changes in v2:
> - simplify number generator
> - take seed with -s parameter or use time(NULL)
> - move loop inside child, so we can use the same seed via srand()
> - take back in_blacklist() method
> - Link to v1: https://lore.kernel.org/r/20250611-crash02_rewrite-v1-1-1becd9dc163d@suse.com
> ---
> testcases/misc/crash/crash02.c | 557 ++++++++---------------------------------
> 1 file changed, 108 insertions(+), 449 deletions(-)
>
> diff --git a/testcases/misc/crash/crash02.c b/testcases/misc/crash/crash02.c
> index c68f580ef62ad3b3c644093f72646a8908e55076..04f56c619f5bfd12b30f90d6d0158556ebbeab6c 100644
> --- a/testcases/misc/crash/crash02.c
> +++ b/testcases/misc/crash/crash02.c
> @@ -1,497 +1,156 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> /*
> - * crash02.c - Test OS robustness by executing syscalls with random args.
> - *
> - * Copyright (C) 2001 Stephane Fillod <f4cfe@free.fr>
> - *
> - * This test program was inspired from crashme, by GEORGE J. CARRETT.
> - *
> - * This program is free software; you can redistribute it and/or
> - * modify it under the terms of the GNU General Public License
> - * as published by the Free Software Foundation; either version 2
> - * of the License, or (at your option) any later version.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> - * GNU General Public License for more details.
> - *
> - * You should have received a copy of the GNU General Public License
> - * along with this program; if not, write to the Free Software
> - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
> + * Copyright (C) 2025 SUSE LLC <andrea.cervesato@suse.com>
> */
>
> -/*
> -A signal handler is set up so that in most cases the machine exception
> -generated by the illegal syscall, bad operands, etc in the procedure
> -made up of random data are caught; and another round of randomness may
> -be tried. Eventually a random syscall may corrupt the program or
> -the machine state in such a way that the program must halt. This is
> -a test of the robustness of the hardware/software for instruction
> -fault handling.
> -
> -Note: Running this program just a few times, using total CPU time of
> -less than a few seconds SHOULD NOT GIVE YOU ANY CONFIDENCE in system
> -robustness. Having it run for hours, with tens of thousands of cases
> -would be a different thing. It would also make sense to run this
> -stress test at the same time you run other tests, like a multi-user
> -benchmark.
> -
> -CAUTION: running this program may crash your system, your disk and all
> - your data along! DO NOT RUN IT ON PRODUCTION SYSTEMS!
> - CONSIDER YOUR DISK FRIED.
> - REMEMBER THE DISCLAIMER PART OF THE LICENSE.
> -
> - Running as user nobody and with all your filesystems
> - remounted to readonly may be wise..
> -
> -TODO:
> - * in rand_long(), stuff in some real pointers to random data
> - * Does a syscall is supposed to send SIGSEGV?
> -*/
> -
> -#define _GNU_SOURCE
> -#include <sys/syscall.h>
> -#include <stdio.h>
> -#include <stdlib.h>
> -#include <string.h>
> -#include <signal.h>
> -#include <setjmp.h>
> -#include <time.h>
> -#include <unistd.h>
> -#include <errno.h>
> -#include <sys/types.h>
> -#include <sys/wait.h>
> -
> -#include "test.h"
> -
> -char *TCID = "crash02";
> -int TST_TOTAL = 1;
> -
> -static int x_opt = 0;
> -static int v_opt = 0;
> -static char *v_copt;
> -static int s_opt = 0;
> -static char *s_copt;
> -static int l_opt = 0;
> -static char *l_copt;
> -static int n_opt = 0;
> -static char *n_copt;
> -
> -int verbose_level = 2;
> -
> -/* depends on architecture.. */
> -unsigned int sysno_max = 127;
> -
> -int nseed;
> -int ntries = 100;
> -
> -/* max time allowed per try, in seconds */
> -#define MAX_TRY_TIME 5
> -
> -void cleanup(void)
> -{
> -
> - tst_rmdir();
> +/*\
> + * Test the robustness of the system generating random syscalls execution
> + * with random data and expecting that the current system is not crashing.
> + */
>
> -}
> +#include <stdlib.h>
> +#include <limits.h>
> +#include "tst_test.h"
> +#include "lapi/syscalls.h"
> +
> +#define MAX_SYSCALLS 465
> +
> +static int *num_errors;
> +static char *str_num_tries;
> +static char *str_seed;
> +static int num_tries = 1000;
> +static int seed;
> +
> +static int blacklist[] = {
> + __NR_vfork,
> + __NR_fork,
> + __NR_clone,
> + __NR_clone2,
> + __NR_clone3,
> + __NR_vhangup, /* terminal logout */
> + __NR_pause, /* sleep indefinitely */
> + __NR_read, /* sleep indefinitely if the first argument is 0 */
> + __NR_kill, /* might kill test */
> + __NR_restart_syscall, /* restart random syscalls */
> + __LTP__NR_INVALID_SYSCALL,
> +};
>
> -void setup(void)
> +static long rand_number(void)
> {
> - /*
> - * setup a default signal hander and a
> - * temporary working directory.
> - */
> - tst_sig(FORK, DEF_HANDLER, cleanup);
> + long num = 0;
>
> - TEST_PAUSE;
> + for (size_t i = 0; i < sizeof(long); i++)
> + num |= ((rand() & 0xFFUL) << (i * 8));
>
> - tst_tmpdir();
> + return num;
> }
>
> -void help(void)
> +static int in_blacklist(const int sysno)
> {
> - printf
> - (" -x dry run, hexdump random code instead\n");
> - printf(" -l x max syscall no\n");
> - printf(" -v x verbose level\n");
> - printf(" -s x random seed\n");
> - printf(" -n x ntries\n");
> -}
> -
> -/*
> - */
> -option_t options[] = {
> - {"v:", &v_opt, &v_copt},
> - {"l:", &l_opt, &l_copt},
> - {"s:", &s_opt, &s_copt},
> - {"n:", &n_opt, &n_copt},
> - {"x", &x_opt, NULL},
> -
> - {NULL, NULL, NULL}
> -};
> -
> -void badboy_fork();
> -void badboy_loop();
> + for (size_t i = 0; i < ARRAY_SIZE(blacklist); i++)
> + if (sysno == blacklist[i])
> + return 1;
>
> -void summarize_errno();
> -void record_errno(unsigned int n);
> + return 0;
> +}
>
> -int main(int argc, char *argv[])
> +static void try_crash(const int num)
> {
> - int lc;
> -
> - tst_parse_opts(argc, argv, options, help);
> + long sysno, arg0, arg1, arg2, arg3, arg4, arg5, arg6;
> + int ret;
>
> - if (v_opt)
> - verbose_level = atoi(v_copt);
> -
> - if (n_opt)
> - ntries = atoi(n_copt);
> -
> - if (l_opt)
> - sysno_max = atoi(l_copt);
> -
> - if (s_opt)
> - nseed = atoi(s_copt);
> - else
> - nseed = time(NULL);
> -
> - setup();
> -
> - for (lc = 0; TEST_LOOPING(lc); lc++) {
> - tst_count = 0;
> + do {
> + sysno = rand() % MAX_SYSCALLS;
> + } while (in_blacklist(sysno));
>
> - tst_resm(TINFO, "crashme02 %d %d %d", sysno_max, nseed, ntries);
> + arg0 = rand_number();
> + arg1 = rand_number();
> + arg2 = rand_number();
> + arg3 = rand_number();
> + arg4 = rand_number();
> + arg5 = rand_number();
> + arg6 = rand_number();
>
> - srand(nseed);
> - badboy_fork();
> + tst_res(TDEBUG,
> + "%d: syscall(%ld, %#lx, %#lx, %#lx, %#lx, %#lx, %#lx, %#lx)",
> + num, sysno, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
>
> - /* still there? */
> - tst_resm(TPASS, "we're still here, OS seems to be robust");
> + ret = syscall(sysno, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
> + if (ret == -1) {
> + (*num_errors)++;
>
> - nseed++;
> + tst_res(TDEBUG, "syscall error: %s", strerror(errno));
> }
> - cleanup();
> - tst_exit();
> }
>
> -/* ************************* */
> -int badboy_pid;
> -
> -void my_signal(int sig, void (*func) ());
> -
> -void monitor_fcn(int sig)
> +static void run(void)
> {
> + pid_t pid;
> int status;
> + int num_signals = 0;
>
> - if (verbose_level >= 3)
> - printf("time limit reached on pid. using kill.\n");
> + *num_errors = 0;
>
> - status = kill(badboy_pid, SIGKILL);
> - if (status < 0) {
> - if (verbose_level >= 3)
> - printf("failed to kill process\n");
> - }
> -}
> + pid = SAFE_FORK();
> + if (!pid) {
> + for (int i = 0; i < num_tries; i++)
> + try_crash(i);
>
> -void badboy_fork(void)
> -{
> - int status, pid;
> - pid_t child = fork();
> -
> - switch (child) {
> - case -1:
> - perror("fork");
> - case 0:
> -#ifdef DEBUG_LATE_BADBOY
> - sleep(ntries * MAX_TRY_TIME + 10);
> -#else
> - badboy_loop();
> -#endif
> exit(0);
> - default:
> - badboy_pid = child;
> - if (verbose_level > 3)
> - printf("badboy pid = %d\n", badboy_pid);
> -
> - /* don't trust the child to return at night */
> - my_signal(SIGALRM, monitor_fcn);
> - alarm(ntries * MAX_TRY_TIME);
> -
> - pid = waitpid(-1, &status, WUNTRACED);
> - if (pid <= 0)
> - perror("wait");
> - else {
> - if (verbose_level > 3)
> - printf("pid %d exited with status %d\n",
> - pid, status);
> -#if 0
> - record_status(status);
> -#endif
> - }
> - }
> - alarm(0);
> -}
> -
> -/* *************** status recording ************************* */
> -
> -/* errno status table (max is actually around 127) */
> -#define STATUS_MAX 256
> -static int errno_table[STATUS_MAX];
> -
> -void record_errno(unsigned int n)
> -{
> - if (n >= STATUS_MAX)
> - return;
> -
> - errno_table[n]++;
> -}
> -
> -/* may not work with -c option */
> -void summarize_errno(void)
> -{
> - int i;
> -
> - if (x_opt || verbose_level < 2)
> - return;
> -
> - printf("errno status ... number of cases\n");
> - for (i = 0; i < STATUS_MAX; i++) {
> - if (errno_table[i])
> - printf("%12d ... %5d\n", i, errno_table[i]);
> }
> -}
> -
> -/* ************* badboy ******************************************* */
>
> -jmp_buf again_buff;
> + SAFE_WAITPID(pid, &status, 0);
>
> -unsigned char *bad_malloc(int n);
> -void my_signal(int sig, void (*func) ());
> -void again_handler(int sig);
> -void try_one_crash(int try_num);
> -void set_up_signals();
> -int in_blacklist(int sysno);
> + if (WIFSIGNALED(status)) {
> + num_signals++;
>
> -/* badboy "entry" point */
> -
> -/*
> - * Unlike crashme, faulty syscalls are not supposed to barf
> - */
> -void badboy_loop(void)
> -{
> - int i;
> -
> - for (i = 0; i < ntries; ++i) {
> - /* level 5 */
> -
> - if (!x_opt && verbose_level >= 5) {
> - printf("try %d\n", i);
> - }
> -
> - if (setjmp(again_buff) == 3) {
> - if (verbose_level >= 5)
> - printf("Barfed\n");
> - } else {
> - set_up_signals();
> - alarm(MAX_TRY_TIME);
> - try_one_crash(i);
> - }
> + tst_res(TDEBUG, "syscall signaled: %s",
> + strsignal(WTERMSIG(status)));
> }
> - summarize_errno();
> -}
>
> -void again_handler(int sig)
> -{
> - char *ss;
> -
> - switch (sig) {
> - case SIGILL:
> - ss = " illegal instruction";
> - break;
> -#ifdef SIGTRAP
> - case SIGTRAP:
> - ss = " trace trap";
> - break;
> -#endif
> - case SIGFPE:
> - ss = " arithmetic exception";
> - break;
> -#ifdef SIGBUS
> - case SIGBUS:
> - ss = " bus error";
> - break;
> -#endif
> - case SIGSEGV:
> - ss = " segmentation violation";
> - break;
> -#ifdef SIGIOT
> - case SIGIOT:
> - ss = " IOT instruction";
> - break;
> -#endif
> -#ifdef SIGEMT
> - case SIGEMT:
> - ss = " EMT instruction";
> - break;
> -#endif
> -#ifdef SIGALRM
> - case SIGALRM:
> - ss = " alarm clock";
> - break;
> -#endif
> - case SIGINT:
> - ss = " interrupt";
> - break;
> - default:
> - ss = "";
> - }
> - if (verbose_level >= 5)
> - printf("Got signal %d%s\n", sig, ss);
> + tst_res(TINFO, "Detected errors: %d", *num_errors);
> + tst_res(TINFO, "Detected signals: %d", num_signals);
>
> - longjmp(again_buff, 3);
> + tst_res(TPASS, "System is still up and running");
> }
>
> -void my_signal(int sig, void (*func) ())
> +static void setup(void)
> {
> - struct sigaction act;
> + if (tst_parse_int(str_num_tries, &num_tries, 1, INT_MAX))
> + tst_brk(TBROK, "Invalid number of entries '%s'", str_num_tries);
>
> - act.sa_handler = func;
> - memset(&act.sa_mask, 0x00, sizeof(sigset_t));
> - act.sa_flags = SA_NOMASK | SA_RESTART;
> - sigaction(sig, &act, 0);
> -}
> + if (tst_parse_int(str_seed, &seed, 0, INT_MAX))
> + tst_brk(TBROK, "Invalid seed number '%s'", str_num_tries);
>
> -void set_up_signals(void)
> -{
> - my_signal(SIGILL, again_handler);
> -#ifdef SIGTRAP
> - my_signal(SIGTRAP, again_handler);
> -#endif
> - my_signal(SIGFPE, again_handler);
> -#ifdef SIGBUS
> - my_signal(SIGBUS, again_handler);
> -#endif
> - my_signal(SIGSEGV, again_handler);
> -#ifdef SIGIOT
> - my_signal(SIGIOT, again_handler);
> -#endif
> -#ifdef SIGEMT
> - my_signal(SIGEMT, again_handler);
> -#endif
> -#ifdef SIGALRM
> - my_signal(SIGALRM, again_handler);
> -#endif
> - my_signal(SIGINT, again_handler);
> -}
> -
> -/*
> - * NB: rand() (ie. RAND_MAX) might be on 31bits only!
> - *
> - * FIXME: 64-bit systems
> - *
> - * TODO: improve arg mixing (16bits and 8bits values, NULLs, etc.).
> - * big values as returned by rand() are no so interresting
> - * (except when used as pointers) because they may fall too
> - * quickly in the invalid parameter sieve. Smaller values,
> - * will be more insidious because they may refer to existing
> - * objects (pids, fd, etc.).
> - */
> -long int rand_long(void)
> -{
> - int r1, r2;
> + num_errors = SAFE_MMAP(
> + NULL, sizeof(int),
> + PROT_READ | PROT_WRITE,
> + MAP_SHARED | MAP_ANONYMOUS,
> + -1, 0);
>
> - r1 = rand();
> - r2 = rand();
> + seed = str_seed ? seed : time(NULL);
> + srand(seed);
>
> - if (r1 & 0x10000L)
> - r1 = 0;
> - if (!r1 && (r2 & 0x50000L))
> - r2 = 0;
> - else if (!r1 && (r2 & 0x20000L))
> - r2 &= 0x00ffL;
> + tst_res(TINFO, "Random seed: %d", seed);
> + tst_res(TINFO, "Number of tries: %d", num_tries);
>
> - return (long int)((r1 & 0xffffL) << 16) | (r2 & 0xffffL);
> + tst_set_runtime((num_tries / 1000) + 1);
> }
>
> -void try_one_crash(int try_num)
> +static void cleanup(void)
> {
> - long int sysno, arg1, arg2, arg3, arg4, arg5, arg6, arg7;
> -
> - do {
> - sysno = rand() % sysno_max;
> - } while (in_blacklist(sysno));
> -
> - arg1 = rand_long();
> - arg2 = rand_long();
> - arg3 = rand_long();
> - arg4 = rand_long();
> - arg5 = rand_long();
> - arg6 = rand_long();
> - arg7 = rand_long();
> -
> - if (x_opt || verbose_level >= 1)
> - printf("%04d: syscall(%ld, %#lx, %#lx, %#lx, %#lx, %#lx, "
> - "%#lx, %#lx)\n", try_num, sysno, arg1, arg2, arg3,
> - arg4, arg5, arg6, arg7);
> -
> - if (!x_opt) {
> - syscall(sysno, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
> - record_errno(errno);
> - }
> + if (num_errors)
> + SAFE_MUNMAP(num_errors, sizeof(int));
> }
>
> -/* The following syscalls create new processes which may cause the test
> - unable to finish. */
> -int in_blacklist(int sysno)
> -{
> - int i;
> - const int list[] = {
> -#if defined(__ia64__)
> - SYS_clone2,
> -#else
> - /*
> - * No SYS_fork(vfork) on IA-64. Instead, it uses,
> - * clone(child_stack=0, flags=CLONE_VM|CLONE_VFORK|SIGCHLD)
> - * clone2()
> - */
> -
> - /*
> - * NOTE (garrcoop):
> - * Could not find reference to SYS_fork(vfork) on mips32
> - * with the Montavista / Octeon toolchain. Need to develop an
> - * autoconf check for this item.
> - */
> -#if defined(__NR_vfork) && __NR_vfork
> - SYS_vfork,
> -#endif
> -#if defined(__NR_fork) && __NR_fork
> - SYS_fork,
> -#endif
> -#endif /* __ia64__ */
> -#if defined(__NR_clone) && __NR_clone
> - SYS_clone,
> -#endif
> -#if defined(__NR_vhangup) && __NR_vhangup
> - __NR_vhangup, /* int vhangup(void); - terminal logout */
> -#endif
> -#if defined(__NR_pause) && __NR_pause
> - __NR_pause, /* int pause(void); - sleep indefinitely */
> -#endif
> -#if defined(__NR_read) && __NR_read
> - /*
> - * ssize_t read(int fd, void *buf, size_t count); - will sleep
> - * indefinitely if the first argument is 0
> - */
> - __NR_read,
> -#endif
> - -1
> - };
> -
> - for (i = 0; list[i] != -1; i++) {
> - if (sysno == list[i])
> - return 1;
> - }
> -
> - return 0;
> -}
> +static struct tst_test test = {
> + .test_all = run,
> + .setup = setup,
> + .cleanup = cleanup,
> + .forks_child = 1,
> + .options = (struct tst_option []) {
> + {"n:", &str_num_tries, "Number of retries (default: 1000)"},
> + {"s:", &str_seed, "Initial seed for random generator"},
> + {}
> + },
> +};
>
> ---
> base-commit: a908cff70f9389c2dd2bf535976cb179bfa8f340
> change-id: 20250611-crash02_rewrite-b84ec3d0d22a
>
> Best regards,
--
Martin Doucha mdoucha@suse.cz
SW Quality Engineer
SUSE LINUX, s.r.o.
CORSO IIa
Krizikova 148/34
186 00 Prague 8
Czech Republic
More information about the ltp
mailing list