EFI RT: resurrect EFIIOC_GET_TABLE

Make it work, but change the interface to be safe for non-root users. In
particular, right now interface only works for the tables which can be
minimally parsed by kernel to determine the table size. Then, userspace can
query the table size, after that it provides a buffer of needed size
and kernel copies out just table to userspace.

Main advantage is that user no longer need to be able to read /dev/mem,
the disadvantage is the need to have minimal parsers aware of the table
types.  Right now the parsers are implemented for ESRT and PROP tables.

Future extension of the present interface might be a return of only
the table physical address, in case kernel does not have suitable
parser yet. Then, a privileged user could read the table from /dev/mem.
This extension, which logically equivalent to the old (non-worked)
EFIIOC_GET_TABLE variant, is not implemented until needed.

Submitted by:	Pavel Balaev <pavel.balaev@3mdeb.com>
MFC after:	2 weeks
Differential revision:	https://reviews.freebsd.org/D30104
This commit is contained in:
Pavel Balaev 2021-07-01 19:27:25 +03:00 committed by Konstantin Belousov
parent 2f514e6f13
commit d12d651f86
5 changed files with 219 additions and 3 deletions

View File

@ -26,7 +26,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd August 12, 2018
.Dd June 18, 2021
.Dt EFIDEV 4
.Os
.Sh NAME
@ -71,11 +71,28 @@ with supplemental structures and constants defined in
.In sys/efi.h :
.Bl -tag -width indent
.It Dv EFIIOC_GET_TABLE Pq Vt "struct efi_get_table_ioc"
Get a table by uuid from the UEFI system table.
Copy the UEFI table specified by the
.Va uuid
field of the
.Vt struct efi_get_table_ioc
into the
.Va buf
field.
The memory size for the buf field can be queried by passing
.Dv NULL
pointer as a buf value.
The required size will be stored in the
.Va table_len
field.
The size of the allocated memory must be specified in the
.Va buf_len
field.
.Bd -literal -offset indent
struct efi_get_table_ioc {
void *buf;
struct uuid uuid;
void *ptr;
size_t table_len;
size_t buf_len;
};
.Ed
.It Dv EFIIOC_GET_TIME Pq Vt "struct efi_tm"

View File

@ -53,6 +53,29 @@ efidev_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
int error;
switch (cmd) {
case EFIIOC_GET_TABLE:
{
struct efi_get_table_ioc *egtioc =
(struct efi_get_table_ioc *)addr;
void *buf = NULL;
error = efi_copy_table(&egtioc->uuid, egtioc->buf ? &buf : NULL,
egtioc->buf_len, &egtioc->table_len);
if (error != 0 || egtioc->buf == NULL)
break;
if (egtioc->buf_len < egtioc->table_len) {
error = EINVAL;
free(buf, M_TEMP);
break;
}
error = copyout(buf, egtioc->buf, egtioc->buf_len);
free(buf, M_TEMP);
break;
}
case EFIIOC_GET_TIME:
{
struct efi_tm *tm = (struct efi_tm *)addr;

View File

@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/linker.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/clock.h>
@ -47,6 +48,7 @@ __FBSDID("$FreeBSD$");
#include <sys/sched.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/vmmeter.h>
#include <machine/fpu.h>
@ -58,6 +60,8 @@ __FBSDID("$FreeBSD$");
#include <vm/pmap.h>
#include <vm/vm_map.h>
#define EFI_TABLE_ALLOC_MAX 0x800000
static struct efi_systbl *efi_systbl;
static eventhandler_tag efi_shutdown_tag;
/*
@ -96,6 +100,11 @@ static int efi_status2err[25] = {
EPROTO /* EFI_PROTOCOL_ERROR */
};
enum efi_table_type {
TYPE_ESRT = 0,
TYPE_PROP
};
static int efi_enter(void);
static void efi_leave(void);
@ -336,6 +345,124 @@ get_table(struct uuid *uuid, void **ptr)
return (ENOENT);
}
static int
get_table_length(enum efi_table_type type, size_t *table_len, void **taddr)
{
switch (type) {
case TYPE_ESRT:
{
struct efi_esrt_table *esrt = NULL;
struct uuid uuid = EFI_TABLE_ESRT;
uint32_t fw_resource_count = 0;
size_t len = sizeof(*esrt);
int error;
void *buf;
error = efi_get_table(&uuid, (void **)&esrt);
if (error != 0)
return (error);
buf = malloc(len, M_TEMP, M_WAITOK);
error = physcopyout((vm_paddr_t)esrt, buf, len);
if (error != 0) {
free(buf, M_TEMP);
return (error);
}
/* Check ESRT version */
if (((struct efi_esrt_table *)buf)->fw_resource_version !=
ESRT_FIRMWARE_RESOURCE_VERSION) {
free(buf, M_TEMP);
return (ENODEV);
}
fw_resource_count = ((struct efi_esrt_table *)buf)->
fw_resource_count;
if (fw_resource_count > EFI_TABLE_ALLOC_MAX /
sizeof(struct efi_esrt_entry_v1)) {
free(buf, M_TEMP);
return (ENOMEM);
}
len += fw_resource_count * sizeof(struct efi_esrt_entry_v1);
*table_len = len;
if (taddr != NULL)
*taddr = esrt;
free(buf, M_TEMP);
return (0);
}
case TYPE_PROP:
{
struct uuid uuid = EFI_PROPERTIES_TABLE;
struct efi_prop_table *prop;
size_t len = sizeof(*prop);
uint32_t prop_len;
int error;
void *buf;
error = efi_get_table(&uuid, (void **)&prop);
if (error != 0)
return (error);
buf = malloc(len, M_TEMP, M_WAITOK);
error = physcopyout((vm_paddr_t)prop, buf, len);
if (error != 0) {
free(buf, M_TEMP);
return (error);
}
prop_len = ((struct efi_prop_table *)buf)->length;
if (prop_len > EFI_TABLE_ALLOC_MAX) {
free(buf, M_TEMP);
return (ENOMEM);
}
*table_len = prop_len;
if (taddr != NULL)
*taddr = prop;
free(buf, M_TEMP);
return (0);
}
}
return (ENOENT);
}
static int
copy_table(struct uuid *uuid, void **buf, size_t buf_len, size_t *table_len)
{
static const struct known_table {
struct uuid uuid;
enum efi_table_type type;
} tables[] = {
{ EFI_TABLE_ESRT, TYPE_ESRT },
{ EFI_PROPERTIES_TABLE, TYPE_PROP }
};
size_t table_idx;
void *taddr;
int rc;
for (table_idx = 0; table_idx < nitems(tables); table_idx++) {
if (!bcmp(&tables[table_idx].uuid, uuid, sizeof(*uuid)))
break;
}
if (table_idx == nitems(tables))
return (EINVAL);
rc = get_table_length(tables[table_idx].type, table_len, &taddr);
if (rc != 0)
return rc;
/* return table length to userspace */
if (buf == NULL)
return (0);
*buf = malloc(*table_len, M_TEMP, M_WAITOK);
rc = physcopyout((vm_paddr_t)taddr, *buf, *table_len);
return (rc);
}
static int efi_rt_handle_faults = EFI_RT_HANDLE_FAULTS_DEFAULT;
SYSCTL_INT(_machdep, OID_AUTO, efi_rt_handle_faults, CTLFLAG_RWTUN,
&efi_rt_handle_faults, 0,
@ -568,6 +695,7 @@ var_set(efi_char *name, struct uuid *vendor, uint32_t attrib,
const static struct efi_ops efi_ops = {
.rt_ok = rt_ok,
.get_table = get_table,
.copy_table = copy_table,
.get_time = get_time,
.get_time_capabilities = get_time_capabilities,
.reset_system = reset_system,

View File

@ -40,6 +40,10 @@
{0xeb9d2d31,0x2d88,0x11d3,0x9a,0x16,{0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define EFI_TABLE_SMBIOS3 \
{0xf2fd1544,0x9794,0x4a2c,0x99,0x2e,{0xe5,0xbb,0xcf,0x20,0xe3,0x94}}
#define EFI_TABLE_ESRT \
{0xb122a263,0x3661,0x4f68,0x99,0x29,{0x78,0xf8,0xb0,0xd6,0x21,0x80}}
#define EFI_PROPERTIES_TABLE \
{0x880aaca3,0x4adc,0x4a04,0x90,0x79,{0xb7,0x47,0x34,0x08,0x25,0xe5}}
enum efi_reset {
EFI_RESET_COLD = 0,
@ -123,6 +127,31 @@ struct efi_tblhdr {
uint32_t __res;
};
#define ESRT_FIRMWARE_RESOURCE_VERSION 1
struct efi_esrt_table {
uint32_t fw_resource_count;
uint32_t fw_resource_count_max;
uint64_t fw_resource_version;
uint8_t entries[];
};
struct efi_esrt_entry_v1 {
struct uuid fw_class;
uint32_t fw_type;
uint32_t fw_version;
uint32_t lowest_supported_fw_version;
uint32_t capsule_flags;
uint32_t last_attempt_version;
uint32_t last_attempt_status;
};
struct efi_prop_table {
uint32_t version;
uint32_t length;
uint64_t memory_protection_attribute;
};
#ifdef _KERNEL
#ifdef EFIABI_ATTR
@ -188,6 +217,7 @@ struct efi_ops {
*/
int (*rt_ok)(void);
int (*get_table)(struct uuid *, void **);
int (*copy_table)(struct uuid *, void **, size_t, size_t *);
int (*get_time)(struct efi_tm *);
int (*get_time_capabilities)(struct efi_tmcap *);
int (*reset_system)(enum efi_reset);
@ -216,6 +246,15 @@ static inline int efi_get_table(struct uuid *uuid, void **ptr)
return (active_efi_ops->get_table(uuid, ptr));
}
static inline int efi_copy_table(struct uuid *uuid, void **buf,
size_t buf_len, size_t *table_len)
{
if (active_efi_ops->copy_table == NULL)
return (ENXIO);
return (active_efi_ops->copy_table(uuid, buf, buf_len, table_len));
}
static inline int efi_get_time(struct efi_tm *tm)
{

View File

@ -32,6 +32,14 @@
#include <sys/uuid.h>
#include <sys/efi.h>
struct efi_get_table_ioc
{
void *buf; /* Pointer to userspace buffer */
struct uuid uuid; /* UUID to look up */
size_t table_len; /* Table size */
size_t buf_len; /* Size of the buffer */
};
struct efi_var_ioc
{
efi_char *name; /* User pointer to name, in wide chars */
@ -42,6 +50,7 @@ struct efi_var_ioc
size_t datasize; /* Number of *bytes* in the data */
};
#define EFIIOC_GET_TABLE _IOWR('E', 1, struct efi_get_table_ioc)
#define EFIIOC_GET_TIME _IOR('E', 2, struct efi_tm)
#define EFIIOC_SET_TIME _IOW('E', 3, struct efi_tm)
#define EFIIOC_VAR_GET _IOWR('E', 4, struct efi_var_ioc)