// 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;
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 1 e74f542 - 71 dd - 4 d66 - 963 e - 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 inode *inode = d_inode(dentry);
struct secret_entry *e = (struct secret_entry *)inode->i_private;
if (e) {
/* Zero out the secret data */
wipe_memory(e->data, secret_entry_data_len(e));
e->guid = NULL_GUID;
}
inode->i_private = NULL;
return simple_unlink(inode, dentry);
}
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();
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, *dir;
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;
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;
dir = securityfs_create_dir("coco" , s->secrets_dir);
if (IS_ERR(dir)) {
dev_err(&dev->dev, "Error creating coco securityfs directory entry err=%ld\n" ,
PTR_ERR(dir));
return PTR_ERR(dir);
}
d_inode(dir)->i_op = &efi_secret_dir_inode_operations;
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 , 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;
}
i++;
}
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 void efi_secret_remove(struct platform_device *dev)
{
efi_secret_securityfs_teardown(dev);
efi_secret_unmap_area();
}
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" );
Messung V0.5 in Prozent C=93 H=94 G=93
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland