Re: [PATCH v2 2/2] of: overlay: rework overlay apply and remove kfree()s

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux