Skip to content

Commit 7f556af

Browse files
authored
validate permutation strong name before building symbol map paths (#10328)
The permutation strong name comes straight from the client `X-GWT-Permutation` header and is concatenated into the symbol/source map file name that `openInputStream` resolves: resymbolize(trace, "../../../../etc/passwd") -> openInputStream("../../../../etc/passwd.symbolMap") `ServerSerializationStreamReader` already restricts the strong name to `[a-zA-Z0-9_]+` before loading the policy file; this applies the same check inside the symbol/source map loaders so an out-of-range name is treated as not found.
1 parent 032c794 commit 7f556af

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ Map<String, String> getAll(String strongName, Set<String> symbols) {
148148

149149
private static final Pattern JsniRefPattern = Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?");
150150
private static final Pattern fragmentIdPattern = Pattern.compile(".*(\\d+)\\.js");
151+
// Matches ServerSerializationStreamReader: the strong name reaches us straight from the
152+
// client (X-GWT-Permutation header) and is concatenated into symbol/source map file names.
153+
private static final Pattern strongNamePattern = Pattern.compile("[a-zA-Z0-9_]+");
151154
private static final int LINE_NUMBER_UNKNOWN = -1;
152155
private static final String SYMBOL_DATA_UNKNOWN = "";
153156

@@ -368,9 +371,13 @@ protected InputStream getSymbolMapInputStream(String permutationStrongName) thro
368371
*/
369372
protected abstract InputStream openInputStream(String fileName) throws IOException;
370373

374+
private static boolean isValidStrongName(String strongName) {
375+
return strongName != null && strongNamePattern.matcher(strongName).matches();
376+
}
377+
371378
private SourceMapping loadSourceMap(String permutationStrongName, int fragmentId) {
372379
SourceMapping toReturn = sourceMaps.get(permutationStrongName + fragmentId);
373-
if (toReturn == null) {
380+
if (toReturn == null && isValidStrongName(permutationStrongName)) {
374381
try {
375382
String sourceMapString = loadStreamAsString(
376383
getSourceMapInputStream(permutationStrongName, fragmentId));
@@ -410,6 +417,9 @@ private Map<String, String> loadSymbolMap(
410417
String line;
411418

412419
try {
420+
if (!isValidStrongName(strongName)) {
421+
throw new IOException("Invalid permutation strong name: " + strongName);
422+
}
413423
BufferedReader bin = new BufferedReader(
414424
new InputStreamReader(getSymbolMapInputStream(strongName)));
415425
try {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2026 GWT Project Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.gwt.core.server;
17+
18+
import junit.framework.TestCase;
19+
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
/**
26+
* Tests for {@link StackTraceDeobfuscator}.
27+
*/
28+
public class StackTraceDeobfuscatorTest extends TestCase {
29+
30+
private static class RecordingDeobfuscator extends StackTraceDeobfuscator {
31+
final List<String> opened = new ArrayList<String>();
32+
33+
@Override
34+
protected InputStream openInputStream(String fileName) throws IOException {
35+
opened.add(fileName);
36+
throw new IOException("no such resource: " + fileName);
37+
}
38+
}
39+
40+
private static StackTraceElement[] trace() {
41+
return new StackTraceElement[] {new StackTraceElement("C", "m", "C.java", 1)};
42+
}
43+
44+
public void testTraversalStrongNameIsNotUsedToBuildPath() {
45+
RecordingDeobfuscator d = new RecordingDeobfuscator();
46+
d.resymbolize(trace(), "../../../../../../etc/passwd");
47+
assertTrue("strong name with path separators must not reach openInputStream: " + d.opened,
48+
d.opened.isEmpty());
49+
}
50+
51+
public void testValidStrongNameStillLoadsSymbolMap() {
52+
RecordingDeobfuscator d = new RecordingDeobfuscator();
53+
d.resymbolize(trace(), "0F2C4A6E8B1D3F5709ABCDEF12345678");
54+
assertEquals("0F2C4A6E8B1D3F5709ABCDEF12345678.symbolMap", d.opened.get(0));
55+
}
56+
}

0 commit comments

Comments
 (0)