From: Frank Rowand <frank.rowand@xxxxxxxx> Fix various kfree() issues related to of_overlay_apply(). - Double kfree() of fdt and tree when init_overlay_changeset() returns an error. - free_overlay_changeset() free the root of the unflattened overlay (variable tree) instead of the memory that contains the unflattened overlay. - For the case of a failure during applying an overlay, move kfree() of new_fdt and overlay_mem into the function that allocated them. For the case of removing an overlay, the kfree() remains in free_overlay_changeset(). - Check return value of of_fdt_unflatten_tree() for error instead of checking the returnded value of overlay_root. More clearly document policy related to lifetime of pointers into overlay memory. Double kfree() Reported-by: Slawomir Stepien <slawomir.stepien@xxxxxxxxx> Signed-off-by: Frank Rowand <frank.rowand@xxxxxxxx> --- Changes since v1: - Move kfree()s from init_overlay_changeset() to of_overlay_fdt_apply() - Better document lifetime of pointers into overlay, both in overlay.c and Documentation/devicetree/overlay-notes.rst Documentation/devicetree/overlay-notes.rst | 23 +++- drivers/of/overlay.c | 127 ++++++++++++--------- 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/Documentation/devicetree/overlay-notes.rst b/Documentation/devicetree/overlay-notes.rst index b2b8db765b8c..7a6e85f75567 100644 --- a/Documentation/devicetree/overlay-notes.rst +++ b/Documentation/devicetree/overlay-notes.rst @@ -119,10 +119,25 @@ Finally, if you need to remove all overlays in one-go, just call of_overlay_remove_all() which will remove every single one in the correct order. -In addition, there is the option to register notifiers that get called on +There is the option to register notifiers that get called on overlay operations. See of_overlay_notifier_register/unregister and enum of_overlay_notify_action for details. -Note that a notifier callback is not supposed to store pointers to a device -tree node or its content beyond OF_OVERLAY_POST_REMOVE corresponding to the -respective node it received. +A notifier callback for OF_OVERLAY_PRE_APPLY, OF_OVERLAY_POST_APPLY, or +OF_OVERLAY_PRE_REMOVE may store pointers to a device tree node in the overlay +or its content but these pointers must not persist past the notifier callback +for OF_OVERLAY_POST_REMOVE. The memory containing the overlay will be +kfree()ed after OF_OVERLAY_POST_REMOVE notifiers are called. Note that the +memory will be kfree()ed even if the notifier for OF_OVERLAY_POST_REMOVE +returns an error. + +The changeset notifiers in drivers/of/dynamic.c are a second type of notifier +that could be triggered by applying or removing an overlay. These notifiers +are not allowed to store pointers to a device tree node in the overlay +or its content. The overlay code does not protect against such pointers +remaining active when the memory containing the overlay is freed as a result +of removing the overlay. + +Any other code that retains a pointer to the overlay nodes or data is +considered to be a bug because after removing the overlay the pointer +will refer to freed memory. diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index f74aa9ff67aa..c8e999518f2f 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -58,6 +58,7 @@ struct fragment { * @id: changeset identifier * @ovcs_list: list on which we are located * @new_fdt: Memory allocated to hold unflattened aligned FDT + * @overlay_mem: the memory chunk that contains @overlay_tree * @overlay_tree: expanded device tree that contains the fragment nodes * @count: count of fragment structures * @fragments: fragment nodes in the overlay expanded device tree @@ -68,6 +69,7 @@ struct overlay_changeset { int id; struct list_head ovcs_list; const void *new_fdt; + const void *overlay_mem; struct device_node *overlay_tree; int count; struct fragment *fragments; @@ -720,18 +722,20 @@ static struct device_node *find_target(struct device_node *info_node) * init_overlay_changeset() - initialize overlay changeset from overlay tree * @ovcs: Overlay changeset to build * @new_fdt: Memory allocated to hold unflattened aligned FDT + * @tree_mem: Memory that contains @overlay_tree * @overlay_tree: Contains the overlay fragments and overlay fixup nodes * * Initialize @ovcs. Populate @ovcs->fragments with node information from * the top level of @overlay_tree. The relevant top level nodes are the * fragment nodes and the __symbols__ node. Any other top level node will - * be ignored. + * be ignored. Populate other @ovcs fields. * * Return: 0 on success, -ENOMEM if memory allocation failure, -EINVAL if error * detected in @overlay_tree, or -ENOSPC if idr_alloc() error. */ static int init_overlay_changeset(struct overlay_changeset *ovcs, - const void *new_fdt, struct device_node *overlay_tree) + const void *new_fdt, const void *tree_mem, + struct device_node *overlay_tree) { struct device_node *node, *overlay_node; struct fragment *fragment; @@ -751,9 +755,6 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, if (!of_node_is_root(overlay_tree)) pr_debug("%s() overlay_tree is not root\n", __func__); - ovcs->overlay_tree = overlay_tree; - ovcs->new_fdt = new_fdt; - INIT_LIST_HEAD(&ovcs->ovcs_list); of_changeset_init(&ovcs->cset); @@ -832,6 +833,9 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, ovcs->id = id; ovcs->count = cnt; + ovcs->new_fdt = new_fdt; + ovcs->overlay_mem = tree_mem; + ovcs->overlay_tree = overlay_tree; ovcs->fragments = fragments; return 0; @@ -846,7 +850,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, return ret; } -static void free_overlay_changeset(struct overlay_changeset *ovcs) +static void free_overlay_changeset_contents(struct overlay_changeset *ovcs) { int i; @@ -861,12 +865,20 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) of_node_put(ovcs->fragments[i].overlay); } kfree(ovcs->fragments); +} +static void free_overlay_changeset(struct overlay_changeset *ovcs) +{ + + free_overlay_changeset_contents(ovcs); + /* - * There should be no live pointers into ovcs->overlay_tree and + * There should be no live pointers into ovcs->overlay_mem and * ovcs->new_fdt due to the policy that overlay notifiers are not - * allowed to retain pointers into the overlay devicetree. + * allowed to retain pointers into the overlay devicetree other + * than the window between OF_OVERLAY_PRE_APPLY overlay notifiers + * and the OF_OVERLAY_POST_REMOVE overlay notifiers. */ - kfree(ovcs->overlay_tree); + kfree(ovcs->overlay_mem); kfree(ovcs->new_fdt); kfree(ovcs); } @@ -876,8 +888,10 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) * * of_overlay_apply() - Create and apply an overlay changeset * @new_fdt: Memory allocated to hold the aligned FDT + * @tree_mem: Memory that contains @overlay_tree * @overlay_tree: Expanded overlay device tree * @ovcs_id: Pointer to overlay changeset id + * @kfree_unsafe: Pointer to flag to not kfree() @new_fdt and @overlay_tree * * Creates and applies an overlay changeset. * @@ -910,34 +924,25 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) * refused. * * Returns 0 on success, or a negative error number. Overlay changeset - * id is returned to *ovcs_id. + * id is returned to *ovcs_id. When references to @new_fdt and @overlay_tree + * may exist, *kfree_unsafe is set to true. */ -static int of_overlay_apply(const void *new_fdt, - struct device_node *overlay_tree, int *ovcs_id) +static int of_overlay_apply(const void *new_fdt, void *tree_mem, + struct device_node *overlay_tree, int *ovcs_id, + bool *kfree_unsafe) { struct overlay_changeset *ovcs; int ret = 0, ret_revert, ret_tmp; - /* - * As of this point, new_fdt and overlay_tree belong to the overlay - * changeset. overlay changeset code is responsible for freeing them. - */ - if (devicetree_corrupt()) { pr_err("devicetree state suspect, refuse to apply overlay\n"); - kfree(new_fdt); - kfree(overlay_tree); - ret = -EBUSY; - goto out; + return -EBUSY; } ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL); if (!ovcs) { - kfree(new_fdt); - kfree(overlay_tree); - ret = -ENOMEM; - goto out; + return -ENOMEM; } of_overlay_mutex_lock(); @@ -945,28 +950,27 @@ static int of_overlay_apply(const void *new_fdt, ret = of_resolve_phandles(overlay_tree); if (ret) - goto err_free_overlay_tree; + goto err_free_ovcs; - ret = init_overlay_changeset(ovcs, new_fdt, overlay_tree); + ret = init_overlay_changeset(ovcs, new_fdt, tree_mem, overlay_tree); if (ret) - goto err_free_overlay_tree; + goto err_free_ovcs_contents; /* - * after overlay_notify(), ovcs->overlay_tree related pointers may have - * leaked to drivers, so can not kfree() overlay_tree, - * aka ovcs->overlay_tree; and can not free memory containing aligned - * fdt. The aligned fdt is contained within the memory at - * ovcs->new_fdt, possibly at an offset from ovcs->new_fdt. + * After overlay_notify(), ovcs->overlay_tree related pointers may have + * leaked to drivers, so can not kfree() ovcs->overlay_mem and + * ovcs->new_fdt until after OF_OVERLAY_POST_REMOVE notifiers. */ + *kfree_unsafe = true; ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY); if (ret) { pr_err("overlay changeset pre-apply notify error %d\n", ret); - goto err_free_overlay_changeset; + goto err_free_ovcs_contents; } ret = build_changeset(ovcs); if (ret) - goto err_free_overlay_changeset; + goto err_free_ovcs_contents; ret_revert = 0; ret = __of_changeset_apply_entries(&ovcs->cset, &ret_revert); @@ -976,7 +980,7 @@ static int of_overlay_apply(const void *new_fdt, ret_revert); devicetree_state_flags |= DTSF_APPLY_FAIL; } - goto err_free_overlay_changeset; + goto err_free_ovcs_contents; } ret = __of_changeset_apply_notify(&ovcs->cset); @@ -997,18 +1001,16 @@ static int of_overlay_apply(const void *new_fdt, goto out_unlock; -err_free_overlay_tree: - kfree(new_fdt); - kfree(overlay_tree); +err_free_ovcs_contents: + free_overlay_changeset_contents(ovcs); -err_free_overlay_changeset: - free_overlay_changeset(ovcs); +err_free_ovcs: + kfree(ovcs); out_unlock: mutex_unlock(&of_mutex); of_overlay_mutex_unlock(); -out: pr_debug("%s() err=%d\n", __func__, ret); return ret; @@ -1019,11 +1021,14 @@ int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size, { void *new_fdt; void *new_fdt_align; + void *overlay_mem; + bool kfree_unsafe; int ret; u32 size; struct device_node *overlay_root = NULL; *ovcs_id = 0; + kfree_unsafe = false; if (overlay_fdt_size < sizeof(struct fdt_header) || fdt_check_header(overlay_fdt)) { @@ -1046,30 +1051,37 @@ int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size, new_fdt_align = PTR_ALIGN(new_fdt, FDT_ALIGN_SIZE); memcpy(new_fdt_align, overlay_fdt, size); - of_fdt_unflatten_tree(new_fdt_align, NULL, &overlay_root); - if (!overlay_root) { + overlay_mem = of_fdt_unflatten_tree(new_fdt_align, NULL, &overlay_root); + if (!overlay_mem) { pr_err("unable to unflatten overlay_fdt\n"); ret = -EINVAL; goto out_free_new_fdt; } - ret = of_overlay_apply(new_fdt, overlay_root, ovcs_id); - if (ret < 0) { - /* - * new_fdt and overlay_root now belong to the overlay - * changeset. - * overlay changeset code is responsible for freeing them. - */ - goto out; - } + ret = of_overlay_apply(new_fdt, overlay_mem, overlay_root, ovcs_id, + &kfree_unsafe); + if (ret < 0) + goto out_free_overlay_mem; + /* + * new_fdt and overlay_mem now belong to the overlay changeset. + * free_overlay_changeset() is responsible for freeing them. + */ return 0; + /* + * After overlay_notify(), ovcs->overlay_tree related pointers may have + * leaked to drivers, so can not kfree() overlay_mem and new_fdt. This + * will result in a memory leak. + */ +out_free_overlay_mem: + if (!kfree_unsafe) + kfree(overlay_mem); out_free_new_fdt: - kfree(new_fdt); + if (!kfree_unsafe) + kfree(new_fdt); -out: return ret; } EXPORT_SYMBOL_GPL(of_overlay_fdt_apply); @@ -1237,6 +1249,11 @@ int of_overlay_remove(int *ovcs_id) *ovcs_id = 0; + /* + * Note that the overlay memory will be kfree()ed by + * free_overlay_changeset() even if the notifier for + * OF_OVERLAY_POST_REMOVE returns an error. + */ ret_tmp = overlay_notify(ovcs, OF_OVERLAY_POST_REMOVE); if (ret_tmp) { pr_err("overlay changeset post-remove notify error %d\n", -- Frank Rowand <frank.rowand@xxxxxxxx>