// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdint.h>
#include <asm-generic/unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdbool.h>
#include "../kselftest.h"
#include <syscall.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/vfs.h>
#include <sys/stat.h>
#include "mseal_helpers.h"

/*
 * define sys_xyx to call syscall directly.
 */
static int sys_mseal(void *start, size_t len)
{
	int sret;

	errno = 0;
	sret = syscall(__NR_mseal, start, len, 0);
	return sret;
}

static inline int sys_mprotect(void *ptr, size_t size, unsigned long prot)
{
	int sret;

	errno = 0;
	sret = syscall(__NR_mprotect, ptr, size, prot);
	return sret;
}

static bool seal_support(void)
{
	int ret;
	void *ptr;
	unsigned long page_size = getpagesize();

	ptr = mmap(NULL, page_size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
	if (ptr == (void *) -1)
		return false;

	ret = sys_mseal(ptr, page_size);
	if (ret < 0)
		return false;

	return true;
}

const char somestr[4096] = {"READONLY"};

static void test_seal_elf(void)
{
	int ret;
	FILE *maps;
	char line[512];
	uintptr_t  addr_start, addr_end;
	char prot[5];
	char filename[256];
	unsigned long page_size = getpagesize();
	unsigned long long ptr = (unsigned long long) somestr;
	char *somestr2 = (char *)somestr;

	/*
	 * Modify the protection of readonly somestr
	 */
	if (((unsigned long long)ptr % page_size) != 0)
		ptr = (unsigned long long)ptr & ~(page_size - 1);

	ksft_print_msg("somestr = %s\n", somestr);
	ksft_print_msg("change protection to rw\n");
	ret = sys_mprotect((void *)ptr, page_size, PROT_READ|PROT_WRITE);
	FAIL_TEST_IF_FALSE(!ret);
	*somestr2 = 'A';
	ksft_print_msg("somestr is modified to: %s\n", somestr);
	ret = sys_mprotect((void *)ptr, page_size, PROT_READ);
	FAIL_TEST_IF_FALSE(!ret);

	maps = fopen("/proc/self/maps", "r");
	FAIL_TEST_IF_FALSE(maps);

	/*
	 * apply sealing to elf binary
	 */
	while (fgets(line, sizeof(line), maps)) {
		if (sscanf(line, "%lx-%lx %4s %*x %*x:%*x %*u %255[^\n]",
			&addr_start, &addr_end, prot, filename) == 4) {
			if (strlen(filename)) {
				/*
				 * seal the mapping if read only.
				 */
				if (strstr(prot, "r-")) {
					ret = sys_mseal((void *)addr_start, addr_end - addr_start);
					FAIL_TEST_IF_FALSE(!ret);
					ksft_print_msg("sealed: %lx-%lx %s %s\n",
						addr_start, addr_end, prot, filename);
					if ((uintptr_t) somestr >= addr_start &&
						(uintptr_t) somestr <= addr_end)
						ksft_print_msg("mapping for somestr found\n");
				}
			}
		}
	}
	fclose(maps);

	ret = sys_mprotect((void *)ptr, page_size, PROT_READ | PROT_WRITE);
	FAIL_TEST_IF_FALSE(ret < 0);
	ksft_print_msg("somestr is sealed, mprotect is rejected\n");

	REPORT_TEST_PASS();
}

int main(int argc, char **argv)
{
	bool test_seal = seal_support();

	ksft_print_header();
	ksft_print_msg("pid=%d\n", getpid());

	if (!test_seal)
		ksft_exit_skip("sealing not supported, check CONFIG_64BIT\n");

	ksft_set_plan(1);

	test_seal_elf();

	ksft_finished();
}
