// SPDX-License-Identifier: GPL-2.0
/*
 * efi_secret module
 *
 * Copyright (C) 2022 IBM Corporation
 * Author: Dov Murik <dovmurik@linux.ibm.com>
 */

/**
 * DOC: efi_secret: Allow reading EFI confidential computing (coco) secret area
 * via securityfs interface.
 *
 * When the module is loaded (and securityfs is mounted, typically under
 * /sys/kernel/security), a "secrets/coco" directory is created in securityfs.
 * In it, a file is created for each secret entry.  The name of each such file
 * is the GUID of the secret entry, and its content is the secret data.
 */

#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/security.h>
#include <linux/efi.h>
#include <linux/cacheflush.h>

#define EFI_SECRET_NUM_FILES 64

struct efi_secret {
	struct dentry *secrets_dir;
	struct dentry *fs_dir;
	struct dentry *fs_files[EFI_SECRET_NUM_FILES];
	void __iomem *secret_data;
	u64 secret_data_len;
};

/*
 * Structure of the EFI secret area
 *
 * Offset   Length
 * (bytes)  (bytes)  Usage
 * -------  -------  -----
 *       0       16  Secret table header GUID (must be 1e74f542-71dd-4d66-963e-ef4287ff173b)
 *      16        4  Length of bytes of the entire secret area
 *
 *      20       16  First secret entry's GUID
 *      36        4  First secret entry's length in bytes (= 16 + 4 + x)
 *      40        x  First secret entry's data
 *
 *    40+x       16  Second secret entry's GUID
 *    56+x        4  Second secret entry's length in bytes (= 16 + 4 + y)
 *    60+x        y  Second secret entry's data
 *
 * (... and so on for additional entries)
 *
 * The GUID of each secret entry designates the usage of the secret data.
 */

/**
 * struct secret_header - Header of entire secret area; this should be followed
 * by instances of struct secret_entry.
 * @guid:	Must be EFI_SECRET_TABLE_HEADER_GUID
 * @len:	Length in bytes of entire secret area, including header
 */
struct secret_header {
	efi_guid_t guid;
	u32 len;
} __attribute((packed));

/**
 * struct secret_entry - Holds one secret entry
 * @guid:	Secret-specific GUID (or NULL_GUID if this secret entry was deleted)
 * @len:	Length of secret entry, including its guid and len fields
 * @data:	The secret data (full of zeros if this secret entry was deleted)
 */
struct secret_entry {
	efi_guid_t guid;
	u32 len;
	u8 data[];
} __attribute((packed));

static size_t secret_entry_data_len(struct secret_entry *e)
{
	return e->len - sizeof(*e);
}

static struct efi_secret the_efi_secret;

static inline struct efi_secret *efi_secret_get(void)
{
	return &the_efi_secret;
}

static int efi_secret_bin_file_show(struct seq_file *file, void *data)
{
	struct secret_entry *e = file->private;

	if (e)
		seq_write(file, e->data, secret_entry_data_len(e));

	return 0;
}
DEFINE_SHOW_ATTRIBUTE(efi_secret_bin_file);

/*
 * Overwrite memory content with zeroes, and ensure that dirty cache lines are
 * actually written back to memory, to clear out the secret.
 */
static void wipe_memory(void *addr, size_t size)
{
	memzero_explicit(addr, size);
#ifdef CONFIG_X86
	clflush_cache_range(addr, size);
#endif
}

static int efi_secret_unlink(struct inode *dir, struct dentry *dentry)
{
	struct efi_secret *s = efi_secret_get();
	struct inode *inode = d_inode(dentry);
	struct secret_entry *e = (struct secret_entry *)inode->i_private;
	int i;

	if (e) {
		/* Zero out the secret data */
		wipe_memory(e->data, secret_entry_data_len(e));
		e->guid = NULL_GUID;
	}

	inode->i_private = NULL;

	for (i = 0; i < EFI_SECRET_NUM_FILES; i++)
		if (s->fs_files[i] == dentry)
			s->fs_files[i] = NULL;

	/*
	 * securityfs_remove tries to lock the directory's inode, but we reach
	 * the unlink callback when it's already locked
	 */
	inode_unlock(dir);
	securityfs_remove(dentry);
	inode_lock(dir);

	return 0;
}

static const struct inode_operations efi_secret_dir_inode_operations = {
	.lookup         = simple_lookup,
	.unlink         = efi_secret_unlink,
};

static int efi_secret_map_area(struct platform_device *dev)
{
	int ret;
	struct efi_secret *s = efi_secret_get();
	struct linux_efi_coco_secret_area *secret_area;

	if (efi.coco_secret == EFI_INVALID_TABLE_ADDR) {
		dev_err(&dev->dev, "Secret area address is not available\n");
		return -EINVAL;
	}

	secret_area = memremap(efi.coco_secret, sizeof(*secret_area), MEMREMAP_WB);
	if (secret_area == NULL) {
		dev_err(&dev->dev, "Could not map secret area EFI config entry\n");
		return -ENOMEM;
	}
	if (!secret_area->base_pa || secret_area->size < sizeof(struct secret_header)) {
		dev_err(&dev->dev,
			"Invalid secret area memory location (base_pa=0x%llx size=0x%llx)\n",
			secret_area->base_pa, secret_area->size);
		ret = -EINVAL;
		goto unmap;
	}

	s->secret_data = ioremap_encrypted(secret_area->base_pa, secret_area->size);
	if (s->secret_data == NULL) {
		dev_err(&dev->dev, "Could not map secret area\n");
		ret = -ENOMEM;
		goto unmap;
	}

	s->secret_data_len = secret_area->size;
	ret = 0;

unmap:
	memunmap(secret_area);
	return ret;
}

static void efi_secret_securityfs_teardown(struct platform_device *dev)
{
	struct efi_secret *s = efi_secret_get();
	int i;

	for (i = (EFI_SECRET_NUM_FILES - 1); i >= 0; i--) {
		securityfs_remove(s->fs_files[i]);
		s->fs_files[i] = NULL;
	}

	securityfs_remove(s->fs_dir);
	s->fs_dir = NULL;

	securityfs_remove(s->secrets_dir);
	s->secrets_dir = NULL;

	dev_dbg(&dev->dev, "Removed securityfs entries\n");
}

static int efi_secret_securityfs_setup(struct platform_device *dev)
{
	struct efi_secret *s = efi_secret_get();
	int ret = 0, i = 0, bytes_left;
	unsigned char *ptr;
	struct secret_header *h;
	struct secret_entry *e;
	struct dentry *dent;
	char guid_str[EFI_VARIABLE_GUID_LEN + 1];

	ptr = (void __force *)s->secret_data;
	h = (struct secret_header *)ptr;
	if (efi_guidcmp(h->guid, EFI_SECRET_TABLE_HEADER_GUID)) {
		/*
		 * This is not an error: it just means that EFI defines secret
		 * area but it was not populated by the Guest Owner.
		 */
		dev_dbg(&dev->dev, "EFI secret area does not start with correct GUID\n");
		return -ENODEV;
	}
	if (h->len < sizeof(*h)) {
		dev_err(&dev->dev, "EFI secret area reported length is too small\n");
		return -EINVAL;
	}
	if (h->len > s->secret_data_len) {
		dev_err(&dev->dev, "EFI secret area reported length is too big\n");
		return -EINVAL;
	}

	s->secrets_dir = NULL;
	s->fs_dir = NULL;
	memset(s->fs_files, 0, sizeof(s->fs_files));

	dent = securityfs_create_dir("secrets", NULL);
	if (IS_ERR(dent)) {
		dev_err(&dev->dev, "Error creating secrets securityfs directory entry err=%ld\n",
			PTR_ERR(dent));
		return PTR_ERR(dent);
	}
	s->secrets_dir = dent;

	dent = securityfs_create_dir("coco", s->secrets_dir);
	if (IS_ERR(dent)) {
		dev_err(&dev->dev, "Error creating coco securityfs directory entry err=%ld\n",
			PTR_ERR(dent));
		return PTR_ERR(dent);
	}
	d_inode(dent)->i_op = &efi_secret_dir_inode_operations;
	s->fs_dir = dent;

	bytes_left = h->len - sizeof(*h);
	ptr += sizeof(*h);
	while (bytes_left >= (int)sizeof(*e) && i < EFI_SECRET_NUM_FILES) {
		e = (struct secret_entry *)ptr;
		if (e->len < sizeof(*e) || e->len > (unsigned int)bytes_left) {
			dev_err(&dev->dev, "EFI secret area is corrupted\n");
			ret = -EINVAL;
			goto err_cleanup;
		}

		/* Skip deleted entries (which will have NULL_GUID) */
		if (efi_guidcmp(e->guid, NULL_GUID)) {
			efi_guid_to_str(&e->guid, guid_str);

			dent = securityfs_create_file(guid_str, 0440, s->fs_dir, (void *)e,
						      &efi_secret_bin_file_fops);
			if (IS_ERR(dent)) {
				dev_err(&dev->dev, "Error creating efi_secret securityfs entry\n");
				ret = PTR_ERR(dent);
				goto err_cleanup;
			}

			s->fs_files[i++] = dent;
		}
		ptr += e->len;
		bytes_left -= e->len;
	}

	dev_info(&dev->dev, "Created %d entries in securityfs secrets/coco\n", i);
	return 0;

err_cleanup:
	efi_secret_securityfs_teardown(dev);
	return ret;
}

static void efi_secret_unmap_area(void)
{
	struct efi_secret *s = efi_secret_get();

	if (s->secret_data) {
		iounmap(s->secret_data);
		s->secret_data = NULL;
		s->secret_data_len = 0;
	}
}

static int efi_secret_probe(struct platform_device *dev)
{
	int ret;

	ret = efi_secret_map_area(dev);
	if (ret)
		return ret;

	ret = efi_secret_securityfs_setup(dev);
	if (ret)
		goto err_unmap;

	return ret;

err_unmap:
	efi_secret_unmap_area();
	return ret;
}

static int efi_secret_remove(struct platform_device *dev)
{
	efi_secret_securityfs_teardown(dev);
	efi_secret_unmap_area();
	return 0;
}

static struct platform_driver efi_secret_driver = {
	.probe = efi_secret_probe,
	.remove = efi_secret_remove,
	.driver = {
		.name = "efi_secret",
	},
};

module_platform_driver(efi_secret_driver);

MODULE_DESCRIPTION("Confidential computing EFI secret area access");
MODULE_AUTHOR("IBM");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:efi_secret");
