Workaround Gitiles bug on All-Users visibility

Gitiles has special FilteredRepository wrapper that
allows to carefully hide refs based on the project's ACLs.
There is however an optimisation that skips the filtering
in case a user has READ permissions on every ACLs patterns.

When the target repository is All-Users, the optimisation
turns into a security issue because it allows seeing everything
that belongs to everyone:
- draft comments
- PII of all users
- external ids
- draft edits

Block Gitiles or any other part of Gerrit to abuse of this
power when the target repository is All-Users, where nobody
can be authorised to skip the ACLs evaluation.

Cover the additional special case of the All-Users project
access with two explicit positive and negative tests,
so that the security check is covered.

Bug: Issue 13621
Change-Id: Ia6ea1a9fd5473adff534204aea7d8f25324a45b7
(cherry picked from commit 45071d6977)
This commit is contained in:
Luca Milanesio
2020-11-13 18:44:29 +00:00
parent e65ce8f8f7
commit 1be1d6ff45
2 changed files with 44 additions and 2 deletions

View File

@@ -39,6 +39,7 @@ import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupMembership; import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GitReceivePackGroups; import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups; import com.google.gerrit.server.config.GitUploadPackGroups;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
@@ -140,6 +141,7 @@ public class ProjectControl {
private final RefVisibilityControl refVisibilityControl; private final RefVisibilityControl refVisibilityControl;
private final VisibleRefFilter.Factory visibleRefFilterFactory; private final VisibleRefFilter.Factory visibleRefFilterFactory;
private final GitRepositoryManager gitRepositoryManager; private final GitRepositoryManager gitRepositoryManager;
private final AllUsersName allUsersName;
private List<SectionMatcher> allSections; private List<SectionMatcher> allSections;
private Map<String, RefControl> refControls; private Map<String, RefControl> refControls;
@@ -156,6 +158,7 @@ public class ProjectControl {
RefVisibilityControl refVisibilityControl, RefVisibilityControl refVisibilityControl,
GitRepositoryManager gitRepositoryManager, GitRepositoryManager gitRepositoryManager,
VisibleRefFilter.Factory visibleRefFilterFactory, VisibleRefFilter.Factory visibleRefFilterFactory,
AllUsersName allUsersName,
@Assisted CurrentUser who, @Assisted CurrentUser who,
@Assisted ProjectState ps) { @Assisted ProjectState ps) {
this.changeControlFactory = changeControlFactory; this.changeControlFactory = changeControlFactory;
@@ -167,6 +170,7 @@ public class ProjectControl {
this.refVisibilityControl = refVisibilityControl; this.refVisibilityControl = refVisibilityControl;
this.gitRepositoryManager = gitRepositoryManager; this.gitRepositoryManager = gitRepositoryManager;
this.visibleRefFilterFactory = visibleRefFilterFactory; this.visibleRefFilterFactory = visibleRefFilterFactory;
this.allUsersName = allUsersName;
user = who; user = who;
state = ps; state = ps;
} }
@@ -262,7 +266,9 @@ public class ProjectControl {
} }
private boolean allRefsAreVisible(Set<String> ignore) { private boolean allRefsAreVisible(Set<String> ignore) {
return user.isInternalUser() || canPerformOnAllRefs(Permission.READ, ignore); return user.isInternalUser()
|| (!getProject().getNameKey().equals(allUsersName)
&& canPerformOnAllRefs(Permission.READ, ignore));
} }
/** Returns whether the project is hidden. */ /** Returns whether the project is hidden. */

View File

@@ -61,6 +61,7 @@ import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.VisibleRefFilter; import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener; import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission; import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission; import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.permissions.RefVisibilityControl; import com.google.gerrit.server.permissions.RefVisibilityControl;
@@ -110,6 +111,16 @@ public class RefControlTest {
assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse(); assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
} }
private void assertAllRefsAreVisible(ProjectControl u) throws PermissionBackendException {
assertThat(u.asForProject().test(ProjectPermission.READ)).named("all refs visible").isTrue();
}
private void assertAllRefsAreNotVisible(ProjectControl u) throws PermissionBackendException {
assertThat(u.asForProject().test(ProjectPermission.READ))
.named("all refs NOT visible")
.isFalse();
}
private void assertCanAccess(ProjectControl u) { private void assertCanAccess(ProjectControl u) {
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS); boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
assertThat(access).named("can access").isTrue(); assertThat(access).named("can access").isTrue();
@@ -199,6 +210,7 @@ public class RefControlTest {
private final Map<Project.NameKey, ProjectState> all = new HashMap<>(); private final Map<Project.NameKey, ProjectState> all = new HashMap<>();
private Project.NameKey localKey = new Project.NameKey("local"); private Project.NameKey localKey = new Project.NameKey("local");
private ProjectConfig local; private ProjectConfig local;
private ProjectConfig allUsers;
private Project.NameKey parentKey = new Project.NameKey("parent"); private Project.NameKey parentKey = new Project.NameKey("parent");
private ProjectConfig parent; private ProjectConfig parent;
private InMemoryRepositoryManager repoManager; private InMemoryRepositoryManager repoManager;
@@ -230,7 +242,7 @@ public class RefControlTest {
@Override @Override
public ProjectState getAllUsers() { public ProjectState getAllUsers() {
return null; return get(allUsersName);
} }
@Override @Override
@@ -290,6 +302,11 @@ public class RefControlTest {
LabelType cr = Util.codeReview(); LabelType cr = Util.codeReview();
allProjects.getLabelSections().put(cr.getName(), cr); allProjects.getLabelSections().put(cr.getName(), cr);
add(allProjects); add(allProjects);
Repository allUsersRepo = repoManager.createRepository(allUsersName);
allUsers = new ProjectConfig(new Project.NameKey(allUsersName.get()));
allUsers.load(allUsersRepo);
add(allUsers);
} catch (IOException | ConfigInvalidException e) { } catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -363,6 +380,24 @@ public class RefControlTest {
assertAdminsAreOwnersAndDevsAreNot(); assertAdminsAreOwnersAndDevsAreNot();
} }
@Test
public void allRefsAreVisibleForRegularProject() throws Exception {
allow(local, READ, DEVS, "refs/*");
allow(local, READ, DEVS, "refs/groups/*");
allow(local, READ, DEVS, "refs/users/default");
assertAllRefsAreVisible(user(local, DEVS));
}
@Test
public void allRefsAreNotVisibleForAllUsers() throws Exception {
allow(allUsers, READ, DEVS, "refs/*");
allow(allUsers, READ, DEVS, "refs/groups/*");
allow(allUsers, READ, DEVS, "refs/users/default");
assertAllRefsAreNotVisible(user(allUsers, DEVS));
}
@Test @Test
public void branchDelegation1() { public void branchDelegation1() {
allow(local, OWNER, ADMIN, "refs/*"); allow(local, OWNER, ADMIN, "refs/*");
@@ -897,6 +932,7 @@ public class RefControlTest {
refVisibilityControl, refVisibilityControl,
gitRepositoryManager, gitRepositoryManager,
visibleRefFilterFactory, visibleRefFilterFactory,
allUsersName,
new MockUser(name, memberOf), new MockUser(name, memberOf),
newProjectState(local)); newProjectState(local));
} }