/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Functions for querying, manipulating and locking rollback indices
 * stored in the TPM NVRAM.
 */

#include "2sysincludes.h"
#include "2common.h"
#include "2crc8.h"

#include "sysincludes.h"
#include "rollback_index.h"
#include "tlcl.h"
#include "tss_constants.h"
#include "utility.h"
#include "vboot_api.h"

#ifndef offsetof
#define offsetof(A,B) __builtin_offsetof(A,B)
#endif

/*
 * Provide protoypes for functions not in the header file. These prototypes
 * fix -Wmissing-prototypes warnings.
 */
uint32_t ReadSpaceFirmware(RollbackSpaceFirmware *rsf);
uint32_t WriteSpaceFirmware(RollbackSpaceFirmware *rsf);
uint32_t ReadSpaceKernel(RollbackSpaceKernel *rsk);
uint32_t WriteSpaceKernel(RollbackSpaceKernel *rsk);

#ifdef FOR_TEST
/*
 * Compiling for unit test, so we need the real implementations of
 * rollback functions.  The unit test mocks the underlying tlcl
 * functions, so this is ok to run on the host.
 */
#undef CHROMEOS_ENVIRONMENT
#undef DISABLE_ROLLBACK_TPM
#endif

#define RETURN_ON_FAILURE(tpm_command) do {				\
		uint32_t result_;					\
		if ((result_ = (tpm_command)) != TPM_SUCCESS) {		\
			VB2_DEBUG("Rollback: %08x returned by " #tpm_command \
				  "\n", (int)result_);			\
			return result_;					\
		}							\
	} while (0)


uint32_t TPMClearAndReenable(void)
{
	VB2_DEBUG("TPM: Clear and re-enable\n");
	RETURN_ON_FAILURE(TlclForceClear());
	RETURN_ON_FAILURE(TlclSetEnable());
	RETURN_ON_FAILURE(TlclSetDeactivated(0));

	return TPM_SUCCESS;
}

uint32_t SafeWrite(uint32_t index, const void *data, uint32_t length)
{
	uint32_t result = TlclWrite(index, data, length);
	if (result == TPM_E_MAXNVWRITES) {
		RETURN_ON_FAILURE(TPMClearAndReenable());
		return TlclWrite(index, data, length);
	} else {
		return result;
	}
}

/* Functions to read and write firmware and kernel spaces. */
uint32_t ReadSpaceFirmware(RollbackSpaceFirmware *rsf)
{
	uint32_t r;
	int attempts = 3;

	while (attempts--) {
		r = TlclRead(FIRMWARE_NV_INDEX, rsf,
			     sizeof(RollbackSpaceFirmware));
		if (r != TPM_SUCCESS)
			return r;

		/*
		 * No CRC in this version, so we'll create one when we write
		 * it. Note that we're marking this as version 2, not
		 * ROLLBACK_SPACE_FIRMWARE_VERSION, because version 2 just
		 * added the CRC. Later versions will need to set default
		 * values for any extra fields explicitly (probably here).
		 */
		if (rsf->struct_version < 2) {
			/* Danger Will Robinson! Danger! */
			rsf->struct_version = 2;
			return TPM_SUCCESS;
		}

		/*
		 * If the CRC is good, we're done. If it's bad, try a couple
		 * more times to see if it gets better before we give up. It
		 * could just be noise.
		 */
		if (rsf->crc8 == vb2_crc8(rsf,
				      offsetof(RollbackSpaceFirmware, crc8)))
			return TPM_SUCCESS;

		VB2_DEBUG("TPM: bad CRC\n");
	}

	VB2_DEBUG("TPM: too many bad CRCs, giving up\n");
	return TPM_E_CORRUPTED_STATE;
}

uint32_t WriteSpaceFirmware(RollbackSpaceFirmware *rsf)
{
	RollbackSpaceFirmware rsf2;
	uint32_t r;
	int attempts = 3;

	/* All writes should use struct_version 2 or greater. */
	if (rsf->struct_version < 2)
		rsf->struct_version = 2;
	rsf->crc8 = vb2_crc8(rsf, offsetof(RollbackSpaceFirmware, crc8));

	while (attempts--) {
		r = SafeWrite(FIRMWARE_NV_INDEX, rsf,
			      sizeof(RollbackSpaceFirmware));
		/* Can't write, not gonna try again */
		if (r != TPM_SUCCESS)
			return r;

		/* Read it back to be sure it got the right values. */
		r = ReadSpaceFirmware(&rsf2);    /* This checks the CRC */
		if (r == TPM_SUCCESS)
			return r;

		VB2_DEBUG("TPM: bad CRC\n");
		/* Try writing it again. Maybe it was garbled on the way out. */
	}

	VB2_DEBUG("TPM: too many bad CRCs, giving up\n");
	return TPM_E_CORRUPTED_STATE;
}

uint32_t SetVirtualDevMode(int val)
{
	RollbackSpaceFirmware rsf;

	VB2_DEBUG("TPM: Entering");
	if (TPM_SUCCESS != ReadSpaceFirmware(&rsf))
		return VBERROR_TPM_FIRMWARE_SETUP;

	VB2_DEBUG("TPM: flags were 0x%02x\n", rsf.flags);
	if (val)
		rsf.flags |= FLAG_VIRTUAL_DEV_MODE_ON;
	else
		rsf.flags &= ~FLAG_VIRTUAL_DEV_MODE_ON;
	/*
	 * NOTE: This doesn't update the FLAG_LAST_BOOT_DEVELOPER bit.  That
	 * will be done on the next boot.
	 */
	VB2_DEBUG("TPM: flags are now 0x%02x\n", rsf.flags);

	if (TPM_SUCCESS != WriteSpaceFirmware(&rsf))
		return VBERROR_TPM_SET_BOOT_MODE_STATE;

	VB2_DEBUG("TPM: Leaving\n");
	return VBERROR_SUCCESS;
}

uint32_t ReadSpaceKernel(RollbackSpaceKernel *rsk)
{
	uint32_t r;
	int attempts = 3;

	while (attempts--) {
		r = TlclRead(KERNEL_NV_INDEX, rsk, sizeof(RollbackSpaceKernel));
		if (r != TPM_SUCCESS)
			return r;

		/*
		 * No CRC in this version, so we'll create one when we write
		 * it. Note that we're marking this as version 2, not
		 * ROLLBACK_SPACE_KERNEL_VERSION, because version 2 just added
		 * the CRC. Later versions will need to set default values for
		 * any extra fields explicitly (probably here).
		 */
		if (rsk->struct_version < 2) {
			/* Danger Will Robinson! Danger! */
			rsk->struct_version = 2;
			return TPM_SUCCESS;
		}

		/*
		 * If the CRC is good, we're done. If it's bad, try a couple
		 * more times to see if it gets better before we give up. It
		 * could just be noise.
		 */
		if (rsk->crc8 ==
		    vb2_crc8(rsk, offsetof(RollbackSpaceKernel, crc8)))
			return TPM_SUCCESS;

		VB2_DEBUG("TPM: bad CRC\n");
	}

	VB2_DEBUG("TPM: too many bad CRCs, giving up\n");
	return TPM_E_CORRUPTED_STATE;
}

uint32_t WriteSpaceKernel(RollbackSpaceKernel *rsk)
{
	RollbackSpaceKernel rsk2;
	uint32_t r;
	int attempts = 3;

	/* All writes should use struct_version 2 or greater. */
	if (rsk->struct_version < 2)
		rsk->struct_version = 2;
	rsk->crc8 = vb2_crc8(rsk, offsetof(RollbackSpaceKernel, crc8));

	while (attempts--) {
		r = SafeWrite(KERNEL_NV_INDEX, rsk,
			      sizeof(RollbackSpaceKernel));
		/* Can't write, not gonna try again */
		if (r != TPM_SUCCESS)
			return r;

		/* Read it back to be sure it got the right values. */
		r = ReadSpaceKernel(&rsk2);    /* This checks the CRC */
		if (r == TPM_SUCCESS)
			return r;

		VB2_DEBUG("TPM: bad CRC\n");
		/* Try writing it again. Maybe it was garbled on the way out. */
	}

	VB2_DEBUG("TPM: too many bad CRCs, giving up\n");
	return TPM_E_CORRUPTED_STATE;
}

#ifdef DISABLE_ROLLBACK_TPM
/* Dummy implementations which don't support TPM rollback protection */

uint32_t RollbackKernelRead(uint32_t* version)
{
	*version = 0;
	return TPM_SUCCESS;
}

uint32_t RollbackKernelWrite(uint32_t version)
{
	return TPM_SUCCESS;
}

uint32_t RollbackKernelLock(int recovery_mode)
{
	return TPM_SUCCESS;
}

uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp)
{
	memset(fwmp, 0, sizeof(*fwmp));
	return TPM_SUCCESS;
}

#else

uint32_t RollbackKernelRead(uint32_t* version)
{
	RollbackSpaceKernel rsk;

	/*
	 * Read the kernel space and verify its permissions.  If the kernel
	 * space has the wrong permission, or it doesn't contain the right
	 * identifier, we give up.  This will need to be fixed by the
	 * recovery kernel.  We have to worry about this because at any time
	 * (even with PP turned off) the TPM owner can remove and redefine a
	 * PP-protected space (but not write to it).
	 */
	RETURN_ON_FAILURE(ReadSpaceKernel(&rsk));
#ifndef TPM2_MODE
	/*
	 * TODO(vbendeb): restore this when it is defined how the kernel space
	 * gets protected.
	 */
	{
		uint32_t perms, uid;

		RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_NV_INDEX, &perms));
		memcpy(&uid, &rsk.uid, sizeof(uid));
		if (TPM_NV_PER_PPWRITE != perms ||
		    ROLLBACK_SPACE_KERNEL_UID != uid)
			return TPM_E_CORRUPTED_STATE;
	}
#endif
	memcpy(version, &rsk.kernel_versions, sizeof(*version));
	VB2_DEBUG("TPM: RollbackKernelRead %x\n", (int)*version);
	return TPM_SUCCESS;
}

uint32_t RollbackKernelWrite(uint32_t version)
{
	RollbackSpaceKernel rsk;
	uint32_t old_version;
	RETURN_ON_FAILURE(ReadSpaceKernel(&rsk));
	memcpy(&old_version, &rsk.kernel_versions, sizeof(old_version));
	VB2_DEBUG("TPM: RollbackKernelWrite %x --> %x\n",
		  (int)old_version, (int)version);
	memcpy(&rsk.kernel_versions, &version, sizeof(version));
	return WriteSpaceKernel(&rsk);
}

uint32_t RollbackKernelLock(int recovery_mode)
{
	static int kernel_locked = 0;
	uint32_t r;

	if (recovery_mode || kernel_locked)
		return TPM_SUCCESS;

	r = TlclLockPhysicalPresence();
	if (TPM_SUCCESS == r)
		kernel_locked = 1;
	return r;
}

uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp)
{
	union {
		/*
		 * Use a union for buf and bf, rather than making bf a pointer
		 * to a bare uint8_t[] buffer.  This ensures bf will be aligned
		 * if necesssary for the target platform.
		 */
		uint8_t buf[FWMP_NV_MAX_SIZE];
		struct RollbackSpaceFwmp bf;
	} u;
	uint32_t r;
	int attempts = 3;

	/* Clear destination in case error or FWMP not present */
	memset(fwmp, 0, sizeof(*fwmp));

	while (attempts--) {
		/* Try to read entire 1.0 struct */
		r = TlclRead(FWMP_NV_INDEX, u.buf, sizeof(u.bf));
		if (r == TPM_E_BADINDEX) {
			/* Missing space is not an error; use defaults */
			VB2_DEBUG("TPM: no FWMP space\n");
			return TPM_SUCCESS;
		} else if (r != TPM_SUCCESS) {
			VB2_DEBUG("TPM: read returned 0x%x\n", r);
			return r;
		}

		/*
		 * Struct must be at least big enough for 1.0, but not bigger
		 * than our buffer size.
		 */
		if (u.bf.struct_size < sizeof(u.bf) ||
		    u.bf.struct_size > sizeof(u.buf))
			return TPM_E_STRUCT_SIZE;

		/*
		 * If space is bigger than we expect, re-read so we properly
		 * compute the CRC.
		 */
		if (u.bf.struct_size > sizeof(u.bf)) {
			r = TlclRead(FWMP_NV_INDEX, u.buf, u.bf.struct_size);
			if (r != TPM_SUCCESS)
				return r;
		}

		/* Verify CRC */
		if (u.bf.crc != vb2_crc8(u.buf + 2, u.bf.struct_size - 2)) {
			VB2_DEBUG("TPM: bad CRC\n");
			continue;
		}

		/* Verify major version is compatible */
		if ((u.bf.struct_version >> 4) !=
		    (ROLLBACK_SPACE_FWMP_VERSION >> 4))
			return TPM_E_STRUCT_VERSION;

		/*
		 * Copy to destination.  Note that if the space is bigger than
		 * we expect (due to a minor version change), we only copy the
		 * part of the FWMP that we know what to do with.
		 *
		 * If this were a 1.1+ reader and the source was a 1.0 struct,
		 * we would need to take care of initializing the extra fields
		 * added in 1.1+.  But that's not an issue yet.
		 */
		memcpy(fwmp, &u.bf, sizeof(*fwmp));
		return TPM_SUCCESS;
	}

	VB2_DEBUG("TPM: too many bad CRCs, giving up\n");
	return TPM_E_CORRUPTED_STATE;
}

#endif /* DISABLE_ROLLBACK_TPM */
