aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorPavel Balaev <pavel.balaev@3mdeb.com>2021-07-01 16:27:25 +0000
committerKonstantin Belousov <kib@FreeBSD.org>2021-07-03 17:06:48 +0000
commitd12d651f8692cfcaf6fd0a6e8264c29547f644c9 (patch)
tree90a276a61982ddd26e690970e793603f53a13dfc /sys
parent2f514e6f13de41aa9ad5f563ed0decc66e91f99c (diff)
downloadsrc-d12d651f8692cfcaf6fd0a6e8264c29547f644c9.tar.gz
src-d12d651f8692cfcaf6fd0a6e8264c29547f644c9.zip
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
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/efidev/efidev.c23
-rw-r--r--sys/dev/efidev/efirt.c128
-rw-r--r--sys/sys/efi.h39
-rw-r--r--sys/sys/efiio.h9
4 files changed, 199 insertions, 0 deletions
diff --git a/sys/dev/efidev/efidev.c b/sys/dev/efidev/efidev.c
index 303b10c1d0ba..79d98956ed24 100644
--- a/sys/dev/efidev/efidev.c
+++ b/sys/dev/efidev/efidev.c
@@ -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;
diff --git a/sys/dev/efidev/efirt.c b/sys/dev/efidev/efirt.c
index aa7e9afdb69d..9ba0508f1902 100644
--- a/sys/dev/efidev/efirt.c
+++ b/sys/dev/efidev/efirt.c
@@ -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,
diff --git a/sys/sys/efi.h b/sys/sys/efi.h
index 7f9408d19b39..6ace7dd6e523 100644
--- a/sys/sys/efi.h
+++ b/sys/sys/efi.h
@@ -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)
{
diff --git a/sys/sys/efiio.h b/sys/sys/efiio.h
index e5a0763536a3..803aed6a965e 100644
--- a/sys/sys/efiio.h
+++ b/sys/sys/efiio.h
@@ -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)