On kwi 10, 2022 23:11, Frank Rowand wrote: > adding cc: Jan Kiszka <jan.kiszka@xxxxxxxxxxx> > > On 4/10/22 16:08, frowand.list@xxxxxxxxx wrote: > > 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> Hi Frank It looks good to me. Reviewed-by: Slawomir Stepien <slawomir.stepien@xxxxxxxxx> > > --- > > > > 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", > -- Slawomir Stepien