[JGIT PATCH] Fix data corruption in DirCacheIterator when EmptyTreeIterator is used

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

 



When a DirCacheIterator was wrapped in an EmptyTreeIterator (such
as during a parallel TreeWalk where the other trees contain a
path that does not appear in the DirCachIterator) we corrupted
the DirCacheEntry path buffers by overwriting part of file name
components with '/' to match the other tree iterators' path length.

This happened because DirCacheIterator violated the iterator
assumption that the path buffer is always mutable.  Instead of
creating a mutable path, DirCacheIterator reused the path buffer
from the DirCacheEntry or the DirCacheTree.  These reused byte
arrays aren't mutable.

By delegating the EmptyTreeIterator creation to each iterator type
we can permit DirCacheIterator to control how it builds the empty
tree for the caller, ensuring that it copies the path buffer before
writing the '/' suffix onto it.

When EmptyTreeIterator has to grow the path buffer to create a new
iterator around itself, we can't just blindly replace every parent
iterator buffer with the larger path buffer.  DirCacheIterators will
be using a different path buffer, and they want to retain their
old path buffer, not the new larger buffer.

Noticed-by: Mark Struberg <struberg@xxxxxxxx>
Signed-off-by: Shawn O. Pearce <spearce@xxxxxxxxxxx>
---

 So uhm, test cases...  I can't come up with a simple test case
 which causes the data corruption, but Mark found a very complex
 one in egit.git.

 Now that I understand what the heck was wrong, its obvious we
 need to do this, as DirCacheIterator was breaking the contract.

 I'd love to have a test case, but I just spent an hour trying to
 come up with one and failed.  I'd rather have the data corruption
 fix without tests than not at all, since its obviously wrong as-is.
 But if you want a test, propose one.  I'm just not seeing the
 magic necessary to get the corruption to trigger.

 .../spearce/jgit/dircache/DirCacheIterator.java    |    9 ++++++++
 .../jgit/treewalk/AbstractTreeIterator.java        |   19 +++++++++++++---
 .../spearce/jgit/treewalk/EmptyTreeIterator.java   |   22 ++++++++++++++++++++
 .../src/org/spearce/jgit/treewalk/TreeWalk.java    |    2 +-
 4 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java
index 356d735..6fb9510 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java
@@ -45,6 +45,7 @@
 import org.spearce.jgit.lib.FileMode;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.treewalk.AbstractTreeIterator;
+import org.spearce.jgit.treewalk.EmptyTreeIterator;
 
 /**
  * Iterate a {@link DirCache} as part of a <code>TreeWalk</code>.
@@ -126,6 +127,14 @@ public AbstractTreeIterator createSubtreeIterator(final Repository repo)
 	}
 
 	@Override
+	public EmptyTreeIterator createEmptyTreeIterator() {
+		final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)];
+		System.arraycopy(path, 0, n, 0, pathLen);
+		n[pathLen] = '/';
+		return new EmptyTreeIterator(this, n, pathLen + 1);
+	}
+
+	@Override
 	public byte[] idBuffer() {
 		if (currentSubtree != null)
 			return subtreeId;
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java
index 2ff3b99..057250e 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java
@@ -73,7 +73,8 @@
  * @see CanonicalTreeParser
  */
 public abstract class AbstractTreeIterator {
-	static final int DEFAULT_PATH_SIZE = 128;
+	/** Default size for the {@link #path} buffer. */
+	protected static final int DEFAULT_PATH_SIZE = 128;
 
 	/** A dummy object id buffer that matches the zero ObjectId. */
 	protected static final byte[] zeroid = new byte[Constants.OBJECT_ID_LENGTH];
@@ -251,9 +252,10 @@ protected AbstractTreeIterator(final AbstractTreeIterator p,
 	 *            be moved into the larger buffer.
 	 */
 	protected void growPath(final int len) {
-		final byte[] n = new byte[path.length << 1];
-		System.arraycopy(path, 0, n, 0, len);
-		for (AbstractTreeIterator p = this; p != null; p = p.parent)
+		final byte[] o = path;
+		final byte[] n = new byte[o.length << 1];
+		System.arraycopy(o, 0, n, 0, len);
+		for (AbstractTreeIterator p = this; p != null && p.path == o; p = p.parent)
 			p.path = n;
 	}
 
@@ -400,6 +402,15 @@ public abstract AbstractTreeIterator createSubtreeIterator(Repository repo)
 			throws IncorrectObjectTypeException, IOException;
 
 	/**
+	 * Create a new iterator as though the current entry were a subtree.
+	 *
+	 * @return a new empty tree iterator.
+	 */
+	public EmptyTreeIterator createEmptyTreeIterator() {
+		return new EmptyTreeIterator(this);
+	}
+
+	/**
 	 * Create a new iterator for the current entry's subtree.
 	 * <p>
 	 * The parent reference of the iterator must be <code>this</code>, otherwise
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java
index eaca04e..cc5ea99 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java
@@ -57,6 +57,28 @@ EmptyTreeIterator(final AbstractTreeIterator p) {
 		pathLen = pathOffset;
 	}
 
+	/**
+	 * Create an iterator for a subtree of an existing iterator.
+	 * <p>
+	 * The caller is responsible for setting up the path of the child iterator.
+	 *
+	 * @param p
+	 *            parent tree iterator.
+	 * @param childPath
+	 *            path array to be used by the child iterator. This path must
+	 *            contain the path from the top of the walk to the first child
+	 *            and must end with a '/'.
+	 * @param childPathOffset
+	 *            position within <code>childPath</code> where the child can
+	 *            insert its data. The value at
+	 *            <code>childPath[childPathOffset-1]</code> must be '/'.
+	 */
+	public EmptyTreeIterator(final AbstractTreeIterator p,
+			final byte[] childPath, final int childPathOffset) {
+		super(p, childPath, childPathOffset);
+		pathLen = childPathOffset - 1;
+	}
+
 	@Override
 	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
 			throws IncorrectObjectTypeException, IOException {
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
index a41ca58..250b213 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
@@ -800,7 +800,7 @@ public void enterSubtree() throws MissingObjectException,
 			if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode))
 				n = t.createSubtreeIterator(db, idBuffer, curs);
 			else
-				n = new EmptyTreeIterator(t);
+				n = t.createEmptyTreeIterator();
 			tmp[i] = n;
 		}
 		depth++;
-- 
1.6.3.48.g99c76

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]