// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2018 Michael Moese <mmoese@suse.com>
 * Copyright (c) Linux Test Project, 2020
 */

#define TST_NO_DEFAULT_MAIN

#include "tst_test.h"
#include "tst_taint.h"
#include "tst_safe_stdio.h"

#define TAINT_FILE "/proc/sys/kernel/tainted"

static unsigned int taint_mask = -1;

static const char *const taint_strings[] = {
	"G (Propriety module loaded)",
	"F (Module force loaded)",
	"S (Running on out of spec system)",
	"R (Module force unloaded)",
	"M (Machine check exception)",
	"B (Bad page reference)",
	"U (User request)",
	"D (OOPS/BUG)",
	"A (ACPI table overridden)",
	"W (Warning)",
	"C (Staging driver loaded)",
	"I (Workaround BIOS/FW bug)",
	"O (Out of tree module loaded)",
	"E (Unsigned module loaded)",
	"L (Soft lock up occured)",
	"K (Live patched)",
	"X (Auxilary)",
	"T (Built with struct randomization)",
};

static unsigned int tst_taint_read(void)
{
	unsigned int val;

	SAFE_FILE_SCANF(TAINT_FILE, "%u", &val);

	return val;
}

static int tst_taint_check_kver(unsigned int mask)
{
	int r1;
	int r2;
	int r3 = 0;

	if (mask & TST_TAINT_X) {
		r1 = 4;
		r2 = 15;
	} else if (mask & TST_TAINT_K) {
		r1 = 4;
		r2 = 0;
	} else if (mask & TST_TAINT_L) {
		r1 = 3;
		r2 = 17;
	} else if (mask & TST_TAINT_E) {
		r1 = 3;
		r2 = 15;
	} else if (mask & TST_TAINT_O) {
		r1 = 3;
		r2 = 2;
	} else if (mask & TST_TAINT_I) {
		r1 = 2;
		r2 = 6;
		r3 = 35;
	} else if (mask & TST_TAINT_C) {
		r1 = 2;
		r2 = 6;
		r3 = 28;
	} else if (mask & TST_TAINT_W) {
		r1 = 2;
		r2 = 6;
		r3 = 26;
	} else if (mask & TST_TAINT_A) {
		r1 = 2;
		r2 = 6;
		r3 = 25;
	} else if (mask & TST_TAINT_D) {
		r1 = 2;
		r2 = 6;
		r3 = 23;
	} else if (mask & TST_TAINT_U) {
		r1 = 2;
		r2 = 6;
		r3 = 21;
	} else {
		r1 = 2;
		r2 = 6;
		r3 = 16;
	}

	return tst_kvercmp(r1, r2, r3);
}

void tst_taint_init(unsigned int mask)
{
	unsigned int taint = -1;
	unsigned long i;

	if (mask == 0)
		tst_brk(TBROK, "mask is not allowed to be 0");

	if (tst_taint_check_kver(mask) < 0)
		tst_res(TCONF, "Kernel is too old for requested mask");

	taint_mask = mask;
	taint = tst_taint_read();

	if (taint & TST_TAINT_W) {
		tst_res(TCONF, "Ignoring already set kernel warning taint");
		taint_mask &= ~TST_TAINT_W;
	}

	if ((taint & taint_mask) != 0) {
		for (i = 0; i < ARRAY_SIZE(taint_strings); i++) {
			if (taint & (1 << i))
				tst_res(TINFO, "tainted: %s", taint_strings[i]);
		}

		tst_brk(TBROK, "Kernel is already tainted");
	}
}


unsigned int tst_taint_check(void)
{
	unsigned int taint = -1;

	if (taint_mask == (unsigned int) -1)
		tst_brk(TBROK, "need to call tst_taint_init() first");

	taint = tst_taint_read();

	return (taint & taint_mask);
}
