Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f8274f14 authored by Rob Herring's avatar Rob Herring
Browse files

Merge tag 'kfree_validate_v7-for-4.20' of...

Merge tag 'kfree_validate_v7-for-4.20' of git://git.kernel.org/pub/scm/linux/kernel/git/frowand/linux

 into dt/next

Pull overlay validation checks from Frank Rowand:

"Add checks to (1) overlay apply process and (2) memory freeing
triggered by overlay release.  The checks are intended to detect
possible memory leaks and invalid overlays.

The checks revealed bugs in existing code.  Fixed the bugs.

While fixing bugs, noted other issues, which are fixed in
separate patches."

Signed-off-by: default avatarRob Herring <robh@kernel.org>
parents 1ae367a2 eeb07c57
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -270,6 +270,8 @@ int dlpar_detach_node(struct device_node *dn)
	if (rc)
		return rc;

	of_node_put(dn);

	return 0;
}

+47 −12
Original line number Diff line number Diff line
@@ -205,15 +205,24 @@ static void __of_attach_node(struct device_node *np)
	const __be32 *phandle;
	int sz;

	np->name = __of_get_property(np, "name", NULL) ? : "<NULL>";
	np->type = __of_get_property(np, "device_type", NULL) ? : "<NULL>";
	if (!of_node_check_flag(np, OF_OVERLAY)) {
		np->name = __of_get_property(np, "name", NULL);
		np->type = __of_get_property(np, "device_type", NULL);
		if (!np->name)
			np->name = "<NULL>";
		if (!np->type)
			np->type = "<NULL>";

		phandle = __of_get_property(np, "phandle", &sz);
		if (!phandle)
			phandle = __of_get_property(np, "linux,phandle", &sz);
		if (IS_ENABLED(CONFIG_PPC_PSERIES) && !phandle)
			phandle = __of_get_property(np, "ibm,phandle", &sz);
	np->phandle = (phandle && (sz >= 4)) ? be32_to_cpup(phandle) : 0;
		if (phandle && (sz >= 4))
			np->phandle = be32_to_cpup(phandle);
		else
			np->phandle = 0;
	}

	np->child = NULL;
	np->sibling = np->parent->child;
@@ -272,9 +281,6 @@ void __of_detach_node(struct device_node *np)

/**
 * of_detach_node() - "Unplug" a node from the device tree.
 *
 * The caller must hold a reference to the node.  The memory associated with
 * the node is not freed until its refcount goes to zero.
 */
int of_detach_node(struct device_node *np)
{
@@ -330,6 +336,25 @@ void of_node_release(struct kobject *kobj)
	if (!of_node_check_flag(node, OF_DYNAMIC))
		return;

	if (of_node_check_flag(node, OF_OVERLAY)) {

		if (!of_node_check_flag(node, OF_OVERLAY_FREE_CSET)) {
			/* premature refcount of zero, do not free memory */
			pr_err("ERROR: memory leak before free overlay changeset,  %pOF\n",
			       node);
			return;
		}

		/*
		 * If node->properties non-empty then properties were added
		 * to this node either by different overlay that has not
		 * yet been removed, or by a non-overlay mechanism.
		 */
		if (node->properties)
			pr_err("ERROR: %s(), unexpected properties in %pOF\n",
			       __func__, node);
	}

	property_list_free(node->properties);
	property_list_free(node->deadprops);

@@ -434,6 +459,16 @@ struct device_node *__of_node_dup(const struct device_node *np,

static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
{
	if (ce->action == OF_RECONFIG_ATTACH_NODE &&
	    of_node_check_flag(ce->np, OF_OVERLAY)) {
		if (kref_read(&ce->np->kobj.kref) > 1) {
			pr_err("ERROR: memory leak, expected refcount 1 instead of %d, of_node_get()/of_node_put() unbalanced - destroy cset entry: attach overlay node %pOF\n",
			       kref_read(&ce->np->kobj.kref), ce->np);
		} else {
			of_node_set_flag(ce->np, OF_OVERLAY_FREE_CSET);
		}
	}

	of_node_put(ce->np);
	list_del(&ce->node);
	kfree(ce);
+3 −1
Original line number Diff line number Diff line
@@ -133,6 +133,9 @@ int __of_attach_node_sysfs(struct device_node *np)
	}
	if (!name)
		return -ENOMEM;

	of_node_get(np);

	rc = kobject_add(&np->kobj, parent, "%s", name);
	kfree(name);
	if (rc)
@@ -159,6 +162,5 @@ void __of_detach_node_sysfs(struct device_node *np)
		kobject_del(&np->kobj);
	}

	/* finally remove the kobj_init ref */
	of_node_put(np);
}
+231 −61
Original line number Diff line number Diff line
@@ -23,14 +23,34 @@

#include "of_private.h"

/**
 * struct target - info about current target node as recursing through overlay
 * @np:			node where current level of overlay will be applied
 * @in_livetree:	@np is a node in the live devicetree
 *
 * Used in the algorithm to create the portion of a changeset that describes
 * an overlay fragment, which is a devicetree subtree.  Initially @np is a node
 * in the live devicetree where the overlay subtree is targeted to be grafted
 * into.  When recursing to the next level of the overlay subtree, the target
 * also recurses to the next level of the live devicetree, as long as overlay
 * subtree node also exists in the live devicetree.  When a node in the overlay
 * subtree does not exist at the same level in the live devicetree, target->np
 * points to a newly allocated node, and all subsequent targets in the subtree
 * will be newly allocated nodes.
 */
struct target {
	struct device_node *np;
	bool in_livetree;
};

/**
 * struct fragment - info about fragment nodes in overlay expanded device tree
 * @target:	target of the overlay operation
 * @overlay:	pointer to the __overlay__ node
 */
struct fragment {
	struct device_node *target;
	struct device_node *overlay;
	struct device_node *target;
};

/**
@@ -72,8 +92,7 @@ static int devicetree_corrupt(void)
}

static int build_changeset_next_level(struct overlay_changeset *ovcs,
		struct device_node *target_node,
		const struct device_node *overlay_node);
		struct target *target, const struct device_node *overlay_node);

/*
 * of_resolve_phandles() finds the largest phandle in the live tree.
@@ -257,15 +276,23 @@ static struct property *dup_and_fixup_symbol_prop(
/**
 * add_changeset_property() - add @overlay_prop to overlay changeset
 * @ovcs:		overlay changeset
 * @target_node:	where to place @overlay_prop in live tree
 * @target:		where @overlay_prop will be placed
 * @overlay_prop:	property to add or update, from overlay tree
 * @is_symbols_prop:	1 if @overlay_prop is from node "/__symbols__"
 *
 * If @overlay_prop does not already exist in @target_node, add changeset entry
 * to add @overlay_prop in @target_node, else add changeset entry to update
 * If @overlay_prop does not already exist in live devicetree, add changeset
 * entry to add @overlay_prop in @target, else add changeset entry to update
 * value of @overlay_prop.
 *
 * Some special properties are not updated (no error returned).
 * @target may be either in the live devicetree or in a new subtree that
 * is contained in the changeset.
 *
 * Some special properties are not added or updated (no error returned):
 * "name", "phandle", "linux,phandle".
 *
 * Properties "#address-cells" and "#size-cells" are not updated if they
 * are already in the live tree, but if present in the live tree, the values
 * in the overlay must match the values in the live tree.
 *
 * Update of property in symbols node is not allowed.
 *
@@ -273,20 +300,24 @@ static struct property *dup_and_fixup_symbol_prop(
 * invalid @overlay.
 */
static int add_changeset_property(struct overlay_changeset *ovcs,
		struct device_node *target_node,
		struct property *overlay_prop,
		struct target *target, struct property *overlay_prop,
		bool is_symbols_prop)
{
	struct property *new_prop = NULL, *prop;
	int ret = 0;
	bool check_for_non_overlay_node = false;

	prop = of_find_property(target_node, overlay_prop->name, NULL);

	if (target->in_livetree)
		if (!of_prop_cmp(overlay_prop->name, "name") ||
		    !of_prop_cmp(overlay_prop->name, "phandle") ||
		    !of_prop_cmp(overlay_prop->name, "linux,phandle"))
			return 0;

	if (target->in_livetree)
		prop = of_find_property(target->np, overlay_prop->name, NULL);
	else
		prop = NULL;

	if (is_symbols_prop) {
		if (prop)
			return -EINVAL;
@@ -298,12 +329,36 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
	if (!new_prop)
		return -ENOMEM;

	if (!prop)
		ret = of_changeset_add_property(&ovcs->cset, target_node,
	if (!prop) {
		check_for_non_overlay_node = true;
		if (!target->in_livetree) {
			new_prop->next = target->np->deadprops;
			target->np->deadprops = new_prop;
		}
		ret = of_changeset_add_property(&ovcs->cset, target->np,
						new_prop);
	else
		ret = of_changeset_update_property(&ovcs->cset, target_node,
	} else if (!of_prop_cmp(prop->name, "#address-cells")) {
		if (!of_prop_val_eq(prop, new_prop)) {
			pr_err("ERROR: changing value of #address-cells is not allowed in %pOF\n",
			       target->np);
			ret = -EINVAL;
		}
	} else if (!of_prop_cmp(prop->name, "#size-cells")) {
		if (!of_prop_val_eq(prop, new_prop)) {
			pr_err("ERROR: changing value of #size-cells is not allowed in %pOF\n",
			       target->np);
			ret = -EINVAL;
		}
	} else {
		check_for_non_overlay_node = true;
		ret = of_changeset_update_property(&ovcs->cset, target->np,
						   new_prop);
	}

	if (check_for_non_overlay_node &&
	    !of_node_check_flag(target->np, OF_OVERLAY))
		pr_err("WARNING: memory leak will occur if overlay removed, property: %pOF/%s\n",
		       target->np, new_prop->name);

	if (ret) {
		kfree(new_prop->name);
@@ -316,13 +371,13 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
/**
 * add_changeset_node() - add @node (and children) to overlay changeset
 * @ovcs:	overlay changeset
 * @target_node:	where to place @node in live tree
 * @target:	where @node will be placed in live tree or changeset
 * @node:	node from within overlay device tree fragment
 *
 * If @node does not already exist in @target_node, add changeset entry
 * to add @node in @target_node.
 * If @node does not already exist in @target, add changeset entry
 * to add @node in @target.
 *
 * If @node already exists in @target_node, and the existing node has
 * If @node already exists in @target, and the existing node has
 * a phandle, the overlay node is not allowed to have a phandle.
 *
 * If @node has child nodes, add the children recursively via
@@ -342,49 +397,65 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
 *       a live devicetree created from Open Firmware.
 *
 * NOTE_2: Multiple mods of created nodes not supported.
 *       If more than one fragment contains a node that does not already exist
 *       in the live tree, then for each fragment of_changeset_attach_node()
 *       will add a changeset entry to add the node.  When the changeset is
 *       applied, __of_attach_node() will attach the node twice (once for
 *       each fragment).  At this point the device tree will be corrupted.
 *
 *       TODO: add integrity check to ensure that multiple fragments do not
 *             create the same node.
 *
 * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
 * invalid @overlay.
 */
static int add_changeset_node(struct overlay_changeset *ovcs,
		struct device_node *target_node, struct device_node *node)
		struct target *target, struct device_node *node)
{
	const char *node_kbasename;
	const __be32 *phandle;
	struct device_node *tchild;
	int ret = 0;
	struct target target_child;
	int ret = 0, size;

	node_kbasename = kbasename(node->full_name);

	for_each_child_of_node(target_node, tchild)
	for_each_child_of_node(target->np, tchild)
		if (!of_node_cmp(node_kbasename, kbasename(tchild->full_name)))
			break;

	if (!tchild) {
		tchild = __of_node_dup(node, node_kbasename);
		tchild = __of_node_dup(NULL, node_kbasename);
		if (!tchild)
			return -ENOMEM;

		tchild->parent = target_node;
		tchild->parent = target->np;
		tchild->name = __of_get_property(node, "name", NULL);
		tchild->type = __of_get_property(node, "device_type", NULL);

		if (!tchild->name)
			tchild->name = "<NULL>";
		if (!tchild->type)
			tchild->type = "<NULL>";

		/* ignore obsolete "linux,phandle" */
		phandle = __of_get_property(node, "phandle", &size);
		if (phandle && (size == 4))
			tchild->phandle = be32_to_cpup(phandle);

		of_node_set_flag(tchild, OF_OVERLAY);

		ret = of_changeset_attach_node(&ovcs->cset, tchild);
		if (ret)
			return ret;

		return build_changeset_next_level(ovcs, tchild, node);
		target_child.np = tchild;
		target_child.in_livetree = false;

		ret = build_changeset_next_level(ovcs, &target_child, node);
		of_node_put(tchild);
		return ret;
	}

	if (node->phandle && tchild->phandle)
	if (node->phandle && tchild->phandle) {
		ret = -EINVAL;
	else
		ret = build_changeset_next_level(ovcs, tchild, node);
	} else {
		target_child.np = tchild;
		target_child.in_livetree = target->in_livetree;
		ret = build_changeset_next_level(ovcs, &target_child, node);
	}
	of_node_put(tchild);

	return ret;
@@ -393,7 +464,7 @@ static int add_changeset_node(struct overlay_changeset *ovcs,
/**
 * build_changeset_next_level() - add level of overlay changeset
 * @ovcs:		overlay changeset
 * @target_node:	where to place @overlay_node in live tree
 * @target:		where to place @overlay_node in live tree
 * @overlay_node:	node from within an overlay device tree fragment
 *
 * Add the properties (if any) and nodes (if any) from @overlay_node to the
@@ -406,27 +477,26 @@ static int add_changeset_node(struct overlay_changeset *ovcs,
 * invalid @overlay_node.
 */
static int build_changeset_next_level(struct overlay_changeset *ovcs,
		struct device_node *target_node,
		const struct device_node *overlay_node)
		struct target *target, const struct device_node *overlay_node)
{
	struct device_node *child;
	struct property *prop;
	int ret;

	for_each_property_of_node(overlay_node, prop) {
		ret = add_changeset_property(ovcs, target_node, prop, 0);
		ret = add_changeset_property(ovcs, target, prop, 0);
		if (ret) {
			pr_debug("Failed to apply prop @%pOF/%s, err=%d\n",
				 target_node, prop->name, ret);
				 target->np, prop->name, ret);
			return ret;
		}
	}

	for_each_child_of_node(overlay_node, child) {
		ret = add_changeset_node(ovcs, target_node, child);
		ret = add_changeset_node(ovcs, target, child);
		if (ret) {
			pr_debug("Failed to apply node @%pOF/%pOFn, err=%d\n",
				 target_node, child, ret);
				 target->np, child, ret);
			of_node_put(child);
			return ret;
		}
@@ -439,17 +509,17 @@ static int build_changeset_next_level(struct overlay_changeset *ovcs,
 * Add the properties from __overlay__ node to the @ovcs->cset changeset.
 */
static int build_changeset_symbols_node(struct overlay_changeset *ovcs,
		struct device_node *target_node,
		struct target *target,
		const struct device_node *overlay_symbols_node)
{
	struct property *prop;
	int ret;

	for_each_property_of_node(overlay_symbols_node, prop) {
		ret = add_changeset_property(ovcs, target_node, prop, 1);
		ret = add_changeset_property(ovcs, target, prop, 1);
		if (ret) {
			pr_debug("Failed to apply prop @%pOF/%s, err=%d\n",
				 target_node, prop->name, ret);
			pr_debug("Failed to apply symbols prop @%pOF/%s, err=%d\n",
				 target->np, prop->name, ret);
			return ret;
		}
	}
@@ -457,6 +527,98 @@ static int build_changeset_symbols_node(struct overlay_changeset *ovcs,
	return 0;
}

static int find_dup_cset_node_entry(struct overlay_changeset *ovcs,
		struct of_changeset_entry *ce_1)
{
	struct of_changeset_entry *ce_2;
	char *fn_1, *fn_2;
	int node_path_match;

	if (ce_1->action != OF_RECONFIG_ATTACH_NODE &&
	    ce_1->action != OF_RECONFIG_DETACH_NODE)
		return 0;

	ce_2 = ce_1;
	list_for_each_entry_continue(ce_2, &ovcs->cset.entries, node) {
		if ((ce_2->action != OF_RECONFIG_ATTACH_NODE &&
		     ce_2->action != OF_RECONFIG_DETACH_NODE) ||
		    of_node_cmp(ce_1->np->full_name, ce_2->np->full_name))
			continue;

		fn_1 = kasprintf(GFP_KERNEL, "%pOF", ce_1->np);
		fn_2 = kasprintf(GFP_KERNEL, "%pOF", ce_2->np);
		node_path_match = !strcmp(fn_1, fn_2);
		kfree(fn_1);
		kfree(fn_2);
		if (node_path_match) {
			pr_err("ERROR: multiple fragments add and/or delete node %pOF\n",
			       ce_1->np);
			return -EINVAL;
		}
	}

	return 0;
}

static int find_dup_cset_prop(struct overlay_changeset *ovcs,
		struct of_changeset_entry *ce_1)
{
	struct of_changeset_entry *ce_2;
	char *fn_1, *fn_2;
	int node_path_match;

	if (ce_1->action != OF_RECONFIG_ADD_PROPERTY &&
	    ce_1->action != OF_RECONFIG_REMOVE_PROPERTY &&
	    ce_1->action != OF_RECONFIG_UPDATE_PROPERTY)
		return 0;

	ce_2 = ce_1;
	list_for_each_entry_continue(ce_2, &ovcs->cset.entries, node) {
		if ((ce_2->action != OF_RECONFIG_ADD_PROPERTY &&
		     ce_2->action != OF_RECONFIG_REMOVE_PROPERTY &&
		     ce_2->action != OF_RECONFIG_UPDATE_PROPERTY) ||
		    of_node_cmp(ce_1->np->full_name, ce_2->np->full_name))
			continue;

		fn_1 = kasprintf(GFP_KERNEL, "%pOF", ce_1->np);
		fn_2 = kasprintf(GFP_KERNEL, "%pOF", ce_2->np);
		node_path_match = !strcmp(fn_1, fn_2);
		kfree(fn_1);
		kfree(fn_2);
		if (node_path_match &&
		    !of_prop_cmp(ce_1->prop->name, ce_2->prop->name)) {
			pr_err("ERROR: multiple fragments add, update, and/or delete property %pOF/%s\n",
			       ce_1->np, ce_1->prop->name);
			return -EINVAL;
		}
	}

	return 0;
}

/**
 * changeset_dup_entry_check() - check for duplicate entries
 * @ovcs:	Overlay changeset
 *
 * Check changeset @ovcs->cset for multiple {add or delete} node entries for
 * the same node or duplicate {add, delete, or update} properties entries
 * for the same property.
 *
 * Returns 0 on success, or -EINVAL if duplicate changeset entry found.
 */
static int changeset_dup_entry_check(struct overlay_changeset *ovcs)
{
	struct of_changeset_entry *ce_1;
	int dup_entry = 0;

	list_for_each_entry(ce_1, &ovcs->cset.entries, node) {
		dup_entry |= find_dup_cset_node_entry(ovcs, ce_1);
		dup_entry |= find_dup_cset_prop(ovcs, ce_1);
	}

	return dup_entry ? -EINVAL : 0;
}

/**
 * build_changeset() - populate overlay changeset in @ovcs from @ovcs->fragments
 * @ovcs:	Overlay changeset
@@ -472,6 +634,7 @@ static int build_changeset_symbols_node(struct overlay_changeset *ovcs,
static int build_changeset(struct overlay_changeset *ovcs)
{
	struct fragment *fragment;
	struct target target;
	int fragments_count, i, ret;

	/*
@@ -486,25 +649,32 @@ static int build_changeset(struct overlay_changeset *ovcs)
	for (i = 0; i < fragments_count; i++) {
		fragment = &ovcs->fragments[i];

		ret = build_changeset_next_level(ovcs, fragment->target,
		target.np = fragment->target;
		target.in_livetree = true;
		ret = build_changeset_next_level(ovcs, &target,
						 fragment->overlay);
		if (ret) {
			pr_debug("apply failed '%pOF'\n", fragment->target);
			pr_debug("fragment apply failed '%pOF'\n",
				 fragment->target);
			return ret;
		}
	}

	if (ovcs->symbols_fragment) {
		fragment = &ovcs->fragments[ovcs->count - 1];
		ret = build_changeset_symbols_node(ovcs, fragment->target,

		target.np = fragment->target;
		target.in_livetree = true;
		ret = build_changeset_symbols_node(ovcs, &target,
						   fragment->overlay);
		if (ret) {
			pr_debug("apply failed '%pOF'\n", fragment->target);
			pr_debug("symbols fragment apply failed '%pOF'\n",
				 fragment->target);
			return ret;
		}
	}

	return 0;
	return changeset_dup_entry_check(ovcs);
}

/*
@@ -514,7 +684,7 @@ static int build_changeset(struct overlay_changeset *ovcs)
 * 1) "target" property containing the phandle of the target
 * 2) "target-path" property containing the path of the target
 */
static struct device_node *find_target_node(struct device_node *info_node)
static struct device_node *find_target(struct device_node *info_node)
{
	struct device_node *node;
	const char *path;
@@ -620,7 +790,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,

		fragment = &fragments[cnt];
		fragment->overlay = overlay_node;
		fragment->target = find_target_node(node);
		fragment->target = find_target(node);
		if (!fragment->target) {
			of_node_put(fragment->overlay);
			ret = -EINVAL;
@@ -808,7 +978,7 @@ static int of_overlay_apply(const void *fdt, struct device_node *tree,

	ret = __of_changeset_apply_notify(&ovcs->cset);
	if (ret)
		pr_err("overlay changeset entry notify error %d\n", ret);
		pr_err("overlay apply changeset entry notify error %d\n", ret);
	/* notify failure is not fatal, continue */

	list_add_tail(&ovcs->ovcs_list, &ovcs_list);
@@ -1067,7 +1237,7 @@ int of_overlay_remove(int *ovcs_id)

	ret = __of_changeset_revert_notify(&ovcs->cset);
	if (ret)
		pr_err("overlay changeset entry notify error %d\n", ret);
		pr_err("overlay remove changeset entry notify error %d\n", ret);
	/* notify failure is not fatal, continue */

	*ovcs_id = 0;
+2 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
			    overlay_12.dtb.o \
			    overlay_13.dtb.o \
			    overlay_15.dtb.o \
			    overlay_bad_add_dup_node.dtb.o \
			    overlay_bad_add_dup_prop.dtb.o \
			    overlay_bad_phandle.dtb.o \
			    overlay_bad_symbol.dtb.o \
			    overlay_base.dtb.o
Loading