// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2018 Michael Moese <mmoese@suse.com>
 */
/* Regression test for CVE-2017-17053, original reproducer can be found
 * here:
 * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ccd5b3235180eef3cfec337df1c8554ab151b5cc
 *
 * Be careful! This test may crash your kernel!
 */

#include "config.h"
#include "tst_test.h"

#ifdef HAVE_ASM_LDT_H
#include <asm/ldt.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

#include "lapi/syscalls.h"

#define EXEC_USEC   5000000

/* this is basically identical to SAFE_PTHREAD_CREATE(), but is tolerating the
 * call to fail whenn the error is EAGAIN or EWOULDBLOCK */
static void try_pthread_create(pthread_t *thread_id, const pthread_attr_t *attr,
			       void *(*thread_fn)(void *), void *arg)
{
	int rval;

	rval = pthread_create(thread_id, attr, thread_fn, arg);

	if (rval && rval != EAGAIN && rval != EWOULDBLOCK)
		tst_brk(TBROK, "pthread_create(%p,%p,%p,%p) failed: %s",
			thread_id, attr, thread_fn, arg, tst_strerrno(rval));
}

/* this is basically identical to SAFE_FORK(), but is tolerating the
 * call to fail whenn the error is EAGAIN or EWOULDBLOCK */
static int try_fork(void)
{
	pid_t pid;

	tst_flush();

	pid = fork();
	if (pid < 0 && errno != EAGAIN && errno == EWOULDBLOCK)
		tst_brk(TBROK | TERRNO, "fork() failed");

	return pid;
}



struct shm_data {
	volatile sig_atomic_t do_exit;
	volatile sig_atomic_t segfaulted;
};
static struct shm_data *shm;

static void handler(int sig)
{
	(void)sig;

	shm->segfaulted = 1;
	shm->do_exit = 1;
}

static void install_sighandler(void)
{
	struct sigaction sa;

	sa.sa_flags = SA_SIGINFO;
	sigemptyset(&sa.sa_mask);
	sa.sa_handler = handler;

	SAFE_SIGACTION(SIGSEGV, &sa, NULL);
}

static void setup(void)
{
	shm = SAFE_MMAP(NULL, sizeof(struct shm_data),
			PROT_READ | PROT_WRITE,
			MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}

static void cleanup(void)
{
	SAFE_MUNMAP(shm, sizeof(struct shm_data));
}

static void *fork_thread(void *arg)
{
	try_fork();
	return arg;
}

void run_test(void)
{
	struct user_desc desc = { .entry_number = 8191 };

	install_sighandler();
	syscall(__NR_modify_ldt, 1, &desc, sizeof(desc));

	for (;;) {
		if (shm->do_exit)
			exit(0);

		if (try_fork() == 0) {
			pthread_t t;

			srand(getpid());
			try_pthread_create(&t, NULL, fork_thread, NULL);
			usleep(rand() % 10000);
			syscall(__NR_exit_group, 0);
		}
	}
}

void run(void)
{
	int status;
	pid_t pid;

	shm->do_exit = 0;
	shm->segfaulted = 0;

	pid = SAFE_FORK();
	if (pid == 0) {
		run_test();
	} else {
		usleep(EXEC_USEC);
		shm->do_exit = 1;
	}

	SAFE_WAIT(&status);

	if (WIFEXITED(status) && shm->segfaulted == 0 && tst_taint_check() == 0)
		tst_res(TPASS, "kernel survived");
	else
		tst_res(TFAIL, "kernel is vulnerable");
}

static struct tst_test test = {
	.forks_child = 1,
	.setup = setup,
	.cleanup = cleanup,
	.test_all = run,
	.taint_check = TST_TAINT_W | TST_TAINT_D,
	.tags = (const struct tst_tag[]) {
		{"linux-git", "ccd5b3235180"},
		{"CVE", "2017-17053"},
		{}
	}
};

#else
TST_TEST_TCONF("no asm/ldt.h header (only for i386 or x86_64)");
#endif
