diff --git a/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java b/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java index 25d851c7c0c..0a7f61cc079 100644 --- a/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java +++ b/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java @@ -145,6 +145,9 @@ Map getAll(String strongName, Set symbols) { private static final Pattern JsniRefPattern = Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?"); private static final Pattern fragmentIdPattern = Pattern.compile(".*(\\d+)\\.js"); + // Matches ServerSerializationStreamReader: the strong name reaches us straight from the + // client (X-GWT-Permutation header) and is concatenated into symbol/source map file names. + private static final Pattern strongNamePattern = Pattern.compile("[a-zA-Z0-9_]+"); private static final int LINE_NUMBER_UNKNOWN = -1; private static final String SYMBOL_DATA_UNKNOWN = ""; @@ -365,9 +368,13 @@ protected InputStream getSymbolMapInputStream(String permutationStrongName) thro */ protected abstract InputStream openInputStream(String fileName) throws IOException; + private static boolean isValidStrongName(String strongName) { + return strongName != null && strongNamePattern.matcher(strongName).matches(); + } + private SourceMapping loadSourceMap(String permutationStrongName, int fragmentId) { SourceMapping toReturn = sourceMaps.get(permutationStrongName + fragmentId); - if (toReturn == null) { + if (toReturn == null && isValidStrongName(permutationStrongName)) { try { String sourceMapString = loadStreamAsString( getSourceMapInputStream(permutationStrongName, fragmentId)); @@ -407,6 +414,9 @@ private Map loadSymbolMap( String line; try { + if (!isValidStrongName(strongName)) { + throw new IOException("Invalid permutation strong name: " + strongName); + } BufferedReader bin = new BufferedReader( new InputStreamReader(getSymbolMapInputStream(strongName))); try { diff --git a/user/test/com/google/gwt/core/server/StackTraceDeobfuscatorTest.java b/user/test/com/google/gwt/core/server/StackTraceDeobfuscatorTest.java new file mode 100644 index 00000000000..5aac2ffc1db --- /dev/null +++ b/user/test/com/google/gwt/core/server/StackTraceDeobfuscatorTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.core.server; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link StackTraceDeobfuscator}. + */ +public class StackTraceDeobfuscatorTest extends TestCase { + + private static class RecordingDeobfuscator extends StackTraceDeobfuscator { + final List opened = new ArrayList(); + + @Override + protected InputStream openInputStream(String fileName) throws IOException { + opened.add(fileName); + throw new IOException("no such resource: " + fileName); + } + } + + private static StackTraceElement[] trace() { + return new StackTraceElement[] {new StackTraceElement("C", "m", "C.java", 1)}; + } + + public void testTraversalStrongNameIsNotUsedToBuildPath() { + RecordingDeobfuscator d = new RecordingDeobfuscator(); + d.resymbolize(trace(), "../../../../../../etc/passwd"); + assertTrue("strong name with path separators must not reach openInputStream: " + d.opened, + d.opened.isEmpty()); + } + + public void testValidStrongNameStillLoadsSymbolMap() { + RecordingDeobfuscator d = new RecordingDeobfuscator(); + d.resymbolize(trace(), "0F2C4A6E8B1D3F5709ABCDEF12345678"); + assertEquals("0F2C4A6E8B1D3F5709ABCDEF12345678.symbolMap", d.opened.get(0)); + } +}