/*
 * Code Copyright 2012 Red Hat, Inc <mjg@redhat.com>
 *
 * Functions cut and pasted from
 *
 *   git://github.com/mjg59/shim.git
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ---
 *
 * This file is a functional simplification of Original code from TianoCore
 * (http://tianocore.sf.net)
 *
 *   MdePkg/Library/BasePeCoffLib/BasePeCoff.c
 *
 * Copyright (c) 2006 - 2012, Intel Corporation. All rights reserved.<BR>
 * Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
 * This program and the accompanying materials
 * are licensed and made available under the terms and conditions of the BSD License
 * which accompanies this distribution.  The full text of the license may be found at
 * http://opensource.org/licenses/bsd-license.php.
 *
 * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
 * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 */

#include <efi.h>
#include <efilib.h>
#ifdef CONFIG_arm
#ifndef BUILD_EFI
/* FIXME:
 * arm efi leaves a visibilit pragma pushed that won't work for
 * non efi programs, so eliminate it */
#pragma GCC visibility pop
#endif
#endif

#include <pecoff.h>
#include <guid.h>
#include <simple_file.h>
#include <variables.h>
#include <sha256.h>
#include <errors.h>
#include <execute.h>
#include <buildefi.h>

EFI_STATUS
pecoff_read_header(PE_COFF_LOADER_IMAGE_CONTEXT *context, void *data)
{
	EFI_IMAGE_DOS_HEADER *DosHdr = data;
	EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data;

	if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE)
		PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew);

	if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) {
		Print(L"Unsupported image type\n");
		return EFI_UNSUPPORTED;
	}

	if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) {
		Print(L"Unsupported image - Relocations have been stripped\n");
		return EFI_UNSUPPORTED;
	}

	if (PEHdr->Pe32.OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC &&
	    PEHdr->Pe32.OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
		Print(L"Only IA32 or X64 images supported\n");
		return EFI_UNSUPPORTED;
	}

	context->PEHdr = PEHdr;
	if (PEHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
		context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase;
		context->ImageSize = (UINT64)PEHdr->Pe32Plus.OptionalHeader.SizeOfImage;
		context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders;
		context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint;
		context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
		context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
		context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
		context->FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment;

	} else if (PEHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
		context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase;
		context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage;
		context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders;
		context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint;
		context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
		context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes;
		context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
		context->FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment;	}
	context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections;
	context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER));

	if (context->SecDir->VirtualAddress >= context->ImageSize) {
		Print(L"Malformed security header\n");
		return EFI_INVALID_PARAMETER;
	}

	return EFI_SUCCESS;
}

EFI_STATUS
pecoff_image_layout(PE_COFF_LOADER_IMAGE_CONTEXT *context, void **data)
{
	void *buffer = AllocatePool(context->ImageSize);
	EFI_IMAGE_SECTION_HEADER *s;
	int i, size;
	char *base, *end;

	CopyMem(buffer, *data, context->SizeOfHeaders);

	for (i = 0; i < context->NumberOfSections; i++) {
		s = &context->FirstSection[i];
		size = ALIGN_VALUE(s->SizeOfRawData, context->FileAlignment);

		base = pecoff_image_address(buffer, context->ImageSize, s->VirtualAddress);
		end = pecoff_image_address(buffer, context->ImageSize, s->VirtualAddress + size - 1);

		if (!base || !end) {
			Print(L"Invalid section size\n");
			return EFI_UNSUPPORTED;
		}

		if (s->SizeOfRawData > 0)
			CopyMem(base, *data + s->PointerToRawData, size);

		if (size < s->Misc.VirtualSize)
			ZeroMem (base + size, s->Misc.VirtualSize - size);

	}
	//FreePool(*data);
	*data = buffer;

	return EFI_SUCCESS;
}

EFI_STATUS
pecoff_relocate(PE_COFF_LOADER_IMAGE_CONTEXT *context, void **data)
{
	EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd;
	UINT64 Adjust;
	UINT16 *Reloc, *RelocEnd;
	char *Fixup, *FixupBase, *FixupData = NULL;
	UINT16 *Fixup16;
	UINT32 *Fixup32;
	UINT64 *Fixup64;
	int size = context->ImageSize;
	void *ImageEnd = (char *)data + size;
	EFI_STATUS efi_status;

	efi_status = pecoff_image_layout(context, data);
	if (efi_status != EFI_SUCCESS) {
		Print(L"pecoff_image_layout: failed to layout image\n");
		return efi_status;
	}

	if (context->PEHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
		context->PEHdr->Pe32Plus.OptionalHeader.ImageBase = (UINT64)*data;
	} else if (context->PEHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
		context->PEHdr->Pe32.OptionalHeader.ImageBase = (UINT32)(long)*data;
	}

	if (context->NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
		Print(L"Image has no relocation entry\n");
		return EFI_UNSUPPORTED;
	}

	RelocBase = pecoff_image_address(*data, size, context->RelocDir->VirtualAddress);
	RelocBaseEnd = pecoff_image_address(*data, size, context->RelocDir->VirtualAddress + context->RelocDir->Size - 1);

	if (!RelocBase || !RelocBaseEnd) {
		Print(L"Reloc table overflows binary %d %d\n",
		      context->RelocDir->VirtualAddress,
		      context->RelocDir->VirtualAddress + context->RelocDir->Size - 1);
		return EFI_UNSUPPORTED;
	}

	Adjust = (UINT64)*data - context->ImageAddress;

	while (RelocBase < RelocBaseEnd) {
		Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION));
		RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock);

		if ((void *)RelocEnd < *data || (void *)RelocEnd > ImageEnd) {
			Print(L"Reloc entry overflows binary\n");
			return EFI_UNSUPPORTED;
		}

		FixupBase = pecoff_image_address(*data, size, RelocBase->VirtualAddress);
		if (!FixupBase) {
			Print(L"Invalid fixupbase\n");
			return EFI_UNSUPPORTED;
		}

		while (Reloc < RelocEnd) {
			Fixup = FixupBase + (*Reloc & 0xFFF);
			switch ((*Reloc) >> 12) {
			case EFI_IMAGE_REL_BASED_ABSOLUTE:
				break;

			case EFI_IMAGE_REL_BASED_HIGH:
				Fixup16   = (UINT16 *) Fixup;
				*Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16)));
				if (FixupData != NULL) {
					*(UINT16 *) FixupData = *Fixup16;
					FixupData             = FixupData + sizeof (UINT16);
				}
				break;

			case EFI_IMAGE_REL_BASED_LOW:
				Fixup16   = (UINT16 *) Fixup;
				*Fixup16  = (UINT16) (*Fixup16 + (UINT16) Adjust);
				if (FixupData != NULL) {
					*(UINT16 *) FixupData = *Fixup16;
					FixupData             = FixupData + sizeof (UINT16);
				}
				break;

			case EFI_IMAGE_REL_BASED_HIGHLOW:
				Fixup32   = (UINT32 *) Fixup;
				*Fixup32  = *Fixup32 + (UINT32) Adjust;
				if (FixupData != NULL) {
					FixupData             = ALIGN_POINTER (FixupData, sizeof (UINT32));
					*(UINT32 *)FixupData  = *Fixup32;
					FixupData             = FixupData + sizeof (UINT32);
				}
				break;

			case EFI_IMAGE_REL_BASED_DIR64:
				Fixup64 = (UINT64 *) Fixup;
				*Fixup64 = *Fixup64 + (UINT64) Adjust;
				if (FixupData != NULL) {
					FixupData = ALIGN_POINTER (FixupData, sizeof(UINT64));
					*(UINT64 *)(FixupData) = *Fixup64;
					FixupData = FixupData + sizeof(UINT64);
				}
				break;

			default:
				Print(L"Unknown relocation\n");
				return EFI_UNSUPPORTED;
			}
			Reloc += 1;
		}
		RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd;
	}

	return EFI_SUCCESS;
}

EFI_STATUS
pecoff_get_signature(PE_COFF_LOADER_IMAGE_CONTEXT *context, void *buffer,
		     WIN_CERTIFICATE **data, int signum)
{
	WIN_CERTIFICATE *cert;
	UINT32 offset;
	int i;

	if (!context->SecDir->Size)
		return EFI_NOT_FOUND;

	offset = context->SecDir->VirtualAddress;
	cert = (WIN_CERTIFICATE *)(buffer + offset);
	for (i = 0; i < signum; i++) {
		offset += ALIGN_VALUE(cert->dwLength, 8);
		cert = (WIN_CERTIFICATE *)(buffer + offset);
		if (offset >= context->SecDir->VirtualAddress + context->SecDir->Size)
			break;
	}
	if (i != signum)
		return EFI_NOT_FOUND;

	*data = cert;
	return EFI_SUCCESS;
}

#ifdef BUILD_EFI
EFI_STATUS
pecoff_check_mok(EFI_HANDLE image, CHAR16 *name)
{
	EFI_STATUS status;
	UINT8 hash[SHA256_DIGEST_SIZE];
	UINT8 *data;
	UINTN len;
	UINT32 attr;

	/* first check is MokSBState.  If we're in insecure mode, boot
	 * anyway regardless of dbx contents */
	status = get_variable_attr(L"MokSBState", &data, &len,
				   MOK_OWNER, &attr);
	if (status == EFI_SUCCESS) {
		UINT8 MokSBState = data[0];

		FreePool(data);
		if ((attr & EFI_VARIABLE_RUNTIME_ACCESS) == 0
		    && MokSBState)
			return EFI_SUCCESS;
	}

	status = sha256_get_pecoff_digest(image, name, hash);
	if (status != EFI_SUCCESS)
		return status;

	if (find_in_variable_esl(L"dbx", SIG_DB, hash, SHA256_DIGEST_SIZE)
	    == EFI_SUCCESS)
		/* MOK list cannot override dbx */
		goto check_tmplist;

	status = get_variable_attr(L"MokList", &data, &len, MOK_OWNER, &attr);
	if (status != EFI_SUCCESS)
		goto check_tmplist;
	FreePool(data);

	if (attr & EFI_VARIABLE_RUNTIME_ACCESS)
		goto check_tmplist;

	if (find_in_variable_esl(L"MokList", MOK_OWNER, hash, SHA256_DIGEST_SIZE) == EFI_SUCCESS)
		return EFI_SUCCESS;

 check_tmplist:
	status = get_variable_attr(L"tmpHashList", &data, &len, MOK_OWNER,
				   &attr);
	if (status == EFI_SUCCESS && attr == EFI_VARIABLE_BOOTSERVICE_ACCESS
	    && find_in_variable_esl(L"tmpHashList", MOK_OWNER, hash,
				    SHA256_DIGEST_SIZE) == EFI_SUCCESS)
		return EFI_SUCCESS;

	return EFI_SECURITY_VIOLATION;
}

EFI_STATUS
pecoff_execute_checked(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab, CHAR16 *name)
{
	EFI_STATUS status;
	EFI_LOADED_IMAGE *li;
	EFI_DEVICE_PATH *loadpath = NULL;
	CHAR16 *PathName = NULL;
	EFI_HANDLE h;
	EFI_FILE *file;

	status = BS->HandleProtocol(image, &IMAGE_PROTOCOL, (VOID **)&li);
	if (status != EFI_SUCCESS)
		return status;
	status = generate_path(name, li, &loadpath, &PathName);
	if (status != EFI_SUCCESS)
		return status;
	status = BS->LoadImage(FALSE, image, loadpath, NULL, 0, &h);
	if (status == EFI_SECURITY_VIOLATION || status == EFI_ACCESS_DENIED)
		status = pecoff_check_mok(image, name);
	if (status != EFI_SUCCESS)
		/* this will fail if signature validation fails */
		return status;
	BS->UnloadImage(h);

	status = simple_file_open(image, name, &file, EFI_FILE_MODE_READ);
	if (status != EFI_SUCCESS)
		return status;

	pecoff_execute_image(file, name, image, systab);
	simple_file_close(file);

	return status;
}

EFI_STATUS
pecoff_execute_image(EFI_FILE *file, CHAR16 *name, EFI_HANDLE image,
		     EFI_SYSTEM_TABLE *systab)
{
	UINTN DataSize;
	void *buffer;
	EFI_STATUS efi_status;
	PE_COFF_LOADER_IMAGE_CONTEXT context;
	EFI_STATUS (EFIAPI *entry_point) (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table);

	efi_status = simple_file_read_all(file, &DataSize, &buffer);
	if (efi_status != EFI_SUCCESS) {
		Print(L"Failed to read %s\n", name);
		return efi_status;
	}

	Print(L"Read %d bytes from %s\n", DataSize, name);
	efi_status = pecoff_read_header(&context, buffer);
	if (efi_status != EFI_SUCCESS) {
		Print(L"Failed to read header\n");
		goto out;
	}

	efi_status = pecoff_relocate(&context, &buffer);
	if (efi_status != EFI_SUCCESS) {
		Print(L"Failed to relocate image\n");
		goto out;
	}

	entry_point = pecoff_image_address(buffer, context.ImageSize, context.EntryPoint);
	if (!entry_point) {
		Print(L"Invalid entry point\n");
		efi_status = EFI_UNSUPPORTED;
		goto out;
	}

	efi_status = entry_point(image, systab);

 out:
	FreePool(buffer);

	return efi_status;
}
#endif
