/*
 * Copyright (C) 2017  Red Hat, Inc.
 *
 * 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 would 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.
 *
 */

/*
 *  Based on Linux/tools/testing/selftests/memfd/memfd_test.c
 *  by David Herrmann <dh.herrmann@gmail.com>
 *
 *  24/02/2017   Port to LTP    <jracek@redhat.com>
 */

#define _GNU_SOURCE

#include <errno.h>
#include "tst_test.h"
#include "memfd_create_common.h"

/*
 * Do few basic sealing tests to see whether setting/retrieving seals works.
 */
static void test_basic(int fd)
{
	/* add basic seals */
	CHECK_MFD_HAS_SEALS(fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SHRINK | F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK | F_SEAL_WRITE);

	/* add them again */
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SHRINK | F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK | F_SEAL_WRITE);

	/* add more seals and seal against sealing */
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_GROW | F_SEAL_SEAL);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK | F_SEAL_GROW |
			F_SEAL_WRITE | F_SEAL_SEAL);

	/* verify that sealing no longer works */
	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_GROW);
	CHECK_MFD_FAIL_ADD_SEALS(fd, 0);
}

/*
 * Verify that no sealing is possible when memfd is created without
 * MFD_ALLOW_SEALING flag.
 */
static void test_no_sealing_without_flag(int fd)
{
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SEAL);
	CHECK_MFD_FAIL_ADD_SEALS(fd,
		F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SEAL);
}

/*
 * Test SEAL_WRITE
 * Test whether SEAL_WRITE actually prevents modifications.
 */
static void test_seal_write(int fd)
{
	CHECK_MFD_HAS_SEALS(fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE);

	CHECK_MFD_READABLE(fd);
	CHECK_MFD_NON_WRITEABLE(fd);
	CHECK_MFD_SHRINKABLE(fd);
	CHECK_MFD_GROWABLE(fd);
	CHECK_MFD_NON_GROWABLE_BY_WRITE(fd);
}

/*
 * Test SEAL_SHRINK
 * Test whether SEAL_SHRINK actually prevents shrinking
 */
static void test_seal_shrink(int fd)
{
	CHECK_MFD_HAS_SEALS(fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK);

	CHECK_MFD_READABLE(fd);
	CHECK_MFD_WRITEABLE(fd);
	CHECK_MFD_NON_SHRINKABLE(fd);
	CHECK_MFD_GROWABLE(fd);
	CHECK_MFD_GROWABLE_BY_WRITE(fd);
}

/*
 * Test SEAL_GROW
 * Test whether SEAL_GROW actually prevents growing
 */
static void test_seal_grow(int fd)
{
	CHECK_MFD_HAS_SEALS(fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_GROW);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_GROW);

	CHECK_MFD_READABLE(fd);
	CHECK_MFD_WRITEABLE(fd);
	CHECK_MFD_SHRINKABLE(fd);
	CHECK_MFD_NON_GROWABLE(fd);
	CHECK_MFD_NON_GROWABLE_BY_WRITE(fd);
}

/*
 * Test SEAL_SHRINK | SEAL_GROW
 * Test whether SEAL_SHRINK | SEAL_GROW actually prevents resizing
 */
static void test_seal_resize(int fd)
{
	CHECK_MFD_HAS_SEALS(fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SHRINK | F_SEAL_GROW);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK | F_SEAL_GROW);

	CHECK_MFD_READABLE(fd);
	CHECK_MFD_WRITEABLE(fd);
	CHECK_MFD_NON_SHRINKABLE(fd);
	CHECK_MFD_NON_GROWABLE(fd);
	CHECK_MFD_NON_GROWABLE_BY_WRITE(fd);
}

/*
 * Test sharing via dup()
 * Test that seals are shared between dupped FDs and they're all equal.
 */
static void test_share_dup(int fd)
{
	int fd2;

	CHECK_MFD_HAS_SEALS(fd, 0);

	fd2 = SAFE_DUP(fd);
	CHECK_MFD_HAS_SEALS(fd2, 0);

	CHECK_MFD_ADD_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd2, F_SEAL_WRITE);

	CHECK_MFD_ADD_SEALS(fd2, F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE | F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd2, F_SEAL_WRITE | F_SEAL_SHRINK);

	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SEAL);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_SEAL);
	CHECK_MFD_HAS_SEALS(fd2, F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_SEAL);

	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_GROW);
	CHECK_MFD_FAIL_ADD_SEALS(fd2, F_SEAL_GROW);
	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_SEAL);
	CHECK_MFD_FAIL_ADD_SEALS(fd2, F_SEAL_SEAL);

	SAFE_CLOSE(fd2);

	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_GROW);
}

/*
 * Test sealing with active mmap()s
 * Modifying seals is only allowed if no other mmap() refs exist.
 */
static void test_share_mmap(int fd)
{
	void *p;

	CHECK_MFD_HAS_SEALS(fd, 0);

	/* shared/writable ref prevents sealing WRITE, but allows others */
	p = SAFE_MMAP(NULL, MFD_DEF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
		fd, 0);

	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_SHRINK);
	SAFE_MUNMAP(p, MFD_DEF_SIZE);

	/* readable ref allows sealing */
	p = SAFE_MMAP(NULL, MFD_DEF_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE | F_SEAL_SHRINK);
	SAFE_MUNMAP(p, MFD_DEF_SIZE);
}

/*
 * Test sealing with open(/proc/self/fd/%d)
 * Via /proc we can get access to a separate file-context for the same memfd.
 * This is *not* like dup(), but like a real separate open(). Make sure the
 * semantics are as expected and we correctly check for RDONLY / WRONLY / RDWR.
 */
static void test_share_open(int fd)
{
	int fd2;

	CHECK_MFD_HAS_SEALS(fd, 0);

	fd2 = CHECK_MFD_OPEN(fd, O_RDWR, 0);
	CHECK_MFD_ADD_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE);
	CHECK_MFD_HAS_SEALS(fd2, F_SEAL_WRITE);

	CHECK_MFD_ADD_SEALS(fd2, F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE | F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd2, F_SEAL_WRITE | F_SEAL_SHRINK);

	SAFE_CLOSE(fd);
	fd = CHECK_MFD_OPEN(fd2, O_RDONLY, 0);

	CHECK_MFD_FAIL_ADD_SEALS(fd, F_SEAL_SEAL);
	CHECK_MFD_HAS_SEALS(fd, F_SEAL_WRITE | F_SEAL_SHRINK);
	CHECK_MFD_HAS_SEALS(fd2, F_SEAL_WRITE | F_SEAL_SHRINK);

	SAFE_CLOSE(fd2);
}


static const struct tcase {
	int flags;
	void (*func)(int fd);
	const char *desc;
} tcases[] = {
	{MFD_ALLOW_SEALING, &test_basic, "Basic tests + set/get seals"},
	{0,                 &test_no_sealing_without_flag, "Disabled sealing"},

	{MFD_ALLOW_SEALING, &test_seal_write, "Write seal"},
	{MFD_ALLOW_SEALING, &test_seal_shrink, "Shrink seal"},
	{MFD_ALLOW_SEALING, &test_seal_grow, "Grow seal"},
	{MFD_ALLOW_SEALING, &test_seal_resize, "Resize seal"},

	{MFD_ALLOW_SEALING, &test_share_dup, "Seals shared for dup"},
	{MFD_ALLOW_SEALING, &test_share_mmap, "Seals shared for mmap"},
	{MFD_ALLOW_SEALING, &test_share_open, "Seals shared for open"},
};

static void verify_memfd_create(unsigned int n)
{
	int fd;
	const struct tcase *tc;

	tc = &tcases[n];

	tst_res(TINFO, "%s", tc->desc);

	fd = CHECK_MFD_NEW(TCID, MFD_DEF_SIZE, tc->flags);

	tc->func(fd);

	SAFE_CLOSE(fd);
}

static void setup(void)
{
	/*
	 * For now, all tests in this file require MFD_ALLOW_SEALING flag
	 * to be implemented, even though that flag isn't always set when
	 * memfd is created. So don't check anything else and TCONF right away
	 * is this flag is missing.
	 */
	if (!MFD_FLAGS_AVAILABLE(MFD_ALLOW_SEALING)) {
		tst_brk(TCONF | TTERRNO,
			"memfd_create(%u) not implemented", MFD_ALLOW_SEALING);
	}
}

static struct tst_test test = {
	.test = verify_memfd_create,
	.tcnt = ARRAY_SIZE(tcases),
	.setup = setup,
};
