git: d12d651f8692 - main - EFI RT: resurrect EFIIOC_GET_TABLE

Konstantin Belousov kib at FreeBSD.org
Sat Jul 3 17:15:40 UTC 2021


The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=d12d651f8692cfcaf6fd0a6e8264c29547f644c9

commit d12d651f8692cfcaf6fd0a6e8264c29547f644c9
Author:     Pavel Balaev <pavel.balaev at 3mdeb.com>
AuthorDate: 2021-07-01 16:27:25 +0000
Commit:     Konstantin Belousov <kib at FreeBSD.org>
CommitDate: 2021-07-03 17:06:48 +0000

    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 at 3mdeb.com>
    MFC after:      2 weeks
    Differential revision:  https://reviews.freebsd.org/D30104
---
 share/man/man4/efidev.4 |  23 +++++++--
 sys/dev/efidev/efidev.c |  23 +++++++++
 sys/dev/efidev/efirt.c  | 128 ++++++++++++++++++++++++++++++++++++++++++++++++
 sys/sys/efi.h           |  39 +++++++++++++++
 sys/sys/efiio.h         |   9 ++++
 5 files changed, 219 insertions(+), 3 deletions(-)

diff --git a/share/man/man4/efidev.4 b/share/man/man4/efidev.4
index b32a44325f92..2cadada51e9c 100644
--- a/share/man/man4/efidev.4
+++ b/share/man/man4/efidev.4
@@ -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"
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)


More information about the dev-commits-src-main mailing list