Do not detect file names starting `..` as having up-level references. `PathFragment::containsUplevelReferences` detects whether a path has an up-level reference (`..`). The logic looks at the beginning of the normalized path whether it starts with a `..`. This is incorrect for path segments which have a `..` prefix, but are not `..`, e.g. `..file`. This causes bazel to crash when we create a tree artifact with a file starting with a `..`. Fix the detection to only recognize paths with `..` segments. PiperOrigin-RevId: 351624019
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java b/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java index e4c290a..6704b2f 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java
@@ -613,8 +613,8 @@ * of the path fragment. */ public boolean containsUplevelReferences() { - // Path is normalized, so any ".." would have to be at the very start - return normalizedPath.startsWith(".."); + // Path is normalized, so any ".." would have to be the first segment. + return normalizedPath.equals("..") || normalizedPath.startsWith(".." + SEPARATOR_CHAR); } /**
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java index a94daf3..9b39d42 100644 --- a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java +++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
@@ -15,6 +15,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.lib.vfs.PathFragment.EMPTY_FRAGMENT; import static com.google.devtools.build.lib.vfs.PathFragment.create; import static org.junit.Assert.assertThrows; @@ -600,4 +601,43 @@ (PathFragment) TestUtils.fromBytes(new DeserializationContext(ImmutableMap.of()), sa); assertThat(a2).isEqualTo(a); } + + @Test + public void containsUplevelReference_emptyPath_returnsFalse() { + assertThat(EMPTY_FRAGMENT.containsUplevelReferences()).isFalse(); + } + + @Test + public void containsUplevelReference_uplevelOnlyPath_returnsTrue() { + PathFragment pathFragment = create(".."); + assertThat(pathFragment.containsUplevelReferences()).isTrue(); + } + + @Test + public void containsUplevelReferences_firstSegmentStartingWithDotDot_returnsFalse() { + PathFragment pathFragment = create("..file"); + assertThat(pathFragment.containsUplevelReferences()).isFalse(); + } + + @Test + public void containsUplevelReferences_startsWithUplevelReference_returnsTrue() { + PathFragment pathFragment = create("../file"); + assertThat(pathFragment.containsUplevelReferences()).isTrue(); + } + + @Test + public void containsUplevelReferences_uplevelReferenceMidPath_normalizesAndReturnsFalse() { + PathFragment pathFragment = create("a/../b"); + + assertThat(pathFragment.containsUplevelReferences()).isFalse(); + assertThat(pathFragment.getPathString()).isEqualTo("b"); + } + + @Test + public void containsUplevelReferenes_uplevelReferenceMidGlobalPath_normalizesAndReturnsFalse() { + PathFragment pathFragment = create("/dir1/dir2/../file"); + + assertThat(pathFragment.containsUplevelReferences()).isFalse(); + assertThat(pathFragment.getPathString()).isEqualTo("/dir1/file"); + } }