[LTP] [PATCH] misc: rewrite crash02 test
Andrea Cervesato
andrea.cervesato@suse.de
Wed Jun 11 15:24:38 CEST 2025
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>
---
testcases/misc/crash/crash02.c | 577 +++++++++--------------------------------
1 file changed, 123 insertions(+), 454 deletions(-)
diff --git a/testcases/misc/crash/crash02.c b/testcases/misc/crash/crash02.c
index c68f580ef62ad3b3c644093f72646a8908e55076..417c2aa2b8e4facf9ddcde358fe59a7f4419e859 100644
--- a/testcases/misc/crash/crash02.c
+++ b/testcases/misc/crash/crash02.c
@@ -1,497 +1,166 @@
+// 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();
-
-}
-
-void setup(void)
-{
- /*
- * setup a default signal hander and a
- * temporary working directory.
- */
- tst_sig(FORK, DEF_HANDLER, cleanup);
+/*\
+ * Test the robustness of the system generating random syscalls execution
+ * with random data and expecting that the current system is not crashing.
+ */
- TEST_PAUSE;
+#include <limits.h>
+#include "tst_test.h"
+#include "lapi/syscalls.h"
+#include "lapi/getrandom.h"
- tst_tmpdir();
-}
+#define MAX_SYSCALLS 465
-void help(void)
-{
- 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");
-}
+static int *num_errors;
+static char *str_num_tries;
+static int num_tries = 1000;
-/*
- */
-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}
+static int blacklist[] = {
+#if defined(__ia64__)
+ __NR_clone2, /* IA-64 uses clone2 instead of fork/vfork */
+#else
+# if defined(__NR_vfork)
+ __NR_vfork,
+# endif
+# if defined(__NR_fork)
+ __NR_fork,
+# endif
+#endif /* __ia64__ */
+#if defined(__NR_clone)
+ __NR_clone,
+#endif
+#if defined(__NR_clone3)
+ __NR_clone3,
+#endif
+#if defined(__NR_vhangup)
+ __NR_vhangup, /* terminal logout */
+#endif
+#if defined(__NR_pause)
+ __NR_pause, /* sleep indefinitely */
+#endif
+#if defined(__NR_read)
+ __NR_read, /* sleep indefinitely if the first argument is 0 */
+#endif
+ __LTP__NR_INVALID_SYSCALL,
};
-void badboy_fork();
-void badboy_loop();
-
-void summarize_errno();
-void record_errno(unsigned int n);
-
-int main(int argc, char *argv[])
+static inline long rand_number(void)
{
- int lc;
-
- tst_parse_opts(argc, argv, options, help);
-
- if (v_opt)
- verbose_level = atoi(v_copt);
+ int64_t num = 0;
+ char buff[4];
- if (n_opt)
- ntries = atoi(n_copt);
+ if (getrandom(buff, 4, 0) == -1)
+ tst_brk(TBROK | TERRNO, "getrandom error");
- if (l_opt)
- sysno_max = atoi(l_copt);
+ num = (buff[0] << 24) | (buff[1] << 16) | (buff[2] << 8) | buff[3];
+ if (num < 0)
+ num *= -1;
+ num = (num % MAX_SYSCALLS) - 1;
- if (s_opt)
- nseed = atoi(s_copt);
- else
- nseed = time(NULL);
-
- setup();
-
- for (lc = 0; TEST_LOOPING(lc); lc++) {
- tst_count = 0;
-
- tst_resm(TINFO, "crashme02 %d %d %d", sysno_max, nseed, ntries);
-
- srand(nseed);
- badboy_fork();
-
- /* still there? */
- tst_resm(TPASS, "we're still here, OS seems to be robust");
-
- nseed++;
- }
- cleanup();
- tst_exit();
+ return (long)num;
}
-/* ************************* */
-int badboy_pid;
-
-void my_signal(int sig, void (*func) ());
-
-void monitor_fcn(int sig)
-{
- int status;
-
- if (verbose_level >= 3)
- printf("time limit reached on pid. using kill.\n");
-
- status = kill(badboy_pid, SIGKILL);
- if (status < 0) {
- if (verbose_level >= 3)
- printf("failed to kill process\n");
- }
-}
-
-void badboy_fork(void)
+static void try_crash(const int num)
{
- int status, pid;
- pid_t child = fork();
+ long sysno, arg0, arg1, arg2, arg3, arg4, arg5, arg6;
+ int invalid;
+ int ret;
- 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
+ do {
+ invalid = 0;
+ sysno = rand_number() % MAX_SYSCALLS;
+
+ for (size_t i = 0; i < ARRAY_SIZE(blacklist); i++) {
+ if (blacklist[i] == sysno) {
+ invalid = 1;
+ break;
+ }
}
- }
- 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;
+ if (!invalid)
+ break;
+ } while (1);
- errno_table[n]++;
-}
+ arg0 = rand_number();
+ arg1 = rand_number();
+ arg2 = rand_number();
+ arg3 = rand_number();
+ arg4 = rand_number();
+ arg5 = rand_number();
+ arg6 = rand_number();
-/* may not work with -c option */
-void summarize_errno(void)
-{
- int i;
+ tst_res(TDEBUG,
+ "%d: syscall(%ld, %#lx, %#lx, %#lx, %#lx, %#lx, %#lx, %#lx)",
+ num, sysno, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
- if (x_opt || verbose_level < 2)
- return;
+ ret = syscall(sysno, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
+ if (ret == -1) {
+ (*num_errors)++;
- 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]);
+ tst_res(TDEBUG, "syscall error: %s", strerror(errno));
}
}
-/* ************* badboy ******************************************* */
-
-jmp_buf again_buff;
-
-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);
-
-/* badboy "entry" point */
-
-/*
- * Unlike crashme, faulty syscalls are not supposed to barf
- */
-void badboy_loop(void)
+static void run(void)
{
- int i;
-
- for (i = 0; i < ntries; ++i) {
- /* level 5 */
+ pid_t pid;
+ int status;
+ int num_signals = 0;
- if (!x_opt && verbose_level >= 5) {
- printf("try %d\n", i);
+ for (int i = 0; i < num_tries; i++) {
+ pid = SAFE_FORK();
+ if (!pid) {
+ try_crash(i);
+ exit(0);
}
- if (setjmp(again_buff) == 3) {
- if (verbose_level >= 5)
- printf("Barfed\n");
- } else {
- set_up_signals();
- alarm(MAX_TRY_TIME);
- try_one_crash(i);
- }
- }
- summarize_errno();
-}
+ SAFE_WAITPID(pid, &status, 0);
-void again_handler(int sig)
-{
- char *ss;
+ if (WIFSIGNALED(status)) {
+ num_signals++;
- 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 = "";
+ tst_res(TDEBUG, "syscall signaled: %s",
+ strsignal(WTERMSIG(status)));
+ }
}
- if (verbose_level >= 5)
- printf("Got signal %d%s\n", sig, ss);
- longjmp(again_buff, 3);
-}
-
-void my_signal(int sig, void (*func) ())
-{
- struct sigaction act;
+ tst_res(TINFO, "Detected errors: %d", *num_errors);
+ tst_res(TINFO, "Detected signals: %d", num_signals);
- act.sa_handler = func;
- memset(&act.sa_mask, 0x00, sizeof(sigset_t));
- act.sa_flags = SA_NOMASK | SA_RESTART;
- sigaction(sig, &act, 0);
-}
-
-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);
+ tst_res(TPASS, "System is still up and running");
}
-/*
- * 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)
+static void setup(void)
{
- int r1, r2;
-
- r1 = rand();
- r2 = rand();
-
- if (r1 & 0x10000L)
- r1 = 0;
- if (!r1 && (r2 & 0x50000L))
- r2 = 0;
- else if (!r1 && (r2 & 0x20000L))
- r2 &= 0x00ffL;
-
- return (long int)((r1 & 0xffffL) << 16) | (r2 & 0xffffL);
+ num_errors = SAFE_MMAP(
+ NULL, sizeof(int),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS,
+ -1, 0);
+
+ if (tst_parse_int(str_num_tries, &num_tries, 1, INT_MAX))
+ tst_brk(TBROK, "Invalid number of entries '%s'", str_num_tries);
}
-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,
+ .needs_root = 1,
+ .forks_child = 1,
+ .runtime = 40,
+ .options = (struct tst_option []) {
+ {"n:", &str_num_tries, "Number of retries (default: 1000)"},
+ {}
+ },
+};
---
base-commit: bf9589d5bdeef15b3dbb03f896793306552d0d0f
change-id: 20250611-crash02_rewrite-b84ec3d0d22a
Best regards,
--
Andrea Cervesato <andrea.cervesato@suse.com>
More information about the ltp
mailing list