From 0f95d2d0462f8badd3cdb1fadb6dbb3fe84074b4 Mon Sep 17 00:00:00 2001
From: Nico Sallembien <nsallembien@google.com>
Date: Fri, 12 Mar 2010 16:07:19 -0800
Subject: [PATCH] Add a paranoid 'must be provided' option to ReceivePack

By default a receive pack assumes that its user will only provide
references to objects that the user already has access to on their
local client.  In certain cases, an additional check to verify the
references point only to reachable objects is necessary.

This additional checking is useful when the code doesn't trust
the client not to provide a forged SHA-1 reference to an object,
in an attempt to access parts of the DAG that they weren't allowed
to see by the configured RefFilter.

Change-Id: I3e4b8505cb2992e3e4be253abb14a1501e47b970
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../eclipse/jgit/transport/ReceivePack.java   | 58 ++++++++++++++++++-
 1 file changed, 55 insertions(+), 3 deletions(-)

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index dae28ab27..6ba326c00 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -70,6 +70,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
 import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
@@ -77,6 +78,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.Config.SectionParser;
 import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -184,6 +186,8 @@ public class ReceivePack {
 
 	private boolean needBaseObjectIds;
 
+	private boolean ensureObjectsProvidedVisible;
+
 	/**
 	 * Create a new pack receive for an open repository.
 	 *
@@ -288,6 +292,26 @@ public final Set<ObjectId> getNewObjectIds() {
 		return ip.getNewObjectIds();
 	}
 
+	/**
+	 * Configure this receive pack instance to ensure that the provided
+	 * objects are visible to the user.
+	 * <p>
+	 * By default, a receive pack assumes that its user will only provide
+	 * references to objects that it can see. Setting this flag to {@code true}
+	 * will add an additional check that verifies that the objects that were
+	 * provided are reachable by a tree or a commit that the user can see.
+	 * <p>
+	 * This option is useful when the code doesn't trust the client not to
+	 * provide a forged SHA-1 reference to an object in an attempt to access
+	 * parts of the DAG that they aren't allowed to see, via the configured
+	 * {@link RefFilter}.
+	 *
+	 * @param b {@code true} to enable the additional check.
+	 */
+	public void setEnsureProvidedObjectsVisible(boolean b) {
+		this.ensureObjectsProvidedVisible = b;
+	}
+
 	/**
 	 * @return true if this class expects a bi-directional pipe opened between
 	 *         the client and itself. The default is true.
@@ -777,8 +801,9 @@ private void receivePack() throws IOException {
 
 		ip = IndexPack.create(db, rawIn);
 		ip.setFixThin(true);
-		ip.setNeedNewObjectIds(needNewObjectIds);
-		ip.setNeedBaseObjectIds(needBaseObjectIds);
+		ip.setNeedNewObjectIds(needNewObjectIds || ensureObjectsProvidedVisible);
+		ip.setNeedBaseObjectIds(needBaseObjectIds
+				|| ensureObjectsProvidedVisible);
 		ip.setObjectChecking(isCheckReceivedObjects());
 		ip.index(NullProgressMonitor.INSTANCE);
 
@@ -802,7 +827,34 @@ private void checkConnectivity() throws IOException {
 		}
 		for (final Ref ref : refs.values())
 			ow.markUninteresting(ow.parseAny(ref.getObjectId()));
-		ow.checkConnectivity();
+
+		ObjectIdSubclassMap<ObjectId> provided =
+			new ObjectIdSubclassMap<ObjectId>();
+		if (ensureObjectsProvidedVisible) {
+			for (ObjectId id : getBaseObjectIds()) {
+				   RevObject b = ow.lookupAny(id, Constants.OBJ_BLOB);
+				   if (!b.has(RevFlag.UNINTERESTING))
+				     throw new MissingObjectException(b, b.getType());
+			}
+			for (ObjectId id : getNewObjectIds()) {
+				provided.add(id);
+			}
+		}
+
+		RevCommit c;
+		while ((c = ow.next()) != null) {
+			if (ensureObjectsProvidedVisible && !provided.contains(c))
+				throw new MissingObjectException(c, Constants.TYPE_COMMIT);
+		}
+
+		RevObject o;
+		while ((o = ow.nextObject()) != null) {
+			if (o instanceof RevBlob && !db.hasObject(o))
+				throw new MissingObjectException(o, Constants.TYPE_BLOB);
+
+			if (ensureObjectsProvidedVisible && !provided.contains(o))
+				throw new MissingObjectException(o, Constants.TYPE_BLOB);
+		}
 	}
 
 	private void validateCommands() {
-- 
GitLab