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

 



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>




[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