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 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>

I should have also added:

Fixes: 83ef4777f5ff ("of: overlay: Stop leaking resources on overlay removal")

-Frank

> 
> 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",




[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