#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;
};
struct secret_header {
efi_guid_t guid;
u32 len;
} __attribute((packed));
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);
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) {
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;
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)) {
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;
}
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"