// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2017 Google, Inc.
 */

/*
 * Regression test for commit 37863c43b2c6 ("KEYS: prevent KEYCTL_READ on
 * negative key").  This is CVE-2017-12192.
 */

#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>

#include "tst_test.h"
#include "lapi/keyctl.h"

static void try_to_read_negative_key(void)
{
	key_serial_t key_id;
	char buffer[128];

	/*
	 * Create a negatively instantiated key of the "user" key type.  This
	 * key type is chosen because it has a ->read() method (which makes the
	 * bug reachable) and is available whenever CONFIG_KEYS is enabled.
	 *
	 * request_key() will result in the creation of a negative key provided
	 * that /sbin/request-key isn't configured to positively instantiate the
	 * key, based on the provided type, description, and callout_info.  If
	 * /sbin/request-key doesn't exist, errno will be ENOENT; while if it
	 * does exist and we specify some random unprefixed description, errno
	 * should be ENOKEY (since /sbin/request-key should not be configured to
	 * instantiate random user keys).  In either case a negative key should
	 * be created and we can continue on with the test.  Negative keys last
	 * for 60 seconds so there should be plenty of time for the test.
	 */
	TEST(request_key("user", "description", "callout_info",
			 KEY_SPEC_PROCESS_KEYRING));
	if (TST_RET != -1)
		tst_brk(TBROK, "request_key() unexpectedly succeeded");

	if (TST_ERR != ENOKEY && TST_ERR != ENOENT) {
		tst_brk(TBROK | TTERRNO,
			"request_key() failed with unexpected error");
	}

	/* Get the ID of the negative key by reading the keyring */
	TEST(keyctl(KEYCTL_READ, KEY_SPEC_PROCESS_KEYRING,
		    &key_id, sizeof(key_id)));
	if (TST_RET < 0)
		tst_brk(TBROK | TTERRNO, "KEYCTL_READ unexpectedly failed");
	if (TST_RET != sizeof(key_id)) {
		tst_brk(TBROK, "KEYCTL_READ returned %ld but expected %zu",
			TST_RET, sizeof(key_id));
	}

	/*
	 * Now try to read the negative key.  Unpatched kernels will oops trying
	 * to read from memory address 0x00000000ffffff92.
	 */
	tst_res(TINFO, "trying to read from the negative key...");
	TEST(keyctl(KEYCTL_READ, key_id, buffer, sizeof(buffer)));
	if (TST_RET != -1) {
		tst_brk(TFAIL,
			"KEYCTL_READ on negative key unexpectedly succeeded");
	}
	if (TST_ERR != ENOKEY) {
		tst_brk(TFAIL | TTERRNO,
			"KEYCTL_READ on negative key failed with unexpected error");
	}
	tst_res(TPASS,
		"KEYCTL_READ on negative key expectedly failed with ENOKEY");
}

static void do_test(void)
{
	int status;

	if (SAFE_FORK() == 0) {
		try_to_read_negative_key();
		return;
	}

	SAFE_WAIT(&status);

	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
		tst_res(TPASS, "didn't crash while reading from negative key");
		return;
	}

	if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) {
		tst_res(TFAIL, "reading from negative key caused kernel oops");
		return;
	}

	if (WIFEXITED(status) && WEXITSTATUS(status) == TCONF)
		tst_brk(TCONF, "syscall not implemented");

	tst_brk(TBROK, "Child %s", tst_strstatus(status));
}

static struct tst_test test = {
	.test_all = do_test,
	.forks_child = 1,
	.tags = (const struct tst_tag[]) {
		{"CVE", "2017-12912"},
		{"linux-git", "37863c43b2c6"},
		{}
	}
};
