Skip to content

Commit

Permalink
Merge pull request #8036 from subhash-arabhi/timecap-on-pac-script
Browse files Browse the repository at this point in the history
Timecap on execution of PAC script
  • Loading branch information
lahodaj authored Feb 24, 2025
2 parents b2bdb81 + 35f11fb commit 4de8ce2
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
Expand All @@ -46,6 +47,9 @@
import org.netbeans.core.network.proxy.pac.PacUtils;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.RequestProcessor.Task;
import org.netbeans.core.ProxySettings;

/**
* NetBeans implementation of a PAC script evaluator. This implementation
Expand Down Expand Up @@ -196,6 +200,7 @@ public class NbPacScriptEvaluator implements PacScriptEvaluator {
private static final String PAC_SOCKS5_FFEXT = "SOCKS5"; // Mozilla Firefox extension. Not part of original Netscape spec.
private static final String PAC_HTTP_FFEXT = "HTTP"; // Mozilla Firefox extension. Not part of original Netscape spec.
private static final String PAC_HTTPS_FFEXT = "HTTPS"; // Mozilla Firefox extension. Not part of original Netscape spec.
private static final RequestProcessor RP = new RequestProcessor(NbPacScriptEvaluator.class.getName(), Runtime.getRuntime().availableProcessors(), true, false);
private final String pacScriptSource;


Expand All @@ -213,7 +218,7 @@ public NbPacScriptEvaluator(String pacSourceCocde) throws PacParsingException {
@Override
public List<Proxy> findProxyForURL(URI uri) throws PacValidationException {

List<Proxy> jsResultAnalyzed;
List<Proxy> jsResultAnalyzed = null;

// First try the cache
if (resultCache != null) {
Expand All @@ -222,38 +227,37 @@ public List<Proxy> findProxyForURL(URI uri) throws PacValidationException {
return jsResultAnalyzed;
}
}
try {
Object jsResult;
synchronized (scriptEngine) {
jsResult = scriptEngine.findProxyForURL(PacUtils.toStrippedURLStr(uri), uri.getHost());
}
jsResultAnalyzed = analyzeResult(uri, jsResult);
if (canUseURLCaching && (resultCache != null)) {
resultCache.put(uri, jsResultAnalyzed); // save the result in the cache
}
return jsResultAnalyzed;
} catch (NoSuchMethodException ex) {
// If this exception occur at this time it is really, really unexpected.
// We already gave the function a test spin in the constructor.
Exceptions.printStackTrace(ex);
return Collections.singletonList(Proxy.NO_PROXY);
} catch (ScriptException ex) {
LOGGER.log(Level.WARNING, "Error when executing PAC script function " + scriptEngine.getJsMainFunction().getJsFunctionName() + " : ", ex);
return Collections.singletonList(Proxy.NO_PROXY);
} catch (Exception ex) { // for runtime exceptions
if (ex.getCause() != null) {
if (ex.getCause() instanceof ClassNotFoundException) {
// Is someone trying to break out of the sandbox ?
LOGGER.log(Level.WARNING, "The downloaded PAC script is attempting to access Java class ''{0}'' which may be a sign of maliciousness. You should investigate this with your network administrator.", ex.getCause().getMessage());
return Collections.singletonList(Proxy.NO_PROXY);

int timeout = ProxySettings.getPacScriptTimeout();

if (timeout <= 0){
jsResultAnalyzed = executeProxyScript(uri);
} else {
AtomicReference<List<Proxy>> resultHolder = new AtomicReference<>(null);
Task task = RP.post(() -> {
resultHolder.set(executeProxyScript(uri));
});

try{
if(!task.waitFinished(timeout)){
LOGGER.log(Level.WARNING, "Timeout when executing PAC script function: {0}", scriptEngine.getJsMainFunction().getJsFunctionName());
}
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "PAC script execution interrupted: {0}", ex);
} finally {
if (!task.isFinished()) {
// interruptThread is set true for the RequestProcessor so cancel will interrupt without any setting
task.cancel();
}
}
// other unforseen errors
LOGGER.log(Level.WARNING, "Error when executing PAC script function " + scriptEngine.getJsMainFunction().getJsFunctionName() + " : ", ex);
return Collections.singletonList(Proxy.NO_PROXY);
jsResultAnalyzed = resultHolder.get();
}
if (canUseURLCaching && (resultCache != null) && (jsResultAnalyzed != null)) {
resultCache.put(uri, jsResultAnalyzed); // save the result in the cache
}
return jsResultAnalyzed != null ? jsResultAnalyzed : Collections.singletonList(Proxy.NO_PROXY);
}

@Override
public boolean usesCaching() {
return (canUseURLCaching && (resultCache != null));
Expand All @@ -275,6 +279,32 @@ public String getPacScriptSource() {
return this.pacScriptSource;
}

private List<Proxy> executeProxyScript(URI uri) {
try{
Object jsResult;
synchronized (scriptEngine) {
jsResult = scriptEngine.findProxyForURL(PacUtils.toStrippedURLStr(uri), uri.getHost());
}
return analyzeResult(uri, jsResult);

} catch (NoSuchMethodException ex) {
// If this exception occur at this time it is really, really unexpected.
// We already gave the function a test spin in the constructor.
Exceptions.printStackTrace(ex);
} catch (ScriptException ex) {
LOGGER.log(Level.WARNING, "Error when executing PAC script function " + scriptEngine.getJsMainFunction().getJsFunctionName() + " : ", ex);
} catch (Exception ex) { // for runtime exceptions
if (ex.getCause() != null) {
if (ex.getCause() instanceof ClassNotFoundException) {
// Is someone trying to break out of the sandbox ?
LOGGER.log(Level.WARNING, "The downloaded PAC script is attempting to access Java class ''{0}'' which may be a sign of maliciousness. You should investigate this with your network administrator.", ex.getCause().getMessage());
}
}
// other unforseen errors
LOGGER.log(Level.WARNING, "Error when executing PAC script function " + scriptEngine.getJsMainFunction().getJsFunctionName() + " : ", ex);
}
return null;
}


private PacScriptEngine getScriptEngine(String pacSource) throws PacParsingException {
Expand Down
30 changes: 30 additions & 0 deletions platform/core.network/test/unit/data/pacFiles2/pac-test-timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.
*/




//
// A PAC script which takes long time to execute and wastes cpu resources
//

function FindProxyForURL(url, host)
{
alert("pac-test-timeout.js");
const repeatedA = "A".repeat(999);
while(true){
console.log(repeatedA);
}
return "DIRECT";
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.netbeans.core.ProxySettings;
import static org.netbeans.core.ProxySettings.PAC_SCRIPT_TIMEOUT;
import org.netbeans.core.network.proxy.pac.impl.NbPacScriptEvaluatorFactory;
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbTestCase;
import org.openide.util.NbPreferences;

/**
*
Expand Down Expand Up @@ -73,6 +76,9 @@ public static final junit.framework.Test suite() {
@Test
public void testEngine() throws PacParsingException, IOException, URISyntaxException, PacValidationException {
System.out.println("toSemiColonListStr");

NbPreferences.forModule(ProxySettings.class)
.putInt(PAC_SCRIPT_TIMEOUT, 2000);

PacScriptEvaluatorFactory factory = new NbPacScriptEvaluatorFactory();

Expand All @@ -81,6 +87,7 @@ public void testEngine() throws PacParsingException, IOException, URISyntaxExcep
testPacFile("pac-test3.js", factory, 1, false);
testPacFileMalicious("pac-test-sandbox-breakout.js", factory);
testPacFileMalicious("pac-test-getclass.js", factory);
testPacFileMalicious("pac-test-timeout.js", factory);

testPacFile2("pac-test4.js", factory);
}
Expand Down
6 changes: 6 additions & 0 deletions platform/o.n.core/src/org/netbeans/core/ProxySettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class ProxySettings {
public static final String USE_PROXY_ALL_PROTOCOLS = "useProxyAllProtocols"; // NOI18N
public static final String DIRECT = "DIRECT"; // NOI18N
public static final String PAC = "PAC"; // NOI18N
public static final String PAC_SCRIPT_TIMEOUT = "pacScriptTimeout"; // NOI18N
public static final int DEFAULT_TIMEOUT = 10000;

public static final String SYSTEM_PROXY_HTTP_HOST = "systemProxyHttpHost"; // NOI18N
public static final String SYSTEM_PROXY_HTTP_PORT = "systemProxyHttpPort"; // NOI18N
Expand Down Expand Up @@ -141,6 +143,10 @@ public static int getProxyType () {
return type;
}

public static int getPacScriptTimeout() {
return getPreferences ()
.getInt(PAC_SCRIPT_TIMEOUT, DEFAULT_TIMEOUT);
}

public static String getSystemHttpHost() {
return getPreferences().get(SYSTEM_PROXY_HTTP_HOST, "");
Expand Down

0 comments on commit 4de8ce2

Please sign in to comment.