Skip to content

Commit 691fb60

Browse files
committedMay 18, 2018
[JENKINS-51390] If an exception cannot be serialized for program.dat, replace it with a ProxyException.
1 parent 2c960e5 commit 691fb60

File tree

3 files changed

+187
-2
lines changed

3 files changed

+187
-2
lines changed
 

‎pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<properties>
6565
<revision>2.19</revision>
6666
<changelist>-SNAPSHOT</changelist>
67-
<jenkins.version>2.60.3</jenkins.version>
67+
<jenkins.version>2.62</jenkins.version>
6868
<java.level>8</java.level>
6969
<no-test-jar>false</no-test-jar>
7070
<git-plugin.version>3.7.0</git-plugin.version>
@@ -151,7 +151,7 @@
151151
<dependency>
152152
<groupId>org.jenkins-ci.plugins.workflow</groupId>
153153
<artifactId>workflow-job</artifactId>
154-
<version>2.12.2</version>
154+
<version>2.21</version>
155155
<scope>test</scope>
156156
</dependency>
157157
<dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2018 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.workflow.support.pickles;
26+
27+
import com.google.common.util.concurrent.ListenableFuture;
28+
import hudson.Extension;
29+
import hudson.Functions;
30+
import hudson.remoting.ProxyException;
31+
import java.io.IOException;
32+
import java.io.NotSerializableException;
33+
import java.io.OutputStream;
34+
import java.util.logging.Level;
35+
import java.util.logging.Logger;
36+
import org.apache.commons.io.output.NullOutputStream;
37+
import org.jboss.marshalling.Marshaller;
38+
import org.jboss.marshalling.Marshalling;
39+
import org.jboss.marshalling.MarshallingConfiguration;
40+
import org.jboss.marshalling.river.RiverMarshallerFactory;
41+
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
42+
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
43+
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
44+
import org.jenkinsci.plugins.workflow.pickles.Pickle;
45+
import org.jenkinsci.plugins.workflow.pickles.PickleFactory;
46+
import org.jenkinsci.plugins.workflow.support.concurrent.Futures;
47+
import org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverWriter;
48+
import org.kohsuke.accmod.Restricted;
49+
import org.kohsuke.accmod.restrictions.NoExternalUse;
50+
51+
/**
52+
* Ensures that exceptions are safely serializable.
53+
* Replaces anything problematic with {@link ProxyException}.
54+
* Mainly defends against {@link NotSerializableException}.
55+
*/
56+
@Restricted(NoExternalUse.class)
57+
public final class ThrowablePickle extends Pickle {
58+
59+
private static final Logger LOGGER = Logger.getLogger(ThrowablePickle.class.getName());
60+
private static final long serialVersionUID = 1;
61+
62+
/** Stack trace of the original exception. */
63+
private final ProxyException t;
64+
/** Class name of the original exception. */
65+
private final String clazz;
66+
/** Stack trace of the problem serializing the original exception. */
67+
private final String error;
68+
69+
private ThrowablePickle(Throwable t, Exception x) {
70+
LOGGER.log(Level.FINE, "Sanitizing {0} due to {1}", new Object[] {t, x});
71+
this.t = new ProxyException(t);
72+
clazz = t.getClass().getName();
73+
error = Functions.printThrowable(x);
74+
}
75+
76+
@Override public ListenableFuture<?> rehydrate(FlowExecutionOwner owner) {
77+
try {
78+
owner.getListener().getLogger().println(error.trim());
79+
owner.getListener().getLogger().println("Loading unserializable exception; result will no longer be assignable to class " + clazz);
80+
} catch (IOException x) {
81+
LOGGER.log(Level.WARNING, null, x);
82+
}
83+
return Futures.immediateFuture(t);
84+
}
85+
86+
@Extension public static final class Factory extends PickleFactory {
87+
88+
/** @see RiverWriter */
89+
@Override public Pickle writeReplace(Object o) {
90+
if (o instanceof Throwable) {
91+
Throwable t = (Throwable) o;
92+
try (OutputStream ignore = new NullOutputStream();
93+
// Could set an ObjectResolver to ignore _other_ pickles, but we do really expect an Exception to have fields of, say, FilePath.
94+
Marshaller marshaller = new RiverMarshallerFactory().createMarshaller(new MarshallingConfiguration())) {
95+
GroovySandbox.runInSandbox(() -> {
96+
marshaller.start(Marshalling.createByteOutput(ignore));
97+
marshaller.writeObject(t);
98+
return null;
99+
}, Whitelist.all());
100+
} catch (Exception x) {
101+
return new ThrowablePickle(t, x);
102+
}
103+
}
104+
return null;
105+
}
106+
107+
}
108+
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2018 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.workflow.support.pickles;
26+
27+
import hudson.remoting.ProxyException;
28+
import java.util.logging.Level;
29+
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
30+
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
31+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
32+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
33+
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
34+
import org.junit.ClassRule;
35+
import org.junit.Test;
36+
import org.junit.Rule;
37+
import org.jvnet.hudson.test.BuildWatcher;
38+
import org.jvnet.hudson.test.Issue;
39+
import org.jvnet.hudson.test.LoggerRule;
40+
import org.jvnet.hudson.test.RestartableJenkinsRule;
41+
42+
public class ThrowablePickleTest {
43+
44+
@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
45+
@Rule public RestartableJenkinsRule rr = new RestartableJenkinsRule();
46+
@Rule public LoggerRule logging = new LoggerRule().record(ThrowablePickle.class, Level.FINE);
47+
48+
@Issue("JENKINS-51390")
49+
@Test public void smokes() throws Exception {
50+
String beName = BadException.class.getName();
51+
rr.then(r -> {
52+
WorkflowJob p = r.createProject(WorkflowJob.class, "p");
53+
p.setDefinition(new CpsFlowDefinition("try {throw new " + beName + "()} catch (x) {semaphore 'wait'; echo(/caught a $x/)}", true));
54+
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
55+
SemaphoreStep.waitForStart("wait/1", b);
56+
});
57+
rr.then(r -> {
58+
WorkflowRun b = r.jenkins.getItemByFullName("p", WorkflowJob.class).getBuildByNumber(1);
59+
SemaphoreStep.success("wait/1", null);
60+
r.assertBuildStatusSuccess(r.waitForCompletion(b));
61+
r.assertLogContains("in field " + beName + ".notSerializable", b);
62+
r.assertLogContains("assignable to class " + beName, b);
63+
r.assertLogContains("caught a " + ProxyException.class.getName() + ": " + beName, b);
64+
});
65+
}
66+
67+
public static class BadException extends Exception {
68+
69+
private final Object notSerializable = new Object();
70+
71+
@Whitelisted
72+
public BadException() {}
73+
74+
}
75+
76+
}

0 commit comments

Comments
 (0)
Please sign in to comment.