Browse Source

talloc: fixed a use after free error

(Import from SAMBA commit 6f51a1f45bf4de062cce7a562477e8140630a53d):

this is the minimal fix for the problem Rusty found. I previously
thought that the best fix would be to change tc->parent to be valid
for all pointers, but that is expensive for realloc with large numbers
of child pointers, which is much more commmon than I expected it to
be.
Rusty Russell 15 years ago
parent
commit
1e962ba9b5
1 changed files with 16 additions and 1 deletions
  1. 16 1
      ccan/talloc/talloc.c

+ 16 - 1
ccan/talloc/talloc.c

@@ -538,13 +538,28 @@ static inline int _talloc_free(const void *ptr)
 		   final choice is the null context. */
 		   final choice is the null context. */
 		void *child = TC_PTR_FROM_CHUNK(tc->child);
 		void *child = TC_PTR_FROM_CHUNK(tc->child);
 		const void *new_parent = null_context;
 		const void *new_parent = null_context;
+		struct talloc_chunk *old_parent = NULL;
 		if (unlikely(tc->child->refs)) {
 		if (unlikely(tc->child->refs)) {
 			struct talloc_chunk *p = talloc_parent_chunk(tc->child->refs);
 			struct talloc_chunk *p = talloc_parent_chunk(tc->child->refs);
 			if (p) new_parent = TC_PTR_FROM_CHUNK(p);
 			if (p) new_parent = TC_PTR_FROM_CHUNK(p);
 		}
 		}
+		/* finding the parent here is potentially quite
+		   expensive, but the alternative, which is to change
+		   talloc to always have a valid tc->parent pointer,
+		   makes realloc more expensive where there are a
+		   large number of children.
+
+		   The reason we need the parent pointer here is that
+		   if _talloc_free_internal() fails due to references
+		   or a failing destructor we need to re-parent, but
+		   the free call can invalidate the prev pointer.
+		*/
+		if (new_parent == null_context && (tc->child->refs || tc->child->destructor)) {
+			old_parent = talloc_parent_chunk(ptr);
+		}
 		if (unlikely(_talloc_free(child) == -1)) {
 		if (unlikely(_talloc_free(child) == -1)) {
 			if (new_parent == null_context) {
 			if (new_parent == null_context) {
-				struct talloc_chunk *p = talloc_parent_chunk(ptr);
+				struct talloc_chunk *p = old_parent;
 				if (p) new_parent = TC_PTR_FROM_CHUNK(p);
 				if (p) new_parent = TC_PTR_FROM_CHUNK(p);
 			}
 			}
 			__talloc_steal(new_parent, child);
 			__talloc_steal(new_parent, child);