From: Matthew Dillon Date: Tue, 22 Nov 2016 22:53:50 +0000 (-0800) Subject: efi - Add EFI run-time ABI support X-Git-Tag: v4.8.0rc~521 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/bb7548fd68273597588b57868a739b2d3a8a8d94 efi - Add EFI run-time ABI support * Add EFI run-time ABI support, ability to query and set the time, scan EFI BIOS variables, etc. * Port from FreeBSD. Use our vmspace management functions to handle the specialized pmap requirements instead of rerolling the page table. Make adjustments for differences in the device API. etc. Ported-by: swildner, dillon Ported-from: FreeBSD, Warner Losh --- diff --git a/lib/Makefile b/lib/Makefile index 1b09eed86e..3976ebdb64 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -67,6 +67,7 @@ SUBDIR= ${SUBDIR_ORDERED} \ libdl \ libdm \ ${_libdmsg} \ + libefivar \ libevtr \ libexpat \ libfsid \ diff --git a/lib/libefivar/Makefile b/lib/libefivar/Makefile new file mode 100644 index 0000000000..840ccdf61a --- /dev/null +++ b/lib/libefivar/Makefile @@ -0,0 +1,51 @@ +# Copyright 1998 Juniper Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD: head/lib/libefivar/Makefile 307071 2016-10-11 22:30:41Z imp $ + +PACKAGE=lib${LIB} +LIB= efivar +SRCS= efivar.c libefivar.c +INCS= efivar.h +SHLIB_MAJOR= 1 +MAN= efivar.3 + +MLINKS+=efivar.3 efi_set_variables_supported.3 \ + efivar.3 efi_del_variable.3 \ + efivar.3 efi_get_variable.3 \ + efivar.3 efi_get_variable_attributes.3 \ + efivar.3 efi_get_variable_size.3 \ + efivar.3 efi_append_variable.3 \ + efivar.3 efi_set_variable.3 \ + efivar.3 efi_get_next_variable_name.3 \ + efivar.3 efi_str_to_guid.3 \ + efivar.3 efi_guid_to_str.3 \ + efivar.3 efi_name_to_guid.3 \ + efivar.3 efi_guid_to_name.3 \ + efivar.3 efi_guid_to_symbol.3 \ + efivar.3 libefivar.3 + +WARNS?= 6 + +.include diff --git a/lib/libefivar/efivar.3 b/lib/libefivar/efivar.3 new file mode 100644 index 0000000000..2bcb7fbd01 --- /dev/null +++ b/lib/libefivar/efivar.3 @@ -0,0 +1,100 @@ +.\" Copyright 2016 Netflix, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libefivar/efivar.3 307071 2016-10-11 22:30:41Z imp $ +.\" +.Dd September 14, 2016 +.Dt LIBEFIVAR 3 +.Os +.Sh NAME +.Nm libefivar +.Nd EFI Non Volatile Variable Support +.Sh LIBRARY +.Lb libefivar +.Sh SYNOPSIS +.In efivar.h +.Ft int +.Fn efi_append_variable "efi_guid_t guid" "const char *name" "void *data" "size_t data_size" "uint32_t attributes" +.Ft int +.Fn efi_del_variable "efi_guid_t guid" "const char *name" +.Ft int +.Fn efi_get_variable "efi_guid_t guid" "const char *name" "void **data" "ssize_t *data_size" "uint32_t *attributes" +.Ft int +.Fn efi_get_variable_attributes "efi_guid_t guid" "const char *name" "uint32_t *attributes" +.Ft int +.Fn efi_get_variable_size "efi_guid_t guid" "const char *name" "size_t *size" +.Ft int +.Fn efi_get_next_variable_name "efi_guid_t **guid" "char **name" +.Ft int +.Fn efi_guid_to_name "efi_guid_t *guid" "char **name" +.Ft int +.Fn efi_guid_to_symbol "efi_guid_t *guid" "char **symbol" +.Ft int +.Fn efi_guid_to_str "const efi_guid_t *guid" "char **sp" +.Ft int +.Fn efi_name_to_guid "const char *name" "efi_guid_t *guid" +.Ft int +.Fn efi_set_variable "efi_guid_t guid" "const char *name" "void *data" "size_t data_size" "uint32_t attributes" +.Ft int +.Fn efi_str_to_guid "const char *s" "efi_guid_t *guid"; +.Ft int +.Fn efi_variables_supported "void"; +.Sh DESCRIPTION +The +.Nm +library implements access to EFI Variables via the EFI Runtime +Serivces. +All char * strings are converted to 16-bit UTF strings before passing +them to EFI. +.Pp +.Fn efi_variables_supported +returns non-zero if the current machine supports setting of EFI firmware +variables and the kernel support for doing so is present. +Otherwise zero is returned. +.Pp +.Fn efi_del_variable +deletes the EFI variable selected by +.Dv guid +and +.Dv name . +.Pp +.Fn efi_get_variable +.Fn efi_get_variable_attributes +.Fn efi_get_variable_size +.Fn efi_append_variable +.Fn efi_set_variable +.Fn efi_get_next_variable_name +.Fn efi_str_to_guid +.Fn efi_guid_to_str +.Fn efi_name_to_guid +.Fn efi_guid_to_name +.Fn efi_guid_to_symbol +This function is not actually implemented. +.Sh BUGS +No facilities exist to process the strings as native UTF. +This is a limitation in the Linux libefivar library interface. +.Sh AUTHORS +.An -nosplit +This software was originally written by +.An Warner Losh . diff --git a/lib/libefivar/efivar.c b/lib/libefivar/efivar.c new file mode 100644 index 0000000000..6936e738ed --- /dev/null +++ b/lib/libefivar/efivar.c @@ -0,0 +1,381 @@ +/*- + * Copyright (c) 2016 Netflix, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: head/lib/libefivar/efivar.c 307189 2016-10-13 06:56:23Z imp $"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libefivar_int.h" + +static int efi_fd = -2; + +#define Z { 0, 0, 0, 0, 0, { 0 } } + +const efi_guid_t efi_guid_empty = Z; + +static struct uuid_table +{ + const char *uuid_str; + const char *name; + efi_guid_t guid; +} guid_tbl [] = +{ + { "00000000-0000-0000-0000-000000000000", "zero", Z }, + { "093e0fae-a6c4-4f50-9f1b-d41e2b89c19a", "sha512", Z }, + { "0abba7dc-e516-4167-bbf5-4d9d1c739416", "redhat", Z }, + { "0b6e5233-a65c-44c9-9407-d9ab83bfc8bd", "sha224", Z }, + { "126a762d-5758-4fca-8531-201a7f57f850", "lenovo_boot_menu", Z }, + { "3bd2a492-96c0-4079-b420-fcf98ef103ed", "x509_sha256", Z }, + { "3c5766e8-269c-4e34-aa14-ed776e85b3b6", "rsa2048", Z }, + { "3CC24E96-22C7-41D8-8863-8E39DCDCC2CF", "lenovo", Z }, + { "3f7e615b-0d45-4f80-88dc-26b234958560", "lenovo_diag", Z }, + { "446dbf63-2502-4cda-bcfa-2465d2b0fe9d", "x509_sha512", Z }, + { "4aafd29d-68df-49ee-8aa9-347d375665a7", "pkcs7_cert", Z }, + { "605dab50-e046-4300-abb6-3dd810dd8b23", "shim", Z }, + { "665d3f60-ad3e-4cad-8e26-db46eee9f1b5", "lenovo_rescue", Z }, + { "67f8444f-8743-48f1-a328-1eaab8736080", "rsa2048_sha1", Z }, + { "7076876e-80c2-4ee6-aad2-28b349a6865b", "x509_sha384", Z }, + { "721c8b66-426c-4e86-8e99-3457c46ab0b9", "lenovo_setup", Z }, + { "77fa9abd-0359-4d32-bd60-28f4e78f784b", "microsoft", Z }, + { "7FACC7B6-127F-4E9C-9C5D-080F98994345", "lenovo_2", Z }, + { "826ca512-cf10-4ac9-b187-be01496631bd", "sha1", Z }, + { "82988420-7467-4490-9059-feb448dd1963", "lenovo_me_config", Z }, + { "8be4df61-93ca-11d2-aa0d-00e098032b8c", "global", Z }, + { "a5c059a1-94e4-4aa7-87b5-ab155c2bf072", "x509_cert", Z }, + { "a7717414-c616-4977-9420-844712a735bf", "rsa2048_sha256_cert", Z }, + { "a7d8d9a6-6ab0-4aeb-ad9d-163e59a7a380", "lenovo_diag_splash", Z }, + { "ade9e48f-9cb8-98e6-31af-b4e6009e2fe3", "redhat_2", Z }, + { "bc7838d2-0f82-4d60-8316-c068ee79d25b", "lenovo_msg", Z }, + { "c1c41626-504c-4092-aca9-41f936934328", "sha256", Z }, + { "c57ad6b7-0515-40a8-9d21-551652854e37", "shell", Z }, + { "d719b2cb-3d3a-4596-a3bc-dad00e67656f", "security", Z }, + { "e2b36190-879b-4a3d-ad8d-f2e7bba32784", "rsa2048_sha256", Z }, + { "ff3e5307-9fd0-48c9-85f1-8ad56c701e01", "sha384", Z }, + { "f46ee6f4-4785-43a3-923d-7f786c3c8479", "lenovo_startup_interrupt", Z }, + { "ffffffff-ffff-ffff-ffff-ffffffffffff", "zzignore-this-guid", Z }, +}; +#undef Z + +static void +efi_guid_tbl_compile(void) +{ + size_t i; + uint32_t status; + + for (i = 0; i < nitems(guid_tbl); i++) { + uuid_from_string(guid_tbl[i].uuid_str, &guid_tbl[i].guid, + &status); + /* all f's is a bad version, so ignore that error */ + if (status != uuid_s_ok && status != uuid_s_bad_version) + fprintf(stderr, "Can't convert %s to a uuid for %s: %d\n", + guid_tbl[i].uuid_str, guid_tbl[i].name, (int)status); + } +} + +static int +efi_open_dev(void) +{ + + if (efi_fd == -2) + efi_fd = open("/dev/efi", O_RDWR); + if (efi_fd < 0) + efi_fd = -1; + else + efi_guid_tbl_compile(); + return (efi_fd); +} + +static void +efi_var_reset(struct efi_var_ioc *var) +{ + var->name = NULL; + var->namesize = 0; + memset(&var->vendor, 0, sizeof(var->vendor)); + var->attrib = 0; + var->data = NULL; + var->datasize = 0; +} + +static int +rv_to_linux_rv(int rv) +{ + if (rv == 0) + rv = 1; + else if (errno == ENOENT) { + rv = 0; + errno = 0; + } else + rv = -errno; + return (rv); +} + +int +efi_append_variable(efi_guid_t guid, const char *name, + uint8_t *data, size_t data_size, uint32_t attributes) +{ + + return efi_set_variable(guid, name, data, data_size, + attributes | EFI_VARIABLE_APPEND_WRITE, 0); +} + +int +efi_del_variable(efi_guid_t guid, const char *name) +{ + + /* data_size of 0 deletes the variable */ + return efi_set_variable(guid, name, NULL, 0, 0, 0); +} + +int +efi_get_variable(efi_guid_t guid, const char *name, + uint8_t **data, size_t *data_size, uint32_t *attributes) +{ + struct efi_var_ioc var; + int rv; + static uint8_t buf[1024*32]; + + if (efi_open_dev() == -1) + return -1; + + efi_var_reset(&var); + rv = libefi_utf8_to_ucs2(name, &var.name, &var.namesize); + if (rv != 0) + goto errout; + var.vendor = guid; + var.data = buf; + var.datasize = sizeof(buf); + rv = ioctl(efi_fd, EFIIOC_VAR_GET, &var); + if (data_size != NULL) + *data_size = var.datasize; + if (data != NULL) + *data = buf; + if (attributes != NULL) + *attributes = var.attrib; +errout: + free(var.name); + + return rv_to_linux_rv(rv); +} + +int +efi_get_variable_attributes(efi_guid_t guid, const char *name, + uint32_t *attributes) +{ + /* Make sure this construct works -- I think it will fail */ + + return efi_get_variable(guid, name, NULL, NULL, attributes); +} + +int +efi_get_variable_size(efi_guid_t guid, const char *name, + size_t *size) +{ + + /* XXX check to make sure this matches the linux value */ + + *size = 0; + return efi_get_variable(guid, name, NULL, size, NULL); +} + +int +efi_get_next_variable_name(efi_guid_t **guid, char **name) +{ + struct efi_var_ioc var; + int rv; + static efi_char *buf; + static size_t buflen = 256 * sizeof(efi_char); + static efi_guid_t retguid; + size_t size; + + if (efi_open_dev() == -1) + return -1; + + + if (buf == NULL) + buf = malloc(buflen); + +again: + efi_var_reset(&var); + var.name = buf; + var.namesize = buflen; + if (*name == NULL) { + *buf = 0; + /* GUID zeroed in var_reset */ + } else { + rv = libefi_utf8_to_ucs2(*name, &var.name, &size); + if (rv != 0) + goto errout; + var.vendor = **guid; + } + rv = ioctl(efi_fd, EFIIOC_VAR_NEXT, &var); + if (rv == 0 && var.name == NULL) { + /* + * oops, too little space. Try again. + */ + void *new = realloc(buf, buflen); + buflen = var.namesize; + if (new == NULL) { + rv = -1; + errno = ENOMEM; + goto done; + } + buf = new; + goto again; + } + + if (rv == 0) { + *name = NULL; /* XXX */ + var.name[var.namesize / sizeof(efi_char)] = 0; /* EFI doesn't NUL terminate */ + rv = libefi_ucs2_to_utf8(var.name, name); + if (rv != 0) + goto errout; + retguid = var.vendor; + *guid = &retguid; + } +errout: + + /* XXX The linux interface expects name to be a static buffer -- fix or leak memory? */ +done: + return (rv_to_linux_rv(rv)); +} + +int +efi_guid_cmp(const efi_guid_t *guid1, const efi_guid_t *guid2) +{ + uint32_t status; + + return uuid_compare(guid1, guid2, &status); +} + +int +efi_guid_is_zero(const efi_guid_t *guid) +{ + uint32_t status; + + return uuid_is_nil(guid, &status); +} + +int +efi_guid_to_name(efi_guid_t *guid, char **name) +{ + size_t i; + uint32_t status; + + for (i = 0; i < nitems(guid_tbl); i++) { + if (uuid_equal(guid, &guid_tbl[i].guid, &status)) { + *name = strdup(guid_tbl[i].name); + return (0); + } + } + return (efi_guid_to_str(guid, name)); +} + +int +efi_guid_to_symbol(efi_guid_t *guid __unused, char **symbol __unused) +{ + + /* + * Unsure what this is used for, efibootmgr doesn't use it. + * Leave unimplemented for now. + */ + return -1; +} + +int +efi_guid_to_str(const efi_guid_t *guid, char **sp) +{ + uint32_t status; + + /* knows efi_guid_t is a typedef of uuid_t */ + uuid_to_string(guid, sp, &status); + + return (status == uuid_s_ok ? 0 : -1); +} + +int +efi_name_to_guid(const char *name, efi_guid_t *guid) +{ + size_t i; + + for (i = 0; i < nitems(guid_tbl); i++) { + if (strcmp(name, guid_tbl[i].name) == 0) { + *guid = guid_tbl[i].guid; + return (0); + } + } + return (efi_str_to_guid(name, guid)); +} + +int +efi_set_variable(efi_guid_t guid, const char *name, + uint8_t *data, size_t data_size, uint32_t attributes, mode_t mode __unused) +{ + struct efi_var_ioc var; + int rv; + + if (efi_open_dev() == -1) + return -1; + + efi_var_reset(&var); + rv = libefi_utf8_to_ucs2(name, &var.name, &var.namesize); + if (rv != 0) + goto errout; + var.vendor = guid; + var.data = data; + var.datasize = data_size; + var.attrib = attributes; + rv = ioctl(efi_fd, EFIIOC_VAR_SET, &var); +errout: + free(var.name); + + return rv; +} + +int +efi_str_to_guid(const char *s, efi_guid_t *guid) +{ + uint32_t status; + + /* knows efi_guid_t is a typedef of uuid_t */ + uuid_from_string(s, guid, &status); + + return (status == uuid_s_ok ? 0 : -1); +} + +int +efi_variables_supported(void) +{ + + return efi_open_dev() != -1; +} diff --git a/lib/libefivar/efivar.h b/lib/libefivar/efivar.h new file mode 100644 index 0000000000..822cd3c125 --- /dev/null +++ b/lib/libefivar/efivar.h @@ -0,0 +1,121 @@ +/*- + * Copyright (c) 2016 Netflix, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: head/lib/libefivar/efivar.h 307071 2016-10-11 22:30:41Z imp $ + */ + +#ifndef _EFIVAR_H_ +#define _EFIVAR_H_ + +#include +#include +#include +#include + +/* Shoud these be elsewhere ? */ +#define EFI_VARIABLE_NON_VOLATILE 0x00000001 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004 +#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x00000008 +#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x00000010 +#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS \ + 0x00000020 +#define EFI_VARIABLE_APPEND_WRITE 0x00000040 +#if 0 /* todo */ +#define EFI_VARIABLE_HAS_AUTH_HEADER +#define EFI_VARIABLE_HAS_SIGNATURE +#endif + + +typedef uuid_t efi_guid_t; +#if BYTE_ORDER == LITTLE_ENDIAN +#define EFI_GUID(a, b, c, d, e0, e1, e2, e3, e4, e5) \ + ((efi_guid_t) {(a), (b), (c), (d) >> 8, (d) & 0xff, \ + { (e0), (e1), (e2), (e3), (e4), (e5) }}) +#else +#define EFI_GUID(a, b, c, d, e0, e1, e2, e3, e4, e5) \ + ((efi_guid_t) {(a), (b), (c), (d) & 0xff, (d) >> 8, \ + { (e0), (e1), (e2), (e3), (e4), (e5) }}) +#endif + +#define EFI_GLOBAL_GUID EFI_GUID(0x8be4df61, 0x93ca, 0x11d2, 0xaa0d, \ + 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) + +int efi_append_variable(efi_guid_t guid, const char *name, + uint8_t *data, size_t data_size, uint32_t attributes); +int efi_del_variable(efi_guid_t guid, const char *name); +int efi_get_variable(efi_guid_t guid, const char *name, + uint8_t **data, size_t *data_size, uint32_t *attributes); +int efi_get_variable_attributes(efi_guid_t guid, const char *name, + uint32_t *attributes); +int efi_get_variable_size(efi_guid_t guid, const char *name, size_t *size); +int efi_get_next_variable_name(efi_guid_t **guid, char **name); +int efi_guid_cmp(const efi_guid_t *guid1, const efi_guid_t *guid2); +int efi_guid_is_zero(const efi_guid_t *guid1); +int efi_guid_to_name(efi_guid_t *guid, char **name); +int efi_guid_to_symbol(efi_guid_t *guid, char **symbol); +int efi_guid_to_str(const efi_guid_t *guid, char **sp); +int efi_name_to_guid(const char *name, efi_guid_t *guid); +int efi_set_variable(efi_guid_t guid, const char *name, + uint8_t *data, size_t data_size, uint32_t attributes, mode_t mode); +int efi_str_to_guid(const char *s, efi_guid_t *guid); +int efi_variables_supported(void); + +extern const efi_guid_t efi_guid_empty; + +/* Stubs that are expected, but aren't really used */ +static inline int +efi_error_get(unsigned int n __unused, char ** const fn __unused, + char ** const func __unused, int *line __unused, + char ** const msg __unused, int *err __unused) +{ + return 0; +} + +static inline int +efi_error_set(const char *fn __unused, const char *func __unused, + int line __unused, int err __unused, const char *fmt __unused, ...) +{ + return 0; +} + +static inline void +efi_error_clear(void) +{ +} + +static inline int +efi_error(const char *fmt __unused, ...) +{ + return 0; +} + +static inline int +efi_error_val(int val __unused, const char *fmt __unused, ...) +{ + return 0; +} + +#endif /* _EFIVAR_H_ */ diff --git a/lib/libefivar/libefivar.c b/lib/libefivar/libefivar.c new file mode 100644 index 0000000000..e8d167b5f7 --- /dev/null +++ b/lib/libefivar/libefivar.c @@ -0,0 +1,188 @@ +/*- + * Copyright (c) 2010 Marcel Moolenaar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: head/lib/libefivar/libefivar.c 307071 2016-10-11 22:30:41Z imp $"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libefivar_int.h" + +#include + +/* + * If nm were converted to utf8, what what would strlen + * return on the resulting string? + */ +static size_t +utf8_len_of_ucs2(const efi_char *nm) +{ + size_t len; + efi_char c; + + len = 0; + while (*nm) { + c = *nm++; + if (c > 0x7ff) + len += 3; + else if (c > 0x7f) + len += 2; + else + len++; + } + + return (len); +} + +int +libefi_ucs2_to_utf8(const efi_char *nm, char **name) +{ + size_t len, sz; + efi_char c; + char *cp; + int freeit = *name == NULL; + + sz = utf8_len_of_ucs2(nm) + 1; + len = 0; + if (*name != NULL) + cp = *name; + else + cp = *name = malloc(sz); + if (*name == NULL) + return (ENOMEM); + + while (*nm) { + c = *nm++; + if (c > 0x7ff) { + if (len++ < sz) + *cp++ = (char)(0xE0 | (c >> 12)); + if (len++ < sz) + *cp++ = (char)(0x80 | ((c >> 6) & 0x3f)); + if (len++ < sz) + *cp++ = (char)(0x80 | (c & 0x3f)); + } else if (c > 0x7f) { + if (len++ < sz) + *cp++ = (char)(0xC0 | ((c >> 6) & 0x1f)); + if (len++ < sz) + *cp++ = (char)(0x80 | (c & 0x3f)); + } else { + if (len++ < sz) + *cp++ = (char)(c & 0x7f); + } + } + + if (len >= sz) { + /* Absent bugs, we'll never return EOVERFLOW */ + if (freeit) + free(*name); + return (EOVERFLOW); + } + *cp++ = '\0'; + + return (0); +} + +int +libefi_utf8_to_ucs2(const char *name, efi_char **nmp, size_t *len) +{ + efi_char *nm; + size_t sz; + uint32_t ucs4; + int c, bytes; + int freeit = *nmp == NULL; + + sz = strlen(name) * 2 + 2; + if (*nmp == NULL) + *nmp = malloc(sz); + nm = *nmp; + *len = sz; + + ucs4 = 0; + bytes = 0; + while (sz > 1 && *name != '\0') { + c = *name++; + /* + * Conditionalize on the two major character types: + * initial and followup characters. + */ + if ((c & 0xc0) != 0x80) { + /* Initial characters. */ + if (bytes != 0) { + if (freeit) + free(nm); + return (EILSEQ); + } + if ((c & 0xf8) == 0xf0) { + ucs4 = c & 0x07; + bytes = 3; + } else if ((c & 0xf0) == 0xe0) { + ucs4 = c & 0x0f; + bytes = 2; + } else if ((c & 0xe0) == 0xc0) { + ucs4 = c & 0x1f; + bytes = 1; + } else { + ucs4 = c & 0x7f; + bytes = 0; + } + } else { + /* Followup characters. */ + if (bytes > 0) { + ucs4 = (ucs4 << 6) + (c & 0x3f); + bytes--; + } else if (bytes == 0) { + if (freeit) + free(nm); + return (EILSEQ); + } + } + if (bytes == 0) { + if (ucs4 > 0xffff) { + if (freeit) + free(nm); + return (EILSEQ); + } + *nm++ = (efi_char)ucs4; + sz -= 2; + } + } + if (sz < 2) { + if (freeit) + free(nm); + return (EDOOFUS); + } + sz -= 2; + *nm = 0; + *len -= sz; + return (0); +} diff --git a/lib/libefivar/libefivar_int.h b/lib/libefivar/libefivar_int.h new file mode 100644 index 0000000000..2bebd41841 --- /dev/null +++ b/lib/libefivar/libefivar_int.h @@ -0,0 +1,35 @@ +/*- + * Copyright (c) 2010 Marcel Moolenaar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/lib/libefivar/libefivar_int.h 307071 2016-10-11 22:30:41Z imp $ + */ + +#ifndef _LIBEFI_INT_H_ +#define _LIBEFI_INT_H_ + +int libefi_ucs2_to_utf8(const efi_char *, char **); +int libefi_utf8_to_ucs2(const char *, efi_char **, size_t *); + +#endif /* _LIBEFI_INT_H_ */ diff --git a/share/mk/bsd.libnames.mk b/share/mk/bsd.libnames.mk index 8e7267d652..f536c1936d 100644 --- a/share/mk/bsd.libnames.mk +++ b/share/mk/bsd.libnames.mk @@ -30,6 +30,7 @@ LIBDIALOG?= ${DESTDIR}${LIBDIR}/libdialog.a LIBDM?= ${DESTDIR}${LIBDIR}/libdm.a #LIBDMSG?= ${DESTDIR}${LIBDIR}/libdmsg.a LIBEDIT?= ${DESTDIR}${LIBDIR}/priv/libprivate_edit.a +LIBEFIVAR?= ${DESTDIR}${LIBDIR}/libefivar.a LIBEVTR?= ${DESTDIR}${LIBDIR}/libevtr.a LIBEXECINFO?= ${DESTDIR}${LIBDIR}/libexecinfo.a LIBFETCH?= ${DESTDIR}${LIBDIR}/libfetch.a diff --git a/sys/dev/misc/efidev/Makefile b/sys/dev/misc/efidev/Makefile new file mode 100644 index 0000000000..32de94fd9c --- /dev/null +++ b/sys/dev/misc/efidev/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD: head/sys/modules/efirt/Makefile 307070 2016-10-11 22:24:30Z imp $ + +.PATH: ${.CURDIR}/../../../platform/${MACHINE_PLATFORM}/${MACHINE_ARCH} + +KMOD= efirt +SRCS= efirt.c efidev.c +SRCS+= device_if.h bus_if.h + +.include diff --git a/sys/dev/misc/efidev/efidev.c b/sys/dev/misc/efidev/efidev.c new file mode 100644 index 0000000000..62ccba4ffb --- /dev/null +++ b/sys/dev/misc/efidev/efidev.c @@ -0,0 +1,224 @@ +/*- + * Copyright (c) 2016 Netflix, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: head/sys/dev/efidev/efidev.c 307391 2016-10-16 06:07:43Z kib $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static d_ioctl_t efidev_ioctl; +static d_open_t efidev_open; +static d_close_t efidev_close; + +static struct dev_ops efi_ops = { + { "efi", 0, 0 }, + .d_open = efidev_open, + .d_close = efidev_close, + .d_ioctl = efidev_ioctl, +}; + +static int +efidev_open(struct dev_open_args *ap) +{ + return 0; +} + +static int +efidev_close(struct dev_close_args *ap) +{ + return 0; +} + +static int +efidev_ioctl(struct dev_ioctl_args *ap) +{ + u_long cmd = ap->a_cmd; + caddr_t addr = ap->a_data; + int error; + + switch (cmd) { + case EFIIOC_GET_TABLE: + { + struct efi_get_table_ioc *egtioc = + (struct efi_get_table_ioc *)addr; + + error = efi_get_table(&egtioc->uuid, &egtioc->ptr); + break; + } + case EFIIOC_GET_TIME: + { + struct efi_tm *tm = (struct efi_tm *)addr; + + error = efi_get_time(tm); + break; + } + case EFIIOC_SET_TIME: + { + struct efi_tm *tm = (struct efi_tm *)addr; + + error = efi_set_time(tm); + break; + } + case EFIIOC_VAR_GET: + { + struct efi_var_ioc *ev = (struct efi_var_ioc *)addr; + void *data; + efi_char *name; + + data = kmalloc(ev->datasize, M_TEMP, M_WAITOK); + name = kmalloc(ev->namesize, M_TEMP, M_WAITOK); + error = copyin(ev->name, name, ev->namesize); + if (error) + goto vg_out; + if (name[ev->namesize / sizeof(efi_char) - 1] != 0) { + error = EINVAL; + goto vg_out; + } + + error = efi_var_get(name, &ev->vendor, &ev->attrib, + &ev->datasize, data); + + if (error == 0) { + error = copyout(data, ev->data, ev->datasize); + } else if (error == EOVERFLOW) { + /* + * Pass back the size we really need, but + * convert the error to 0 so the copyout + * happens. datasize was updated in the + * efi_var_get call. + */ + ev->data = NULL; + error = 0; + } +vg_out: + kfree(data, M_TEMP); + kfree(name, M_TEMP); + break; + } + case EFIIOC_VAR_NEXT: + { + struct efi_var_ioc *ev = (struct efi_var_ioc *)addr; + efi_char *name; + + name = kmalloc(ev->namesize, M_TEMP, M_WAITOK); + error = copyin(ev->name, name, ev->namesize); + if (error) + goto vn_out; + /* Note: namesize is the buffer size, not the string lenght */ + + error = efi_var_nextname(&ev->namesize, name, &ev->vendor); + if (error == 0) { + error = copyout(name, ev->name, ev->namesize); + } else if (error == EOVERFLOW) { + ev->name = NULL; + error = 0; + } + vn_out: + kfree(name, M_TEMP); + break; + } + case EFIIOC_VAR_SET: + { + struct efi_var_ioc *ev = (struct efi_var_ioc *)addr; + void *data = NULL; + efi_char *name; + + /* datasize == 0 -> delete (more or less) */ + if (ev->datasize > 0) + data = kmalloc(ev->datasize, M_TEMP, M_WAITOK); + name = kmalloc(ev->namesize, M_TEMP, M_WAITOK); + if (ev->datasize) { + error = copyin(ev->data, data, ev->datasize); + if (error) + goto vs_out; + } + error = copyin(ev->name, name, ev->namesize); + if (error) + goto vs_out; + if (name[ev->namesize / sizeof(efi_char) - 1] != 0) { + error = EINVAL; + goto vs_out; + } + + error = efi_var_set(name, &ev->vendor, ev->attrib, ev->datasize, + data); +vs_out: + kfree(data, M_TEMP); + kfree(name, M_TEMP); + break; + } + default: + error = ENOTTY; + break; + } + + return (error); +} + +static struct cdev *efidev; + +static int +efidev_modevents(module_t m, int event, void *arg __unused) +{ + switch (event) { + case MOD_LOAD: + efidev = make_dev(&efi_ops, 0, UID_ROOT, GID_WHEEL, + 0700, "efi"); + return (0); + + case MOD_UNLOAD: + if (efidev != NULL) + destroy_dev(efidev); + efidev = NULL; + return (0); + + case MOD_SHUTDOWN: + return (0); + + default: + return (EOPNOTSUPP); + } +} + +static moduledata_t efidev_moddata = { + .name = "efidev", + .evhand = efidev_modevents, + .priv = NULL, +}; + +DECLARE_MODULE(efidev, efidev_moddata, SI_SUB_PRE_DRIVERS, SI_ORDER_ANY); +MODULE_VERSION(efidev, 1); +MODULE_DEPEND(efidev, efirt, 1, 1, 1); diff --git a/sys/platform/pc64/include/efi.h b/sys/platform/pc64/include/efi.h new file mode 100644 index 0000000000..3c475006aa --- /dev/null +++ b/sys/platform/pc64/include/efi.h @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2016 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Konstantin Belousov + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/sys/amd64/include/efi.h 306209 2016-09-22 19:04:51Z imp $ + */ + +#ifndef __AMD64_INCLUDE_EFI_H_ +#define __AMD64_INCLUDE_EFI_H_ + +/* + * XXX: from gcc 6.2 manual: + * Note, the ms_abi attribute for Microsoft Windows 64-bit targets + * currently requires the -maccumulate-outgoing-args option. + */ +#define EFIABI_ATTR __attribute__((ms_abi)) + +#ifdef _KERNEL +struct uuid; +struct efi_tm; + +int efi_get_table(struct uuid *uuid, void **ptr); +int efi_get_time(struct efi_tm *tm); +int efi_get_time_locked(struct efi_tm *tm); +int efi_reset_system(void); +int efi_set_time(struct efi_tm *tm); +int efi_set_time_locked(struct efi_tm *tm); +int efi_var_get(uint16_t *name, struct uuid *vendor, uint32_t *attrib, + size_t *datasize, void *data); +int efi_var_nextname(size_t *namesize, uint16_t *name, struct uuid *vendor); +int efi_var_set(uint16_t *name, struct uuid *vendor, uint32_t attrib, + size_t datasize, void *data); +#endif + +#endif /* __AMD64_INCLUDE_EFI_H_ */ diff --git a/sys/platform/pc64/include/pmap.h b/sys/platform/pc64/include/pmap.h index 54ca8b38f3..eb87044192 100644 --- a/sys/platform/pc64/include/pmap.h +++ b/sys/platform/pc64/include/pmap.h @@ -377,6 +377,40 @@ pmap_emulate_ad_bits(pmap_t pmap) { return pmap->pm_flags & PMAP_EMULATE_AD_BITS; } +/* Return various clipped indexes for a given VA */ + +/* + * Returns the index of a pte in a page table, representing a terminal + * page. + */ +static __inline vm_pindex_t +pmap_pte_index(vm_offset_t va) +{ + + return ((va >> PAGE_SHIFT) & ((1ul << NPTEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +pmap_pde_index(vm_offset_t va) +{ + + return ((va >> PDRSHIFT) & ((1ul << NPDEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +pmap_pdpe_index(vm_offset_t va) +{ + + return ((va >> PDPSHIFT) & ((1ul << NPDPEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +pmap_pml4e_index(vm_offset_t va) +{ + + return ((va >> PML4SHIFT) & ((1ul << NPML4EPGSHIFT) - 1)); +} + #endif /* _KERNEL */ #endif /* !LOCORE */ diff --git a/sys/platform/pc64/x86_64/efirt.c b/sys/platform/pc64/x86_64/efirt.c new file mode 100644 index 0000000000..bd84a6c20b --- /dev/null +++ b/sys/platform/pc64/x86_64/efirt.c @@ -0,0 +1,637 @@ +/*- + * Copyright (c) 2004 Marcel Moolenaar + * Copyright (c) 2001 Doug Rabson + * Copyright (c) 2016 The FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were developed by Konstantin Belousov + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/sys/amd64/amd64/efirt.c 307391 2016-10-16 06:07:43Z kib $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +static struct efi_systbl *efi_systbl; +static struct efi_cfgtbl *efi_cfgtbl; +static struct efi_rt *efi_runtime; + +static int efi_status2err[25] = { + 0, /* EFI_SUCCESS */ + ENOEXEC, /* EFI_LOAD_ERROR */ + EINVAL, /* EFI_INVALID_PARAMETER */ + ENOSYS, /* EFI_UNSUPPORTED */ + EMSGSIZE, /* EFI_BAD_BUFFER_SIZE */ + EOVERFLOW, /* EFI_BUFFER_TOO_SMALL */ + EBUSY, /* EFI_NOT_READY */ + EIO, /* EFI_DEVICE_ERROR */ + EROFS, /* EFI_WRITE_PROTECTED */ + EAGAIN, /* EFI_OUT_OF_RESOURCES */ + EIO, /* EFI_VOLUME_CORRUPTED */ + ENOSPC, /* EFI_VOLUME_FULL */ + ENXIO, /* EFI_NO_MEDIA */ + ESTALE, /* EFI_MEDIA_CHANGED */ + ENOENT, /* EFI_NOT_FOUND */ + EACCES, /* EFI_ACCESS_DENIED */ + ETIMEDOUT, /* EFI_NO_RESPONSE */ + EADDRNOTAVAIL, /* EFI_NO_MAPPING */ + ETIMEDOUT, /* EFI_TIMEOUT */ + EDOOFUS, /* EFI_NOT_STARTED */ + EALREADY, /* EFI_ALREADY_STARTED */ + ECANCELED, /* EFI_ABORTED */ + EPROTO, /* EFI_ICMP_ERROR */ + EPROTO, /* EFI_TFTP_ERROR */ + EPROTO /* EFI_PROTOCOL_ERROR */ +}; + +MALLOC_DEFINE(M_EFI, "efi", "EFI BIOS"); + +static int +efi_status_to_errno(efi_status status) +{ + u_long code; + + code = status & 0x3ffffffffffffffful; + return (code < nitems(efi_status2err) ? efi_status2err[code] : EDOOFUS); +} + +static struct lock efi_lock; +static struct lock resettodr_lock; +static mcontext_t efi_ctx; +static struct vmspace *efi_savevm; +static struct vmspace *efi_vmspace; +static vm_object_t efi_obj; +static struct efi_md *efi_map; +static int efi_ndesc; +static int efi_descsz; + +static void +efi_destroy_1t1_map(void) +{ + vm_object_t obj; + vm_page_t m; + + if ((obj = efi_obj) != NULL) { + efi_obj = NULL; + vm_object_hold(obj); + vm_object_reference_locked(obj); /* match deallocate */ + } + if (efi_vmspace) { + pmap_remove_pages(vmspace_pmap(efi_vmspace), + VM_MIN_USER_ADDRESS, VM_MAX_USER_ADDRESS); + vm_map_remove(&efi_vmspace->vm_map, + VM_MIN_USER_ADDRESS, + VM_MAX_USER_ADDRESS); + vmspace_rel(efi_vmspace); + efi_vmspace = NULL; + } + if (obj) { + while ((m = RB_ROOT(&obj->rb_memq)) != NULL) { + vm_page_busy_wait(m, FALSE, "efipg"); + vm_page_unwire(m, 1); + m->flags &= ~(PG_MAPPED | PG_WRITEABLE); + cdev_pager_free_page(obj, m); + kfree(m, M_EFI); + } + vm_object_drop(obj); + vm_object_deallocate(obj); + } +} + +static int +efi_pg_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot, + vm_ooffset_t foff, struct ucred *cred, u_short *color) +{ + *color = 0; + return 0; +} + +static void +efi_pg_dtor(void *handle) +{ +} + +static int +efi_pg_fault(vm_object_t obj, vm_ooffset_t offset, int prot, vm_page_t *mres) +{ + vm_page_t m; + + m = *mres; + if ((m->flags & PG_FICTITIOUS) == 0) { + *mres = NULL; + vm_page_remove(m); + vm_page_free(m); + m = NULL; + } + if (m == NULL) { + kprintf("efi_pg_fault: unmapped pg @%016jx\n", offset); + return VM_PAGER_ERROR; + } + + /* + * Shouldn't get hit, we pre-loaded all the pages. + */ + kprintf("efi_pg_fault: ok %p/%p @%016jx m=%016jx,%016jx\n", + obj, efi_obj, offset, m->pindex, m->phys_addr); + + return VM_PAGER_OK; +} + +static struct cdev_pager_ops efi_pager_ops = { + .cdev_pg_fault = efi_pg_fault, + .cdev_pg_ctor = efi_pg_ctor, + .cdev_pg_dtor = efi_pg_dtor +}; + +static bool +efi_create_1t1_map(struct efi_md *map, int ndesc, int descsz) +{ + vm_page_t m; + struct efi_md *p; + int i; + int count; + int result; + + efi_map = map; + efi_ndesc = ndesc; + efi_descsz = descsz; + + efi_vmspace = vmspace_alloc(VM_MIN_USER_ADDRESS, VM_MAX_USER_ADDRESS); + pmap_pinit2(vmspace_pmap(efi_vmspace)); + efi_obj = cdev_pager_allocate(NULL, OBJT_MGTDEVICE, &efi_pager_ops, + VM_MAX_USER_ADDRESS, + VM_PROT_READ | VM_PROT_WRITE, + 0, proc0.p_ucred); + vm_object_hold(efi_obj); + + count = vm_map_entry_reserve(MAP_RESERVE_COUNT); + vm_map_lock(&efi_vmspace->vm_map); + result = vm_map_insert(&efi_vmspace->vm_map, &count, efi_obj, NULL, + 0, 0, VM_MAX_USER_ADDRESS, + VM_MAPTYPE_NORMAL, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, + 0); + vm_map_unlock(&efi_vmspace->vm_map); + if (result != KERN_SUCCESS) + goto fail; + + for (i = 0, p = map; + i < ndesc; i++, p = efi_next_descriptor(p, descsz)) { + vm_offset_t va; + uint64_t idx; + int mode; + + if ((p->md_attr & EFI_MD_ATTR_RT) == 0) + continue; + if (p->md_virt != NULL) { + if (bootverbose) + kprintf("EFI Runtime entry %d is mapped\n", i); + goto fail; + } + if ((p->md_phys & EFI_PAGE_MASK) != 0) { + if (bootverbose) + kprintf("EFI Runtime entry %d is not aligned\n", + i); + goto fail; + } + if (p->md_phys + p->md_pages * EFI_PAGE_SIZE < p->md_phys || + p->md_phys + p->md_pages * EFI_PAGE_SIZE >= + VM_MAX_USER_ADDRESS) { + kprintf("EFI Runtime entry %d is not in mappable for RT:" + "base %#016jx %#jx pages\n", + i, (uintmax_t)p->md_phys, + (uintmax_t)p->md_pages); + goto fail; + } + + if ((p->md_attr & EFI_MD_ATTR_WB) != 0) + mode = VM_MEMATTR_WRITE_BACK; + else if ((p->md_attr & EFI_MD_ATTR_WT) != 0) + mode = VM_MEMATTR_WRITE_THROUGH; + else if ((p->md_attr & EFI_MD_ATTR_WC) != 0) + mode = VM_MEMATTR_WRITE_COMBINING; + else if ((p->md_attr & EFI_MD_ATTR_WP) != 0) + mode = VM_MEMATTR_WRITE_PROTECTED; + else if ((p->md_attr & EFI_MD_ATTR_UC) != 0) + mode = VM_MEMATTR_UNCACHEABLE; + else { + if (bootverbose) + kprintf("EFI Runtime entry %d mapping " + "attributes unsupported\n", i); + mode = VM_MEMATTR_UNCACHEABLE; + } + + if (bootverbose) { + kprintf("efirt: map %016jx-%016jx\n", + p->md_phys, + p->md_phys + IDX_TO_OFF(p->md_pages)); + } + + for (va = p->md_phys, idx = 0; idx < p->md_pages; idx++, + va += PAGE_SIZE) { + m = kmalloc(sizeof(*m), M_EFI, M_WAITOK | M_ZERO); + /*m->flags |= PG_WRITEABLE;*/ + vm_page_initfake(m, va, mode); /* va is phys addr */ + m->valid = VM_PAGE_BITS_ALL; + m->dirty = m->valid; + vm_page_insert(m, efi_obj, OFF_TO_IDX(va)); + vm_page_wakeup(m); + } + } + vm_object_drop(efi_obj); + vm_map_entry_release(count); + + return true; + +fail: + vm_object_drop(efi_obj); + vm_map_entry_release(count); + efi_destroy_1t1_map(); + + return false; +} + +/* + * Create an environment for the EFI runtime code call. The most + * important part is creating the required 1:1 physical->virtual + * mappings for the runtime segments. To do that, we manually create + * page table which unmap userspace but gives correct kernel mapping. + * The 1:1 mappings for runtime segments usually occupy low 4G of the + * physical address map. + * + * The 1:1 mappings were chosen over the SetVirtualAddressMap() EFI RT + * service, because there are some BIOSes which fail to correctly + * relocate itself on the call, requiring both 1:1 and virtual + * mapping. As result, we must provide 1:1 mapping anyway, so no + * reason to bother with the virtual map, and no need to add a + * complexity into loader. + * + * The fpu_kern_enter() call allows firmware to use FPU, as mandated + * by the specification. In particular, CR0.TS bit is cleared. Also + * it enters critical section, giving us neccessary protection against + * context switch. + * + * There is no need to disable interrupts around the change of %cr3, + * the kernel mappings are correct, while we only grabbed the + * userspace portion of VA. Interrupts handlers must not access + * userspace. Having interrupts enabled fixes the issue with + * firmware/SMM long operation, which would negatively affect IPIs, + * esp. TLB shootdown requests. + */ +static int +efi_enter(void) +{ + thread_t td = curthread; + + if (efi_runtime == NULL) + return (ENXIO); + lockmgr(&efi_lock, LK_EXCLUSIVE); + efi_savevm = td->td_lwp->lwp_vmspace; + pmap_setlwpvm(td->td_lwp, efi_vmspace); + npxpush(&efi_ctx); + cpu_invltlb(); + + return (0); +} + +static void +efi_leave(void) +{ + thread_t td = curthread; + + pmap_setlwpvm(td->td_lwp, efi_savevm); + npxpop(&efi_ctx); + cpu_invltlb(); + efi_savevm = NULL; + lockmgr(&efi_lock, LK_RELEASE); +} + +static int +efi_init(void) +{ + struct efi_map_header *efihdr; + struct efi_md *map; + caddr_t kmdp; + size_t efisz; + + lockinit(&efi_lock, "efi", 0, LK_CANRECURSE); + lockinit(&resettodr_lock, "efitodr", 0, LK_CANRECURSE); + + if (efi_systbl_phys == 0) { + if (bootverbose) + kprintf("EFI systbl not available\n"); + return (ENXIO); + } + efi_systbl = (struct efi_systbl *)PHYS_TO_DMAP(efi_systbl_phys); + if (efi_systbl->st_hdr.th_sig != EFI_SYSTBL_SIG) { + efi_systbl = NULL; + if (bootverbose) + kprintf("EFI systbl signature invalid\n"); + return (ENXIO); + } + efi_cfgtbl = (efi_systbl->st_cfgtbl == 0) ? NULL : + (struct efi_cfgtbl *)efi_systbl->st_cfgtbl; + if (efi_cfgtbl == NULL) { + if (bootverbose) + kprintf("EFI config table is not present\n"); + } + + kmdp = preload_search_by_type("elf kernel"); + if (kmdp == NULL) + kmdp = preload_search_by_type("elf64 kernel"); + efihdr = (struct efi_map_header *)preload_search_info(kmdp, + MODINFO_METADATA | MODINFOMD_EFI_MAP); + if (efihdr == NULL) { + if (bootverbose) + kprintf("EFI map is not present\n"); + return (ENXIO); + } + efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf; + map = (struct efi_md *)((uint8_t *)efihdr + efisz); + if (efihdr->descriptor_size == 0) + return (ENOMEM); + + if (!efi_create_1t1_map(map, efihdr->memory_size / + efihdr->descriptor_size, efihdr->descriptor_size)) { + if (bootverbose) + kprintf("EFI cannot create runtime map\n"); + return (ENOMEM); + } + + efi_runtime = (efi_systbl->st_rt == 0) ? NULL : + (struct efi_rt *)efi_systbl->st_rt; + if (efi_runtime == NULL) { + if (bootverbose) + kprintf("EFI runtime services table is not present\n"); + efi_destroy_1t1_map(); + return (ENXIO); + } + + return (0); +} + +static void +efi_uninit(void) +{ + efi_destroy_1t1_map(); + + efi_systbl = NULL; + efi_cfgtbl = NULL; + efi_runtime = NULL; + + lockuninit(&efi_lock); + lockuninit(&resettodr_lock); +} + +int +efi_get_table(struct uuid *uuid, void **ptr) +{ + struct efi_cfgtbl *ct; + u_long count; + + if (efi_cfgtbl == NULL) + return (ENXIO); + count = efi_systbl->st_entries; + ct = efi_cfgtbl; + while (count--) { + if (!bcmp(&ct->ct_uuid, uuid, sizeof(*uuid))) { + *ptr = (void *)PHYS_TO_DMAP(ct->ct_data); + return (0); + } + ct++; + } + return (ENOENT); +} + +char SaveCode[1024]; + +int +efi_get_time_locked(struct efi_tm *tm) +{ + efi_status status; + int error; + + KKASSERT(lockowned(&resettodr_lock) != 0); + error = efi_enter(); + if (error != 0) + return (error); + status = efi_runtime->rt_gettime(tm, NULL); + efi_leave(); + error = efi_status_to_errno(status); + + return (error); +} + +int +efi_get_time(struct efi_tm *tm) +{ + int error; + + if (efi_runtime == NULL) + return (ENXIO); + lockmgr(&resettodr_lock, LK_EXCLUSIVE); + error = efi_get_time_locked(tm); + lockmgr(&resettodr_lock, LK_RELEASE); + + return (error); +} + +int +efi_reset_system(void) +{ + int error; + + error = efi_enter(); + if (error != 0) + return (error); + efi_runtime->rt_reset(EFI_RESET_WARM, 0, 0, NULL); + efi_leave(); + return (EIO); +} + +int +efi_set_time_locked(struct efi_tm *tm) +{ + efi_status status; + int error; + + KKASSERT(lockowned(&resettodr_lock) != 0); + error = efi_enter(); + if (error != 0) + return (error); + status = efi_runtime->rt_settime(tm); + efi_leave(); + error = efi_status_to_errno(status); + return (error); +} + +int +efi_set_time(struct efi_tm *tm) +{ + int error; + + if (efi_runtime == NULL) + return (ENXIO); + lockmgr(&resettodr_lock, LK_EXCLUSIVE); + error = efi_set_time_locked(tm); + lockmgr(&resettodr_lock, LK_RELEASE); + return (error); +} + +int +efi_var_get(efi_char *name, struct uuid *vendor, uint32_t *attrib, + size_t *datasize, void *data) +{ + efi_status status; + int error; + + error = efi_enter(); + if (error != 0) + return (error); + status = efi_runtime->rt_getvar(name, vendor, attrib, datasize, data); + efi_leave(); + error = efi_status_to_errno(status); + return (error); +} + +int +efi_var_nextname(size_t *namesize, efi_char *name, struct uuid *vendor) +{ + efi_status status; + int error; + + error = efi_enter(); + if (error != 0) + return (error); + status = efi_runtime->rt_scanvar(namesize, name, vendor); + efi_leave(); + error = efi_status_to_errno(status); + return (error); +} + +int +efi_var_set(efi_char *name, struct uuid *vendor, uint32_t attrib, + size_t datasize, void *data) +{ + efi_status status; + int error; + + error = efi_enter(); + if (error != 0) + return (error); + status = efi_runtime->rt_setvar(name, vendor, attrib, datasize, data); + efi_leave(); + error = efi_status_to_errno(status); + return (error); +} + +static int +efirt_modevents(module_t m, int event, void *arg __unused) +{ + + switch (event) { + case MOD_LOAD: + return (efi_init()); + + case MOD_UNLOAD: + efi_uninit(); + return (0); + + case MOD_SHUTDOWN: + return (0); + + default: + return (EOPNOTSUPP); + } +} + +static moduledata_t efirt_moddata = { + .name = "efirt", + .evhand = efirt_modevents, + .priv = NULL, +}; + +DECLARE_MODULE(efirt, efirt_moddata, SI_SUB_DRIVERS, SI_ORDER_ANY); +MODULE_VERSION(efirt, 1); + + +/* XXX debug stuff */ +static int +efi_time_sysctl_handler(SYSCTL_HANDLER_ARGS) +{ + union { + struct efi_tm tm; + char buf[256]; + } u; + int error, val; +#define tm u.tm + + val = 0; + error = sysctl_handle_int(oidp, &val, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + error = efi_get_time(&tm); + if (error == 0) { + uprintf("EFI reports: Year %d Month %d Day %d Hour %d Min %d " + "Sec %d\n", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); + } + return (error); +} + +#undef tm + +SYSCTL_PROC(_debug, OID_AUTO, efi_time, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, + efi_time_sysctl_handler, "I", ""); diff --git a/sys/platform/pc64/x86_64/pmap.c b/sys/platform/pc64/x86_64/pmap.c index 7764abc345..67c542947d 100644 --- a/sys/platform/pc64/x86_64/pmap.c +++ b/sys/platform/pc64/x86_64/pmap.c @@ -437,17 +437,6 @@ pmap_pml4_pindex(void) /* * Return various clipped indexes for a given VA * - * Returns the index of a pte in a page table, representing a terminal - * page. - */ -static __inline -vm_pindex_t -pmap_pte_index(vm_offset_t va) -{ - return ((va >> PAGE_SHIFT) & ((1ul << NPTEPGSHIFT) - 1)); -} - -/* * Returns the index of a pt in a page directory, representing a page * table. */ diff --git a/sys/sys/efiio.h b/sys/sys/efiio.h new file mode 100644 index 0000000000..e4db866d97 --- /dev/null +++ b/sys/sys/efiio.h @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2016 Netflix, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: head/sys/sys/efiio.h 307070 2016-10-11 22:24:30Z imp $ + */ + +#ifndef _SYS_EFIIO_H_ +#define _SYS_EFIIO_H_ + +#include +#include +#include + +struct efi_get_table_ioc +{ + struct uuid uuid; /* UUID to look up */ + void *ptr; /* Pointer to table in KVA space */ +}; + +struct efi_var_ioc +{ + efi_char *name; /* User pointer to name, in wide chars */ + size_t namesize; /* Number of wide characters in name */ + struct uuid vendor; /* Vendor's UUID for variable */ + uint32_t attrib; /* Attributes */ + void *data; /* User pointer to the data */ + 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) +#define EFIIOC_VAR_NEXT _IOWR('E', 5, struct efi_var_ioc) +#define EFIIOC_VAR_SET _IOWR('E', 6, struct efi_var_ioc) + +#endif /* _SYS_EFIIO_H_ */ diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 6dc01b9785..8169e1eb0b 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -32,6 +32,7 @@ SUBDIR= 802_11 \ dev_mkdb \ dntpd \ edquota \ + efivar \ faithd \ fdcontrol \ fdformat \ @@ -124,6 +125,7 @@ SUBDIR= 802_11 \ traceroute6 \ trpt \ tzsetup \ + uefisign \ usbconfig \ usbdump \ vidcontrol \ diff --git a/usr.sbin/efivar/Makefile b/usr.sbin/efivar/Makefile new file mode 100644 index 0000000000..1c6fe20524 --- /dev/null +++ b/usr.sbin/efivar/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD: head/usr.sbin/efivar/Makefile 307072 2016-10-11 22:31:45Z imp $ + +PROG= efivar +MAN= efivar.8 + +LDADD= -lefivar +DPADD= ${LIBEFIVAR} + +.include diff --git a/usr.sbin/efivar/efivar.8 b/usr.sbin/efivar/efivar.8 new file mode 100644 index 0000000000..341ef33828 --- /dev/null +++ b/usr.sbin/efivar/efivar.8 @@ -0,0 +1,164 @@ +.\" Copyright (c) 2003 Netflix, Inc +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/usr.sbin/efivar/efivar.8 307224 2016-10-13 17:03:54Z imp $ +.\" +.Dd September 29, 2016 +.Dt EFIVAR 8 +.Os +.Sh NAME +.Nm efivar +.Nd UEFI environemnt variable interaction +.Sh SYNOPSIS +.Nm +.Op Fl abdDHlLNpRtw +.Op Fl n Ar name +.Op Fl f Ar file +.Op Fl -append +.Op Fl -ascii +.Op Fl -attributes +.Op Fl -binary +.Op Fl -delete +.Op Fl -fromfile Ar file +.Op Fl -hex +.Op Fl -list-guids +.Op Fl -list +.Op Fl -name Ar name +.Op Fl -no-name +.Op Fl -print +.Op Fl -print-decimal +.Op Fl -raw-guid +.Op Fl -write +.Ar name Ns Op = Ns Ar value +.Sh DESCRIPTION +This program manages +.Dq Unified Extensible Firmware Interface +.Pq UEFI +environment variables. +UEFI variables have three part: A namespace, a name and a value. +The namespace is a GUID that's self assigned by the group defining the +variables. +The name is a Unicode name for the variable. +The value is binary data. +All Unicode data is presented to the user as UTF-8. +.Pp +The following options are available: +.Bl -tag -width 20m +.It Fl n Ar name Fl -name Ar name +Specify the name of the variable to operate on. +The +.Ar name +argument is the GUID of variable, followed by a dash, followed by the +UEFI variable name. +The GUID may be in numeric format, or may be one of the well known +symbolic name (see +.Fl -list-guids +for a complete list). +.It Fl f Ar file Fl -fromfile Ar file +When writing or appending to a variable, take the data for the +variable's value from +.Ar file +instead of from the command line. +This flag implies +.Fl -write +unless the +.Fl -append +flag is given. +This is not well understood and currently unimplemented. +.It Fl a Fl -append +Append the specified value to the UEFI variable rather than replacing +it.p +.It Fl t Ar attr Fl -attributes Ar attr +Specify, in user hostile hexidecimal, the attributes for this +variable. +See section 7.2 (GetVariable subsection, Related Definitions) of the +UEFI Specification for hex values to use. +.It Fl A Fl -ascii +Display the variable data as modified ascii: All printable characters +are printed, while unprintable characters are rendered as a two-digit +hexadecimal number preceeded by a % character. +.It Fl b Fl -binary +Display the variable data as binary data. +Usually will be used with the +.Fl N +or +.Fl -no-name +flag. +Useful in scripts. +.It Fl D Fl -delete +Delete the specified variable. +May not be used with either the +.Fl -write +or the +.Fl -append +flags. +No +.Ar value +may be specified. +.It Fl H Fl -hex +List variable data as a hex dump. +.It Fl L Fl -list-guids +Lists the well known GUIDs. +The names listed here may be used in place of the numeric GUID values. +These names will replace the numeric GUID values unless +.Fl -raw-guid +flag is specified. +.It Fl l Fl -list +List all the variables. +If the +.Fl -print +flag is also listed, their values will be displayed. +.It Fl N Fl -no-name +Do not display the variable name. +.It Fl p Fl -print +Print the value of the variable. +.It Fl d Fl -print-decimal +Treat the value of the variable as a number and print it as a +decimal. +This is currently unimplemented. +.It Fl R Fl -raw-guid +Do not substitute well known names for GUID numeric values in output. +.It Fl w Fl -write +Write (replace) the variable specified with the value specified. +.It Ar name +Display the +.Ar name +environment variable. +.It Ar name Ns = Ns Ar value +Set the specified +.Ar name +to +.Ar value . +This is not yet implemented. +.Sh COMPATIBILITY +The +.Nm +program is intended to be compatible (strict superset) with a progam +of the same name included in the Red Hat libefivar package. +.Sh SEE ALSO +Appendix A of the UEFI specification has the format for GUIDs. +All GUIDs +.Dq Globally Unique Identifiers +have the format described in RFC 4122. +.El diff --git a/usr.sbin/efivar/efivar.c b/usr.sbin/efivar/efivar.c new file mode 100644 index 0000000000..5c62564087 --- /dev/null +++ b/usr.sbin/efivar/efivar.c @@ -0,0 +1,351 @@ +/*- + * Copyright (c) 2016 Netflix, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: head/usr.sbin/efivar/efivar.c 307390 2016-10-16 05:53:18Z imp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* options descriptor */ +static struct option longopts[] = { + { "append", no_argument, NULL, 'a' }, + { "ascii", no_argument, NULL, 'A' }, + { "attributes", required_argument, NULL, 't' }, + { "binary", no_argument, NULL, 'b' }, + { "delete", no_argument, NULL, 'D' }, + { "fromfile", required_argument, NULL, 'f' }, + { "hex", no_argument, NULL, 'H' }, + { "list-guids", no_argument, NULL, 'L' }, + { "list", no_argument, NULL, 'l' }, + { "name", required_argument, NULL, 'n' }, + { "no-name", no_argument, NULL, 'N' }, + { "print", no_argument, NULL, 'p' }, + { "print-decimal", no_argument, NULL, 'd' }, + { "raw-guid", no_argument, NULL, 'R' }, + { "write", no_argument, NULL, 'w' }, + { NULL, 0, NULL, 0 } +}; + + +static int aflag, Aflag, bflag, dflag, Dflag, Hflag, Nflag, + lflag, Lflag, Rflag, wflag, pflag; +static char *varname; +static u_long attrib = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; + +static void +usage(void) +{ + + errx(1, "efivar [-abdDHlLNpRtw] [-n name] [-f file] [--append] [--ascii]\n" + "\t[--attributes] [--binary] [--delete] [--fromfile file] [--hex]\n" + "\t[--list-guids] [--list] [--name name] [--no-name] [--print]\n" + "\t[--print-decimal] [--raw-guid] [--write] name[=value]"); +} + +static void +breakdown_name(char *name, efi_guid_t *guid, char **vname) +{ + char *cp; + + cp = strrchr(name, '-'); + if (cp == NULL) + errx(1, "Invalid name: %s", name); + *vname = cp + 1; + *cp = '\0'; + if (efi_str_to_guid(name, guid) < 0) + errx(1, "Invalid guid %s", name); +} + +static uint8_t * +get_value(char *val, size_t *datalen) +{ + static char buffer[16*1024]; + + if (val != NULL) { + *datalen = strlen(val); + return ((uint8_t *)val); + } + /* Read from stdin */ + *datalen = sizeof(buffer); + *datalen = read(0, buffer, *datalen); + return ((uint8_t *)buffer); +} + +static void +append_variable(char *name, char *val) +{ + char *vname; + efi_guid_t guid; + size_t datalen; + uint8_t *data; + + breakdown_name(name, &guid, &vname); + data = get_value(val, &datalen); + if (efi_append_variable(guid, vname, data, datalen, attrib) < 0) + err(1, "efi_append_variable"); +} + +static void +delete_variable(char *name) +{ + char *vname; + efi_guid_t guid; + + breakdown_name(name, &guid, &vname); + if (efi_del_variable(guid, vname) < 0) + err(1, "efi_del_variable"); +} + +static void +write_variable(char *name, char *val) +{ + char *vname; + efi_guid_t guid; + size_t datalen; + uint8_t *data; + + breakdown_name(name, &guid, &vname); + data = get_value(val, &datalen); + if (efi_set_variable(guid, vname, data, datalen, attrib, 0) < 0) + err(1, "efi_set_variable"); +} + +static void +asciidump(uint8_t *data, size_t datalen) +{ + size_t i; + int len; + + len = 0; + if (!Nflag) + printf("\n"); + for (i = 0; i < datalen; i++) { + if (isprint(data[i])) { + len++; + if (len > 80) { + len = 0; + printf("\n"); + } + printf("%c", data[i]); + } else { + len +=3; + if (len > 80) { + len = 0; + printf("\n"); + } + printf("%%%02x", data[i]); + } + } + printf("\n"); +} + +static void +hexdump(uint8_t *data, size_t datalen) +{ + size_t i; + + if (!Nflag) + printf("\n"); + for (i = 0; i < datalen; i++) { + if (i % 16 == 0) { + if (i != 0) + printf("\n"); + printf("%04x: ", (int)i); + } + printf("%02x ", data[i]); + } + printf("\n"); +} + +static void +bindump(uint8_t *data, size_t datalen) +{ + write(1, data, datalen); +} + +static void +print_var(efi_guid_t *guid, char *name) +{ + uint32_t att; + uint8_t *data; + size_t datalen; + char *gname; + int rv; + + efi_guid_to_str(guid, &gname); + if (!Nflag) + printf("%s-%s", gname, name); + if (pflag) { + rv = efi_get_variable(*guid, name, &data, &datalen, &att); + + if (rv < 0) + printf("\n --- Error getting value --- %d", errno); + else { + if (Aflag) + asciidump(data, datalen); + else if (bflag) + bindump(data, datalen); + else + hexdump(data, datalen); + } + } + free(gname); + if (!Nflag) + printf("\n"); +} + +static void +print_variable(char *name) +{ + char *vname; + efi_guid_t guid; + + breakdown_name(name, &guid, &vname); + print_var(&guid, vname); +} + +static void +print_variables(void) +{ + int rv; + char *name = NULL; + efi_guid_t *guid = NULL; + + while ((rv = efi_get_next_variable_name(&guid, &name)) > 0) + print_var(guid, name); + + if (rv < 0) + err(1, "Error listing names"); +} + +static void +parse_args(int argc, char **argv) +{ + int ch, i; + + while ((ch = getopt_long(argc, argv, "aAbdDf:HlLNn:pRt:w", + longopts, NULL)) != -1) { + switch (ch) { + case 'a': + aflag++; + break; + case 'A': + Aflag++; + break; + case 'b': + bflag++; + break; + case 'd': + dflag++; + break; + case 'D': + Dflag++; + break; + case 'H': + Hflag++; + break; + case 'l': + lflag++; + break; + case 'L': + Lflag++; + break; + case 'n': + varname = optarg; + break; + case 'N': + Nflag++; + break; + case 'p': + pflag++; + break; + case 'R': + Rflag++; + break; + case 't': + attrib = strtoul(optarg, NULL, 16); + break; + case 'w': + wflag++; + break; + case 'f': + case 0: + errx(1, "unknown or unimplemented option\n"); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 1) + varname = argv[0]; + + if (aflag + Dflag + wflag > 1) { + warnx("Can only use one of -a (--append), " + "-D (--delete) and -w (--write)"); + usage(); + } + + if (aflag + Dflag + wflag > 0 && varname == NULL) { + warnx("Must specify a variable for -a (--append), " + "-D (--delete) or -w (--write)"); + usage(); + } + + if (aflag) + append_variable(varname, NULL); + else if (Dflag) + delete_variable(varname); + else if (wflag) + write_variable(varname, NULL); + else if (varname) { + pflag++; + print_variable(varname); + } else if (argc > 0) { + pflag++; + for (i = 0; i < argc; i++) + print_variable(argv[i]); + } else + print_variables(); +} + +int +main(int argc, char **argv) +{ + + parse_args(argc, argv); +} diff --git a/usr.sbin/uefisign/Makefile b/usr.sbin/uefisign/Makefile new file mode 100644 index 0000000000..f36eb7f962 --- /dev/null +++ b/usr.sbin/uefisign/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD: head/usr.sbin/uefisign/Makefile 279323 2015-02-26 15:48:20Z trasz $ + +PROG= uefisign +SRCS= uefisign.c child.c pe.c +MAN= uefisign.8 + +LDADD= -lprivate_crypto +DPADD= ${LIBRECRYPTO} +LDFLAGS+= -rpath /lib/priv -L ${_SHLIBDIRPREFIX}/usr/lib/priv + +.include diff --git a/usr.sbin/uefisign/child.c b/usr.sbin/uefisign/child.c new file mode 100644 index 0000000000..ad6c6ce387 --- /dev/null +++ b/usr.sbin/uefisign/child.c @@ -0,0 +1,267 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD: head/usr.sbin/uefisign/child.c 305980 2016-09-19 16:07:32Z emaste $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "uefisign.h" + +static void +load(struct executable *x) +{ + int error, fd; + struct stat sb; + char *buf; + size_t nread, len; + + fd = fileno(x->x_fp); + + error = fstat(fd, &sb); + if (error != 0) + err(1, "%s: fstat", x->x_path); + + len = sb.st_size; + if (len <= 0) + errx(1, "%s: file is empty", x->x_path); + + buf = malloc(len); + if (buf == NULL) + err(1, "%s: cannot malloc %zd bytes", x->x_path, len); + + nread = fread(buf, len, 1, x->x_fp); + if (nread != 1) + err(1, "%s: fread", x->x_path); + + x->x_buf = buf; + x->x_len = len; +} + +static void +digest_range(struct executable *x, EVP_MD_CTX *mdctx, off_t off, size_t len) +{ + int ok; + + range_check(x, off, len, "chunk"); + + ok = EVP_DigestUpdate(mdctx, x->x_buf + off, len); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_DigestUpdate(3) failed"); + } +} + +static void +digest(struct executable *x) +{ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + size_t sum_of_bytes_hashed; + int i, ok; + + /* + * Windows Authenticode Portable Executable Signature Format + * spec version 1.0 specifies MD5 and SHA1. However, pesign + * and sbsign both use SHA256, so do the same. + */ + md = EVP_get_digestbyname(DIGEST); + if (md == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST); + } + + mdctx = EVP_MD_CTX_create(); + if (mdctx == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_MD_CTX_create(3) failed"); + } + + ok = EVP_DigestInit_ex(mdctx, md, NULL); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_DigestInit_ex(3) failed"); + } + + /* + * According to the Authenticode spec, we need to compute + * the digest in a rather... specific manner; see "Calculating + * the PE Image Hash" part of the spec for details. + * + * First, everything from 0 to before the PE checksum. + */ + digest_range(x, mdctx, 0, x->x_checksum_off); + + /* + * Second, from after the PE checksum to before the Certificate + * entry in Data Directory. + */ + digest_range(x, mdctx, x->x_checksum_off + x->x_checksum_len, + x->x_certificate_entry_off - + (x->x_checksum_off + x->x_checksum_len)); + + /* + * Then, from after the Certificate entry to the end of headers. + */ + digest_range(x, mdctx, + x->x_certificate_entry_off + x->x_certificate_entry_len, + x->x_headers_len - + (x->x_certificate_entry_off + x->x_certificate_entry_len)); + + /* + * Then, each section in turn, as specified in the PE Section Table. + * + * XXX: Sorting. + */ + sum_of_bytes_hashed = x->x_headers_len; + for (i = 0; i < x->x_nsections; i++) { + digest_range(x, mdctx, + x->x_section_off[i], x->x_section_len[i]); + sum_of_bytes_hashed += x->x_section_len[i]; + } + + /* + * I believe this can happen with overlapping sections. + */ + if (sum_of_bytes_hashed > x->x_len) + errx(1, "number of bytes hashed is larger than file size"); + + /* + * I can't really explain this one; just do what the spec says. + */ + if (sum_of_bytes_hashed < x->x_len) { + digest_range(x, mdctx, sum_of_bytes_hashed, + x->x_len - (signature_size(x) + sum_of_bytes_hashed)); + } + + ok = EVP_DigestFinal_ex(mdctx, x->x_digest, &x->x_digest_len); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_DigestFinal_ex(3) failed"); + } + + EVP_MD_CTX_destroy(mdctx); +} + +static void +show_digest(const struct executable *x) +{ + int i; + + printf("computed %s digest ", DIGEST); + for (i = 0; i < (int)x->x_digest_len; i++) + printf("%02x", (unsigned char)x->x_digest[i]); + printf("; digest len %u\n", x->x_digest_len); +} + +static void +send_digest(const struct executable *x, int pipefd) +{ + + send_chunk(x->x_digest, x->x_digest_len, pipefd); +} + +static void +receive_signature(struct executable *x, int pipefd) +{ + + receive_chunk(&x->x_signature, &x->x_signature_len, pipefd); +} + +static void +save(struct executable *x, FILE *fp, const char *path) +{ + size_t nwritten; + + assert(fp != NULL); + assert(path != NULL); + + nwritten = fwrite(x->x_buf, x->x_len, 1, fp); + if (nwritten != 1) + err(1, "%s: fwrite", path); +} + +int +child(const char *inpath, const char *outpath, int pipefd, + bool Vflag, bool vflag) +{ + FILE *outfp = NULL, *infp = NULL; + struct executable *x; + + infp = checked_fopen(inpath, "r"); + if (outpath != NULL) + outfp = checked_fopen(outpath, "w"); + + x = calloc(1, sizeof(*x)); + if (x == NULL) + err(1, "calloc"); + x->x_path = inpath; + x->x_fp = infp; + + load(x); + parse(x); + if (Vflag) { + if (signature_size(x) == 0) + errx(1, "file not signed"); + + printf("file contains signature\n"); + if (vflag) { + digest(x); + show_digest(x); + show_certificate(x); + } + } else { + if (signature_size(x) != 0) + errx(1, "file already signed"); + + digest(x); + if (vflag) + show_digest(x); + send_digest(x, pipefd); + receive_signature(x, pipefd); + update(x); + save(x, outfp, outpath); + } + + return (0); +} diff --git a/usr.sbin/uefisign/magic.h b/usr.sbin/uefisign/magic.h new file mode 100644 index 0000000000..c4c4e4d9b3 --- /dev/null +++ b/usr.sbin/uefisign/magic.h @@ -0,0 +1,66 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/usr.sbin/uefisign/magic.h 289677 2015-10-21 05:37:09Z eadler $ + * + */ + +/* + * This file contains Authenticode-specific ASN.1 "configuration", used, + * after being processed by asprintf(3), as an input to ASN1_generate_nconf(3). + */ +static const char *magic_fmt = +"asn1 = SEQUENCE:SpcIndirectDataContent\n" +"\n" +"[SpcIndirectDataContent]\n" +"a = SEQUENCE:SpcAttributeTypeAndOptionalValue\n" +"b = SEQUENCE:DigestInfo\n" +"\n" +"[SpcAttributeTypeAndOptionalValue]\n" +"# SPC_PE_IMAGE_DATAOBJ\n" +"a = OID:1.3.6.1.4.1.311.2.1.15\n" +"b = SEQUENCE:SpcPeImageData\n" +"\n" +"[SpcPeImageData]\n" +"a = FORMAT:HEX,BITSTRING:00\n" +/* + * Well, there should be some other struct here, "SPCLink", but it doesn't + * appear to be necessary for UEFI, and I have no idea how to synthesize it, + * as it uses the CHOICE type. + */ +"\n" +"[DigestInfo]\n" +"a = SEQUENCE:AlgorithmIdentifier\n" +/* + * Here goes the digest computed from PE headers and sections. + */ +"b = FORMAT:HEX,OCTETSTRING:%s\n" +"\n" +"[AlgorithmIdentifier]\n" +"a = OBJECT:sha256\n" +"b = NULL\n"; diff --git a/usr.sbin/uefisign/pe.c b/usr.sbin/uefisign/pe.c new file mode 100644 index 0000000000..7a1606b510 --- /dev/null +++ b/usr.sbin/uefisign/pe.c @@ -0,0 +1,564 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * PE format reference: + * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + */ + +#include +__FBSDID("$FreeBSD: head/usr.sbin/uefisign/pe.c 289677 2015-10-21 05:37:09Z eadler $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uefisign.h" + +#ifndef CTASSERT +#define CTASSERT(x) _CTASSERT(x, __LINE__) +#define _CTASSERT(x, y) __CTASSERT(x, y) +#define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1] +#endif + +struct mz_header { + uint8_t mz_signature[2]; + uint8_t mz_dont_care[58]; + uint16_t mz_lfanew; +} __attribute__((packed)); + +struct coff_header { + uint8_t coff_dont_care[2]; + uint16_t coff_number_of_sections; + uint8_t coff_dont_care_either[16]; +} __attribute__((packed)); + +#define PE_SIGNATURE 0x00004550 + +struct pe_header { + uint32_t pe_signature; + struct coff_header pe_coff; +} __attribute__((packed)); + +#define PE_OPTIONAL_MAGIC_32 0x010B +#define PE_OPTIONAL_MAGIC_32_PLUS 0x020B + +#define PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION 10 +#define PE_OPTIONAL_SUBSYSTEM_EFI_BOOT 11 +#define PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME 12 + +struct pe_optional_header_32 { + uint16_t po_magic; + uint8_t po_dont_care[58]; + uint32_t po_size_of_headers; + uint32_t po_checksum; + uint16_t po_subsystem; + uint8_t po_dont_care_either[22]; + uint32_t po_number_of_rva_and_sizes; +} __attribute__((packed)); + +CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60); +CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64); +CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68); +CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92); + +struct pe_optional_header_32_plus { + uint16_t po_magic; + uint8_t po_dont_care[58]; + uint32_t po_size_of_headers; + uint32_t po_checksum; + uint16_t po_subsystem; + uint8_t po_dont_care_either[38]; + uint32_t po_number_of_rva_and_sizes; +} __attribute__((packed)); + +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60); +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64); +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68); +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108); + +#define PE_DIRECTORY_ENTRY_CERTIFICATE 4 + +struct pe_directory_entry { + uint32_t pde_rva; + uint32_t pde_size; +} __attribute__((packed)); + +struct pe_section_header { + uint8_t psh_dont_care[16]; + uint32_t psh_size_of_raw_data; + uint32_t psh_pointer_to_raw_data; + uint8_t psh_dont_care_either[16]; +} __attribute__((packed)); + +CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16); +CTASSERT(offsetof(struct pe_section_header, psh_pointer_to_raw_data) == 20); + +#define PE_CERTIFICATE_REVISION 0x0200 +#define PE_CERTIFICATE_TYPE 0x0002 + +struct pe_certificate { + uint32_t pc_len; + uint16_t pc_revision; + uint16_t pc_type; + char pc_signature[0]; +} __attribute__((packed)); + +void +range_check(const struct executable *x, off_t off, size_t len, + const char *name) +{ + + if (off < 0) { + errx(1, "%s starts at negative offset %jd", + name, (intmax_t)off); + } + if (off >= (off_t)x->x_len) { + errx(1, "%s starts at %jd, past the end of executable at %zd", + name, (intmax_t)off, x->x_len); + } + if (len >= x->x_len) { + errx(1, "%s size %zd is larger than the executable size %zd", + name, len, x->x_len); + } + if (off + len > x->x_len) { + errx(1, "%s extends to %jd, past the end of executable at %zd", + name, (intmax_t)(off + len), x->x_len); + } +} + +size_t +signature_size(const struct executable *x) +{ + const struct pe_directory_entry *pde; + + range_check(x, x->x_certificate_entry_off, + x->x_certificate_entry_len, "Certificate Directory"); + + pde = (struct pe_directory_entry *) + (x->x_buf + x->x_certificate_entry_off); + + if (pde->pde_rva != 0 && pde->pde_size == 0) + warnx("signature size is 0, but its RVA is %d", pde->pde_rva); + if (pde->pde_rva == 0 && pde->pde_size != 0) + warnx("signature RVA is 0, but its size is %d", pde->pde_size); + + return (pde->pde_size); +} + +void +show_certificate(const struct executable *x) +{ + struct pe_certificate *pc; + const struct pe_directory_entry *pde; + + range_check(x, x->x_certificate_entry_off, + x->x_certificate_entry_len, "Certificate Directory"); + + pde = (struct pe_directory_entry *) + (x->x_buf + x->x_certificate_entry_off); + + if (signature_size(x) == 0) { + printf("file not signed\n"); + return; + } + +#if 0 + printf("certificate chunk at offset %zd, size %zd\n", + pde->pde_rva, pde->pde_size); +#endif + + range_check(x, pde->pde_rva, pde->pde_size, "Certificate chunk"); + + pc = (struct pe_certificate *)(x->x_buf + pde->pde_rva); + if (pc->pc_revision != PE_CERTIFICATE_REVISION) { + errx(1, "wrong certificate chunk revision, is %d, should be %d", + pc->pc_revision, PE_CERTIFICATE_REVISION); + } + if (pc->pc_type != PE_CERTIFICATE_TYPE) { + errx(1, "wrong certificate chunk type, is %d, should be %d", + pc->pc_type, PE_CERTIFICATE_TYPE); + } + printf("to dump PKCS7:\n " + "dd if='%s' bs=1 skip=%zd | openssl pkcs7 -inform DER -print\n", + x->x_path, pde->pde_rva + offsetof(struct pe_certificate, pc_signature)); + printf("to dump raw ASN.1:\n " + "openssl asn1parse -i -inform DER -offset %zd -in '%s'\n", + pde->pde_rva + offsetof(struct pe_certificate, pc_signature), x->x_path); +} + +static void +parse_section_table(struct executable *x, off_t off, int number_of_sections) +{ + const struct pe_section_header *psh; + int i; + + range_check(x, off, sizeof(*psh) * number_of_sections, + "section table"); + + if (x->x_headers_len <= off + sizeof(*psh) * number_of_sections) + errx(1, "section table outside of headers"); + + psh = (const struct pe_section_header *)(x->x_buf + off); + + if (number_of_sections >= MAX_SECTIONS) { + errx(1, "too many sections: got %d, should be %d", + number_of_sections, MAX_SECTIONS); + } + x->x_nsections = number_of_sections; + + for (i = 0; i < number_of_sections; i++) { + if (psh->psh_pointer_to_raw_data < x->x_headers_len) + errx(1, "section points inside the headers"); + + range_check(x, psh->psh_pointer_to_raw_data, + psh->psh_size_of_raw_data, "section"); +#if 0 + printf("section %d: start %d, size %d\n", + i, psh->psh_pointer_to_raw_data, psh->psh_size_of_raw_data); +#endif + x->x_section_off[i] = psh->psh_pointer_to_raw_data; + x->x_section_len[i] = psh->psh_size_of_raw_data; + psh++; + } +} + +static void +parse_directory(struct executable *x, off_t off, + int number_of_rva_and_sizes, int number_of_sections) +{ + //int i; + const struct pe_directory_entry *pde; + + //printf("Data Directory at offset %zd\n", off); + + if (number_of_rva_and_sizes <= PE_DIRECTORY_ENTRY_CERTIFICATE) { + errx(1, "wrong NumberOfRvaAndSizes %d; should be at least %d", + number_of_rva_and_sizes, PE_DIRECTORY_ENTRY_CERTIFICATE); + } + + range_check(x, off, sizeof(*pde) * number_of_rva_and_sizes, + "PE Data Directory"); + if (x->x_headers_len <= off + sizeof(*pde) * number_of_rva_and_sizes) + errx(1, "PE Data Directory outside of headers"); + + x->x_certificate_entry_off = + off + sizeof(*pde) * PE_DIRECTORY_ENTRY_CERTIFICATE; + x->x_certificate_entry_len = sizeof(*pde); +#if 0 + printf("certificate directory entry at offset %zd, len %zd\n", + x->x_certificate_entry_off, x->x_certificate_entry_len); + + pde = (struct pe_directory_entry *)(x->x_buf + off); + for (i = 0; i < number_of_rva_and_sizes; i++) { + printf("rva %zd, size %zd\n", pde->pde_rva, pde->pde_size); + pde++; + } +#endif + + return (parse_section_table(x, + off + sizeof(*pde) * number_of_rva_and_sizes, number_of_sections)); +} + +/* + * The PE checksum algorithm is undocumented; this code is mostly based on + * http://forum.sysinternals.com/optional-header-checksum-calculation_topic24214.html + * + * "Sum the entire image file, excluding the CheckSum field in the optional + * header, as an array of USHORTs, allowing any carry above 16 bits to be added + * back onto the low 16 bits. Then add the file size to get a 32-bit value." + * + * Note that most software does not care about the checksum at all; perhaps + * we could just set it to 0 instead. + * + * XXX: Endianness? + */ +static uint32_t +compute_checksum(const struct executable *x) +{ + uint32_t cksum = 0; + uint16_t tmp; + int i; + + range_check(x, x->x_checksum_off, x->x_checksum_len, "PE checksum"); + + assert(x->x_checksum_off % 2 == 0); + + for (i = 0; i + sizeof(tmp) < x->x_len; i += 2) { + /* + * Don't checksum the checksum. The +2 is because the checksum + * is 4 bytes, and here we're iterating over 2 byte chunks. + */ + if (i == x->x_checksum_off || i == x->x_checksum_off + 2) { + tmp = 0; + } else { + assert(i + sizeof(tmp) <= x->x_len); + memcpy(&tmp, x->x_buf + i, sizeof(tmp)); + } + + cksum += tmp; + cksum += cksum >> 16; + cksum &= 0xffff; + } + + cksum += cksum >> 16; + cksum &= 0xffff; + + cksum += x->x_len; + + return (cksum); +} + +static void +parse_optional_32_plus(struct executable *x, off_t off, + int number_of_sections) +{ +#if 0 + uint32_t computed_checksum; +#endif + const struct pe_optional_header_32_plus *po; + + range_check(x, off, sizeof(*po), "PE Optional Header"); + + po = (struct pe_optional_header_32_plus *)(x->x_buf + off); + switch (po->po_subsystem) { + case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION: + case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT: + case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME: + break; + default: + errx(1, "wrong PE Optional Header subsystem 0x%x", + po->po_subsystem); + } + +#if 0 + printf("subsystem %d, checksum 0x%x, %d data directories\n", + po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes); +#endif + + x->x_checksum_off = off + + offsetof(struct pe_optional_header_32_plus, po_checksum); + x->x_checksum_len = sizeof(po->po_checksum); +#if 0 + printf("checksum 0x%x at offset %zd, len %zd\n", + po->po_checksum, x->x_checksum_off, x->x_checksum_len); + + computed_checksum = compute_checksum(x); + if (computed_checksum != po->po_checksum) { + warnx("invalid PE+ checksum; is 0x%x, should be 0x%x", + po->po_checksum, computed_checksum); + } +#endif + + if (x->x_len < x->x_headers_len) + errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers); + x->x_headers_len = po->po_size_of_headers; + //printf("Size of Headers: %d\n", po->po_size_of_headers); + + return (parse_directory(x, off + sizeof(*po), + po->po_number_of_rva_and_sizes, number_of_sections)); +} + +static void +parse_optional_32(struct executable *x, off_t off, int number_of_sections) +{ +#if 0 + uint32_t computed_checksum; +#endif + const struct pe_optional_header_32 *po; + + range_check(x, off, sizeof(*po), "PE Optional Header"); + + po = (struct pe_optional_header_32 *)(x->x_buf + off); + switch (po->po_subsystem) { + case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION: + case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT: + case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME: + break; + default: + errx(1, "wrong PE Optional Header subsystem 0x%x", + po->po_subsystem); + } + +#if 0 + printf("subsystem %d, checksum 0x%x, %d data directories\n", + po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes); +#endif + + x->x_checksum_off = off + + offsetof(struct pe_optional_header_32, po_checksum); + x->x_checksum_len = sizeof(po->po_checksum); +#if 0 + printf("checksum at offset %zd, len %zd\n", + x->x_checksum_off, x->x_checksum_len); + + computed_checksum = compute_checksum(x); + if (computed_checksum != po->po_checksum) { + warnx("invalid PE checksum; is 0x%x, should be 0x%x", + po->po_checksum, computed_checksum); + } +#endif + + if (x->x_len < x->x_headers_len) + errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers); + x->x_headers_len = po->po_size_of_headers; + //printf("Size of Headers: %d\n", po->po_size_of_headers); + + return (parse_directory(x, off + sizeof(*po), + po->po_number_of_rva_and_sizes, number_of_sections)); +} + +static void +parse_optional(struct executable *x, off_t off, int number_of_sections) +{ + const struct pe_optional_header_32 *po; + + //printf("Optional header offset %zd\n", off); + + range_check(x, off, sizeof(*po), "PE Optional Header"); + + po = (struct pe_optional_header_32 *)(x->x_buf + off); + + switch (po->po_magic) { + case PE_OPTIONAL_MAGIC_32: + return (parse_optional_32(x, off, number_of_sections)); + case PE_OPTIONAL_MAGIC_32_PLUS: + return (parse_optional_32_plus(x, off, number_of_sections)); + default: + errx(1, "wrong PE Optional Header magic 0x%x", po->po_magic); + } +} + +static void +parse_pe(struct executable *x, off_t off) +{ + const struct pe_header *pe; + + //printf("PE offset %zd, PE size %zd\n", off, sizeof(*pe)); + + range_check(x, off, sizeof(*pe), "PE header"); + + pe = (struct pe_header *)(x->x_buf + off); + if (pe->pe_signature != PE_SIGNATURE) + errx(1, "wrong PE signature 0x%x", pe->pe_signature); + + //printf("Number of sections: %d\n", pe->pe_coff.coff_number_of_sections); + + parse_optional(x, off + sizeof(*pe), + pe->pe_coff.coff_number_of_sections); +} + +void +parse(struct executable *x) +{ + const struct mz_header *mz; + + range_check(x, 0, sizeof(*mz), "MZ header"); + + mz = (struct mz_header *)x->x_buf; + if (mz->mz_signature[0] != 'M' || mz->mz_signature[1] != 'Z') + errx(1, "MZ header not found"); + + return (parse_pe(x, mz->mz_lfanew)); +} + +static off_t +append(struct executable *x, void *ptr, size_t len) +{ + off_t off; + + /* + * XXX: Alignment. + */ + off = x->x_len; + x->x_buf = realloc(x->x_buf, x->x_len + len); + if (x->x_buf == NULL) + err(1, "realloc"); + memcpy(x->x_buf + x->x_len, ptr, len); + x->x_len += len; + + return (off); +} + +void +update(struct executable *x) +{ + uint32_t checksum; + struct pe_certificate *pc; + struct pe_directory_entry pde; + size_t pc_len; + off_t pc_off; + + pc_len = sizeof(*pc) + x->x_signature_len; + pc = calloc(1, pc_len); + if (pc == NULL) + err(1, "calloc"); + +#if 0 + /* + * Note that pc_len is the length of pc_certificate, + * not the whole structure. + * + * XXX: That's what the spec says - but it breaks at least + * sbverify and "pesign -S", so the spec is probably wrong. + */ + pc->pc_len = x->x_signature_len; +#else + pc->pc_len = pc_len; +#endif + pc->pc_revision = PE_CERTIFICATE_REVISION; + pc->pc_type = PE_CERTIFICATE_TYPE; + memcpy(&pc->pc_signature, x->x_signature, x->x_signature_len); + + pc_off = append(x, pc, pc_len); +#if 0 + printf("added signature chunk at offset %zd, len %zd\n", + pc_off, pc_len); +#endif + + free(pc); + + pde.pde_rva = pc_off; + pde.pde_size = pc_len; + memcpy(x->x_buf + x->x_certificate_entry_off, &pde, sizeof(pde)); + + checksum = compute_checksum(x); + assert(sizeof(checksum) == x->x_checksum_len); + memcpy(x->x_buf + x->x_checksum_off, &checksum, sizeof(checksum)); +#if 0 + printf("new checksum 0x%x\n", checksum); +#endif +} diff --git a/usr.sbin/uefisign/uefisign.8 b/usr.sbin/uefisign/uefisign.8 new file mode 100644 index 0000000000..3c9a9e97f6 --- /dev/null +++ b/usr.sbin/uefisign/uefisign.8 @@ -0,0 +1,93 @@ +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/usr.sbin/uefisign/uefisign.8 285671 2015-07-18 12:03:17Z trasz $ +.\" +.Dd July 11, 2015 +.Dt UEFISIGN 8 +.Os +.Sh NAME +.Nm uefisign +.Nd UEFI Secure Boot signing utility +.Sh SYNOPSIS +.Nm +.Fl k Ar key +.Fl c Ar certificate +.Fl o Ar output +.Op Fl v +.Ar file +.Nm +.Fl V +.Op Fl v +.Ar file +.Sh DESCRIPTION +The +.Nm +utility signs PE binary files using Authenticode scheme, as required by +UEFI Secure Boot specification. +Alternatively, it can be used to view and verify existing signatures. +These options are available: +.Bl -tag -width ".Fl l" +.It Fl V +Determine whether the file is signed. +Note that this does not verify the correctness of the signature; +only that the file contains a signature. +.It Fl k +Name of file containing the private key used to sign the binary. +.It Fl c +Name of file containing the certificate used to sign the binary. +.It Fl o +Name of file to write the signed binary to. +.It Fl v +Be verbose. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh EXAMPLES +Generate self-signed certificate and use it to sign a binary: +.Dl /usr/share/examples/uefisign/uefikeys testcert +.Dl uefisign -c testcert.pem -k testcert.key -o signed-binary binary +.Pp +View signature: +.Dl uefisign -Vv binary +.Sh SEE ALSO +.Xr openssl 1 , +.Xr loader 8 , +.Xr uefi 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 10.2 . +.Sh AUTHORS +The +.Nm +utility was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff --git a/usr.sbin/uefisign/uefisign.c b/usr.sbin/uefisign/uefisign.c new file mode 100644 index 0000000000..390eab562b --- /dev/null +++ b/usr.sbin/uefisign/uefisign.c @@ -0,0 +1,425 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD: head/usr.sbin/uefisign/uefisign.c 279315 2015-02-26 09:15:24Z trasz $"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "uefisign.h" +#include "magic.h" + +static void +usage(void) +{ + + fprintf(stderr, "usage: uefisign -c cert -k key -o outfile [-v] file\n" + " uefisign -V [-c cert] [-v] file\n"); + exit(1); +} + +static char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + err(1, "strdup"); + return (c); +} + +FILE * +checked_fopen(const char *path, const char *mode) +{ + FILE *fp; + + assert(path != NULL); + + fp = fopen(path, mode); + if (fp == NULL) + err(1, "%s", path); + return (fp); +} + +void +send_chunk(const void *buf, size_t len, int pipefd) +{ + ssize_t ret; + + ret = write(pipefd, &len, sizeof(len)); + if (ret != sizeof(len)) + err(1, "write"); + ret = write(pipefd, buf, len); + if (ret != (ssize_t)len) + err(1, "write"); +} + +void +receive_chunk(void **bufp, size_t *lenp, int pipefd) +{ + ssize_t ret; + size_t len; + void *buf; + + ret = read(pipefd, &len, sizeof(len)); + if (ret != sizeof(len)) + err(1, "read"); + + buf = calloc(1, len); + if (buf == NULL) + err(1, "calloc"); + + ret = read(pipefd, buf, len); + if (ret != (ssize_t)len) + err(1, "read"); + + *bufp = buf; + *lenp = len; +} + +static char * +bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 1; /* +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + err(1, "malloc"); + + tmp = hex; + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +/* + * We need to replace a standard chunk of PKCS7 signature with one mandated + * by Authenticode. Problem is, replacing it just like that and then calling + * PKCS7_final() would make OpenSSL segfault somewhere in PKCS7_dataFinal(). + * So, instead, we call PKCS7_dataInit(), then put our Authenticode-specific + * data into BIO it returned, then call PKCS7_dataFinal() - which now somehow + * does not panic - and _then_ we replace it in the signature. This technique + * was used in sbsigntool by Jeremy Kerr, and might have originated in + * osslsigncode. + */ +static void +magic(PKCS7 *pkcs7, const char *digest, size_t digest_len) +{ + BIO *bio, *t_bio; + ASN1_TYPE *t; + ASN1_STRING *s; + CONF *cnf; + unsigned char *buf, *tmp; + char *digest_hex, *magic_conf, *str; + int len, nid, ok; + + digest_hex = bin2hex(digest, digest_len); + + /* + * Construct the SpcIndirectDataContent chunk. + */ + nid = OBJ_create("1.3.6.1.4.1.311.2.1.4", NULL, NULL); + + asprintf(&magic_conf, magic_fmt, digest_hex); + if (magic_conf == NULL) + err(1, "asprintf"); + + bio = BIO_new_mem_buf((void *)magic_conf, -1); + if (bio == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_new_mem_buf(3) failed"); + } + + cnf = NCONF_new(NULL); + if (cnf == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "NCONF_new(3) failed"); + } + + ok = NCONF_load_bio(cnf, bio, NULL); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "NCONF_load_bio(3) failed"); + } + + str = NCONF_get_string(cnf, "default", "asn1"); + if (str == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "NCONF_get_string(3) failed"); + } + + t = ASN1_generate_nconf(str, cnf); + if (t == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "ASN1_generate_nconf(3) failed"); + } + + /* + * We now have our proprietary piece of ASN.1. Let's do + * the actual signing. + */ + len = i2d_ASN1_TYPE(t, NULL); + tmp = buf = calloc(1, len); + if (tmp == NULL) + err(1, "calloc"); + i2d_ASN1_TYPE(t, &tmp); + + /* + * We now have contents of 't' stuffed into memory buffer 'buf'. + */ + tmp = NULL; + t = NULL; + + t_bio = PKCS7_dataInit(pkcs7, NULL); + if (t_bio == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_dataInit(3) failed"); + } + + BIO_write(t_bio, buf + 2, len - 2); + + ok = PKCS7_dataFinal(pkcs7, t_bio); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_dataFinal(3) failed"); + } + + t = ASN1_TYPE_new(); + s = ASN1_STRING_new(); + ASN1_STRING_set(s, buf, len); + ASN1_TYPE_set(t, V_ASN1_SEQUENCE, s); + + PKCS7_set0_type_other(pkcs7->d.sign->contents, nid, t); +} + +static void +sign(X509 *cert, EVP_PKEY *key, int pipefd) +{ + PKCS7 *pkcs7; + BIO *bio, *out; + const EVP_MD *md; + PKCS7_SIGNER_INFO *info; + void *digest, *signature; + size_t digest_len, signature_len; + int ok; + + assert(cert != NULL); + assert(key != NULL); + + receive_chunk(&digest, &digest_len, pipefd); + + bio = BIO_new_mem_buf(digest, digest_len); + if (bio == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_new_mem_buf(3) failed"); + } + + pkcs7 = PKCS7_sign(NULL, NULL, NULL, bio, PKCS7_BINARY | PKCS7_PARTIAL); + if (pkcs7 == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_sign(3) failed"); + } + + md = EVP_get_digestbyname(DIGEST); + if (md == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST); + } + + info = PKCS7_sign_add_signer(pkcs7, cert, key, md, 0); + if (info == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_sign_add_signer(3) failed"); + } + + /* + * XXX: All the signed binaries seem to have this, but where is it + * described in the spec? + */ + PKCS7_add_signed_attribute(info, NID_pkcs9_contentType, + V_ASN1_OBJECT, OBJ_txt2obj("1.3.6.1.4.1.311.2.1.4", 1)); + + magic(pkcs7, digest, digest_len); + +#if 0 + out = BIO_new(BIO_s_file()); + BIO_set_fp(out, stdout, BIO_NOCLOSE); + PKCS7_print_ctx(out, pkcs7, 0, NULL); + + i2d_PKCS7_bio(out, pkcs7); +#endif + + out = BIO_new(BIO_s_mem()); + if (out == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_new(3) failed"); + } + + ok = i2d_PKCS7_bio(out, pkcs7); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "i2d_PKCS7_bio(3) failed"); + } + + signature_len = BIO_get_mem_data(out, &signature); + if (signature_len <= 0) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_get_mem_data(3) failed"); + } + + (void)BIO_set_close(out, BIO_NOCLOSE); + BIO_free(out); + + send_chunk(signature, signature_len, pipefd); +} + +static int +wait_for_child(pid_t pid) +{ + int status; + + pid = waitpid(pid, &status, 0); + if (pid == -1) + err(1, "waitpid"); + + return (WEXITSTATUS(status)); +} + +int +main(int argc, char **argv) +{ + int ch, error; + bool Vflag = false, vflag = false; + const char *certpath = NULL, *keypath = NULL, *outpath = NULL, *inpath = NULL; + FILE *certfp = NULL, *keyfp = NULL; + X509 *cert = NULL; + EVP_PKEY *key = NULL; + pid_t pid; + int pipefds[2]; + + while ((ch = getopt(argc, argv, "Vc:k:o:v")) != -1) { + switch (ch) { + case 'V': + Vflag = true; + break; + case 'c': + certpath = checked_strdup(optarg); + break; + case 'k': + keypath = checked_strdup(optarg); + break; + case 'o': + outpath = checked_strdup(optarg); + break; + case 'v': + vflag = true; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + if (argc != 1) + usage(); + + if (Vflag) { + if (certpath != NULL) + errx(1, "-V and -c are mutually exclusive"); + if (keypath != NULL) + errx(1, "-V and -k are mutually exclusive"); + if (outpath != NULL) + errx(1, "-V and -o are mutually exclusive"); + } else { + if (certpath == NULL) + errx(1, "-c option is mandatory"); + if (keypath == NULL) + errx(1, "-k option is mandatory"); + if (outpath == NULL) + errx(1, "-o option is mandatory"); + } + + inpath = argv[0]; + + OPENSSL_config(NULL); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + error = pipe(pipefds); + if (error != 0) + err(1, "pipe"); + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) + return (child(inpath, outpath, pipefds[1], Vflag, vflag)); + + if (!Vflag) { + certfp = checked_fopen(certpath, "r"); + cert = PEM_read_X509(certfp, NULL, NULL, NULL); + if (cert == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "failed to load certificate from %s", certpath); + } + + keyfp = checked_fopen(keypath, "r"); + key = PEM_read_PrivateKey(keyfp, NULL, NULL, NULL); + if (key == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "failed to load private key from %s", keypath); + } + + sign(cert, key, pipefds[0]); + } + + return (wait_for_child(pid)); +} diff --git a/usr.sbin/uefisign/uefisign.h b/usr.sbin/uefisign/uefisign.h new file mode 100644 index 0000000000..ac0bdf02a7 --- /dev/null +++ b/usr.sbin/uefisign/uefisign.h @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/usr.sbin/uefisign/uefisign.h 279315 2015-02-26 09:15:24Z trasz $ + */ + +#ifndef EFISIGN_H +#define EFISIGN_H + +#include +#include + +#define DIGEST "SHA256" +#define MAX_SECTIONS 128 + +struct executable { + const char *x_path; + FILE *x_fp; + + char *x_buf; + size_t x_len; + + /* + * Set by pe_parse(), used by digest(). + */ + size_t x_headers_len; + + off_t x_checksum_off; + size_t x_checksum_len; + + off_t x_certificate_entry_off; + size_t x_certificate_entry_len; + + int x_nsections; + off_t x_section_off[MAX_SECTIONS]; + size_t x_section_len[MAX_SECTIONS]; + + /* + * Computed by digest(). + */ + unsigned char x_digest[EVP_MAX_MD_SIZE]; + unsigned int x_digest_len; + + /* + * Received from the parent process, which computes it in sign(). + */ + void *x_signature; + size_t x_signature_len; +}; + + +FILE *checked_fopen(const char *path, const char *mode); +void send_chunk(const void *buf, size_t len, int pipefd); +void receive_chunk(void **bufp, size_t *lenp, int pipefd); + +int child(const char *inpath, const char *outpath, int pipefd, + bool Vflag, bool vflag); + +void parse(struct executable *x); +void update(struct executable *x); +size_t signature_size(const struct executable *x); +void show_certificate(const struct executable *x); +void range_check(const struct executable *x, + off_t off, size_t len, const char *name); + +#endif /* !EFISIGN_H */