59
59
import java .util .logging .Logger ;
60
60
import edu .umd .cs .findbugs .annotations .CheckForNull ;
61
61
import edu .umd .cs .findbugs .annotations .NonNull ;
62
+ import groovy .lang .MissingPropertyException ;
62
63
import javax .inject .Inject ;
63
64
import jenkins .model .Jenkins ;
64
65
import jenkins .scm .impl .SingleSCMSource ;
75
76
import org .jenkinsci .plugins .workflow .steps .StepContextParameter ;
76
77
import org .kohsuke .accmod .Restricted ;
77
78
import org .kohsuke .accmod .restrictions .DoNotUse ;
79
+ import org .kohsuke .groovy .sandbox .GroovyInterceptor ;
80
+ import org .kohsuke .groovy .sandbox .impl .Checker ;
78
81
import org .kohsuke .stapler .AncestorInPath ;
79
82
import org .kohsuke .stapler .DataBoundConstructor ;
80
83
import org .kohsuke .stapler .DataBoundSetter ;
@@ -270,11 +273,15 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
270
273
if (clazz != null ) {
271
274
// Field access?
272
275
try {
273
- // not doing a Whitelist check since GroovyClassLoaderWhitelist would be allowing it anyway
276
+ if (isSandboxed ()) {
277
+ return Checker .checkedGetAttribute (loadClass (prefix + clazz ), false , false , property );
278
+ }
274
279
return loadClass (prefix + clazz ).getField (property ).get (null );
275
- } catch (NoSuchFieldException x ) {
280
+ } catch (MissingPropertyException | NoSuchFieldException x ) {
276
281
// guessed wrong
277
- } catch (IllegalAccessException x ) {
282
+ } catch (SecurityException x ) {
283
+ throw x ;
284
+ } catch (Throwable x ) {
278
285
throw new GroovyRuntimeException (x );
279
286
}
280
287
}
@@ -284,6 +291,8 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
284
291
loadClass (prefix + fullClazz );
285
292
// OK, class really exists, stash it and await methods
286
293
return new LoadedClasses (library , trusted , changelog , prefix , fullClazz , srcUrl );
294
+ } else if (clazz != null ) {
295
+ throw new MissingPropertyException (property , loadClass (prefix + clazz ));
287
296
} else {
288
297
// Still selecting package components.
289
298
return new LoadedClasses (library , trusted , changelog , prefix + property + '.' , null , srcUrl );
@@ -293,13 +302,43 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
293
302
@ Override public Object invokeMethod (String name , Object _args ) {
294
303
Class <?> c = loadClass (prefix + clazz );
295
304
Object [] args = _args instanceof Object [] ? (Object []) _args : new Object [] {_args }; // TODO why does Groovy not just pass an Object[] to begin with?!
305
+ if (isSandboxed ()) {
306
+ try {
307
+ if (name .equals ("new" )) {
308
+ return Checker .checkedConstructor (c , args );
309
+ } else {
310
+ return Checker .checkedStaticCall (c , name , args );
311
+ }
312
+ } catch (SecurityException x ) {
313
+ throw x ;
314
+ } catch (Throwable x ) {
315
+ throw new GroovyRuntimeException (x );
316
+ }
317
+ }
296
318
if (name .equals ("new" )) {
297
319
return InvokerHelper .invokeConstructorOf (c , args );
298
320
} else {
299
321
return InvokerHelper .invokeStaticMethod (c , name , args );
300
322
}
301
323
}
302
324
325
+ /**
326
+ * Check whether the current thread has at least one active {@link GroovyInterceptor}.
327
+ * <p>
328
+ * Typically, {@code GroovyClassLoaderWhitelist} will allow access to everything defined in a class in a
329
+ * library, but there are some synthetic constructors, fields, and methods which should not be accessible.
330
+ * <p>
331
+ * As a result, when getting properties or invoking methods using this class, we need to apply sandbox
332
+ * protection if the Pipeline code performing the operation is sandbox-transformed. Unfortunately, it is
333
+ * difficult to detect that case specifically, so we instead intercept all calls if the Pipeline itself is
334
+ * sandboxed. This results in a false positive {@code RejectedAccessException} being thrown if a trusted
335
+ * library uses the {@code library} step and tries to access static fields or methods that are not permitted to
336
+ * be used in the sandbox.
337
+ */
338
+ private static boolean isSandboxed () {
339
+ return !GroovyInterceptor .getApplicableInterceptors ().isEmpty ();
340
+ }
341
+
303
342
// TODO putProperty for static field set
304
343
305
344
private Class <?> loadClass (String name ) {
0 commit comments