| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Verified Boot for Embedded (VBE) OS request (device tree fixup) functions | |
| * | |
| * Copyright 2022 Google LLC | |
| * Written by Simon Glass <sjg@chromium.org> | |
| */ | |
| #define LOG_CATEGORY LOGC_BOOT | |
| #include <dm.h> | |
| #include <event.h> | |
| #include <image.h> | |
| #include <malloc.h> | |
| #include <rng.h> | |
| #include <dm/ofnode.h> | |
| #define VBE_PREFIX "vbe," | |
| #define VBE_PREFIX_LEN (sizeof(VBE_PREFIX) - 1) | |
| #define VBE_ERR_STR_LEN 128 | |
| #define VBE_MAX_RAND_SIZE 256 | |
| struct vbe_result { | |
| int errnum; | |
| char err_str[VBE_ERR_STR_LEN]; | |
| }; | |
| typedef int (*vbe_req_func)(ofnode node, struct vbe_result *result); | |
| static int handle_random_req(ofnode node, int default_size, | |
| struct vbe_result *result) | |
| { | |
| char buf[VBE_MAX_RAND_SIZE]; | |
| struct udevice *dev; | |
| u32 size; | |
| int ret; | |
| if (!CONFIG_IS_ENABLED(DM_RNG)) | |
| return -ENOTSUPP; | |
| if (ofnode_read_u32(node, "vbe,size", &size)) { | |
| if (!default_size) { | |
| snprintf(result->err_str, VBE_ERR_STR_LEN, | |
| "Missing vbe,size property"); | |
| return log_msg_ret("byt", -EINVAL); | |
| } | |
| size = default_size; | |
| } | |
| if (size > VBE_MAX_RAND_SIZE) { | |
| snprintf(result->err_str, VBE_ERR_STR_LEN, | |
| "vbe,size %#x exceeds max size %#x", size, | |
| VBE_MAX_RAND_SIZE); | |
| return log_msg_ret("siz", -E2BIG); | |
| } | |
| ret = uclass_first_device_err(UCLASS_RNG, &dev); | |
| if (ret) { | |
| snprintf(result->err_str, VBE_ERR_STR_LEN, | |
| "Cannot find random-number device (err=%d)", ret); | |
| return log_msg_ret("wr", ret); | |
| } | |
| ret = dm_rng_read(dev, buf, size); | |
| if (ret) { | |
| snprintf(result->err_str, VBE_ERR_STR_LEN, | |
| "Failed to read random-number device (err=%d)", ret); | |
| return log_msg_ret("rd", ret); | |
| } | |
| ret = ofnode_write_prop(node, "data", buf, size, true); | |
| if (ret) | |
| return log_msg_ret("wr", -EINVAL); | |
| return 0; | |
| } | |
| static int vbe_req_random_seed(ofnode node, struct vbe_result *result) | |
| { | |
| return handle_random_req(node, 0, result); | |
| } | |
| static int vbe_req_aslr_move(ofnode node, struct vbe_result *result) | |
| { | |
| return -ENOTSUPP; | |
| } | |
| static int vbe_req_aslr_rand(ofnode node, struct vbe_result *result) | |
| { | |
| return handle_random_req(node, 4, result); | |
| } | |
| static int vbe_req_efi_runtime_rand(ofnode node, struct vbe_result *result) | |
| { | |
| return handle_random_req(node, 4, result); | |
| } | |
| static struct vbe_req { | |
| const char *compat; | |
| vbe_req_func func; | |
| } vbe_reqs[] = { | |
| /* address space layout randomization - move the OS in memory */ | |
| { "aslr-move", vbe_req_aslr_move }, | |
| /* provide random data for address space layout randomization */ | |
| { "aslr-rand", vbe_req_aslr_rand }, | |
| /* provide random data for EFI-runtime-services address */ | |
| { "efi-runtime-rand", vbe_req_efi_runtime_rand }, | |
| /* generate random data bytes to see the OS's rand generator */ | |
| { "random-rand", vbe_req_random_seed }, | |
| }; | |
| static int vbe_process_request(ofnode node, struct vbe_result *result) | |
| { | |
| const char *compat, *req_name; | |
| int i; | |
| compat = ofnode_read_string(node, "compatible"); | |
| if (!compat) | |
| return 0; | |
| if (strlen(compat) <= VBE_PREFIX_LEN || | |
| strncmp(compat, VBE_PREFIX, VBE_PREFIX_LEN)) | |
| return -EINVAL; | |
| req_name = compat + VBE_PREFIX_LEN; /* drop "vbe," prefix */ | |
| for (i = 0; i < ARRAY_SIZE(vbe_reqs); i++) { | |
| if (!strcmp(vbe_reqs[i].compat, req_name)) { | |
| int ret; | |
| ret = vbe_reqs[i].func(node, result); | |
| if (ret) | |
| return log_msg_ret("req", ret); | |
| return 0; | |
| } | |
| } | |
| snprintf(result->err_str, VBE_ERR_STR_LEN, "Unknown request: %s", | |
| req_name); | |
| return -ENOTSUPP; | |
| } | |
| /** | |
| * bootmeth_vbe_ft_fixup() - Process VBE OS requests and do device tree fixups | |
| * | |
| * If there are no images provided, this does nothing and returns 0. | |
| * | |
| * @ctx: Context for event | |
| * @event: Event to process | |
| * @return 0 if OK, -ve on error | |
| */ | |
| static int bootmeth_vbe_ft_fixup(void *ctx, struct event *event) | |
| { | |
| const struct event_ft_fixup *fixup = &event->data.ft_fixup; | |
| const struct bootm_headers *images = fixup->images; | |
| ofnode parent, dest_parent, root, node; | |
| oftree fit; | |
| if (!images || !images->fit_hdr_os) | |
| return 0; | |
| /* Get the image node with requests in it */ | |
| log_debug("fit=%p, noffset=%d\n", images->fit_hdr_os, | |
| images->fit_noffset_os); | |
| fit = oftree_from_fdt(images->fit_hdr_os); | |
| root = oftree_root(fit); | |
| if (of_live_active()) { | |
| log_warning("Cannot fix up live tree\n"); | |
| return 0; | |
| } | |
| if (!ofnode_valid(root)) | |
| return log_msg_ret("rt", -EINVAL); | |
| parent = noffset_to_ofnode(root, images->fit_noffset_os); | |
| if (!ofnode_valid(parent)) | |
| return log_msg_ret("img", -EINVAL); | |
| dest_parent = oftree_path(fixup->tree, "/chosen"); | |
| if (!ofnode_valid(dest_parent)) | |
| return log_msg_ret("dst", -EINVAL); | |
| ofnode_for_each_subnode(node, parent) { | |
| const char *name = ofnode_get_name(node); | |
| struct vbe_result result; | |
| ofnode dest; | |
| int ret; | |
| log_debug("copy subnode: %s\n", name); | |
| ret = ofnode_add_subnode(dest_parent, name, &dest); | |
| if (ret && ret != -EEXIST) | |
| return log_msg_ret("add", ret); | |
| ret = ofnode_copy_props(dest, node); | |
| if (ret) | |
| return log_msg_ret("cp", ret); | |
| *result.err_str = '\0'; | |
| ret = vbe_process_request(dest, &result); | |
| if (ret) { | |
| result.errnum = ret; | |
| log_warning("Failed to process VBE request %s (err=%d)\n", | |
| ofnode_get_name(dest), ret); | |
| if (*result.err_str) { | |
| char *msg = strdup(result.err_str); | |
| if (!msg) | |
| return log_msg_ret("msg", -ENOMEM); | |
| ret = ofnode_write_string(dest, "vbe,error", | |
| msg); | |
| if (ret) { | |
| free(msg); | |
| return log_msg_ret("str", -ENOMEM); | |
| } | |
| } | |
| if (result.errnum) { | |
| ret = ofnode_write_u32(dest, "vbe,errnum", | |
| result.errnum); | |
| if (ret) | |
| return log_msg_ret("num", -ENOMEM); | |
| if (result.errnum != -ENOTSUPP) | |
| return log_msg_ret("pro", | |
| result.errnum); | |
| if (result.errnum == -ENOTSUPP && | |
| ofnode_read_bool(dest, "vbe,required")) { | |
| log_err("Cannot handle required request: %s\n", | |
| ofnode_get_name(dest)); | |
| return log_msg_ret("req", | |
| result.errnum); | |
| } | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| EVENT_SPY_FULL(EVT_FT_FIXUP, bootmeth_vbe_ft_fixup); |