5959import java .util .logging .Logger ;
6060import edu .umd .cs .findbugs .annotations .CheckForNull ;
6161import edu .umd .cs .findbugs .annotations .NonNull ;
62+ import groovy .lang .MissingPropertyException ;
6263import javax .inject .Inject ;
6364import jenkins .model .Jenkins ;
6465import jenkins .scm .impl .SingleSCMSource ;
7576import org .jenkinsci .plugins .workflow .steps .StepContextParameter ;
7677import org .kohsuke .accmod .Restricted ;
7778import org .kohsuke .accmod .restrictions .DoNotUse ;
79+ import org .kohsuke .groovy .sandbox .GroovyInterceptor ;
80+ import org .kohsuke .groovy .sandbox .impl .Checker ;
7881import org .kohsuke .stapler .AncestorInPath ;
7982import org .kohsuke .stapler .DataBoundConstructor ;
8083import org .kohsuke .stapler .DataBoundSetter ;
@@ -270,11 +273,15 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
270273 if (clazz != null ) {
271274 // Field access?
272275 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+ }
274279 return loadClass (prefix + clazz ).getField (property ).get (null );
275- } catch (NoSuchFieldException x ) {
280+ } catch (MissingPropertyException | NoSuchFieldException x ) {
276281 // guessed wrong
277- } catch (IllegalAccessException x ) {
282+ } catch (SecurityException x ) {
283+ throw x ;
284+ } catch (Throwable x ) {
278285 throw new GroovyRuntimeException (x );
279286 }
280287 }
@@ -284,6 +291,8 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
284291 loadClass (prefix + fullClazz );
285292 // OK, class really exists, stash it and await methods
286293 return new LoadedClasses (library , trusted , changelog , prefix , fullClazz , srcUrl );
294+ } else if (clazz != null ) {
295+ throw new MissingPropertyException (property , loadClass (prefix + clazz ));
287296 } else {
288297 // Still selecting package components.
289298 return new LoadedClasses (library , trusted , changelog , prefix + property + '.' , null , srcUrl );
@@ -293,13 +302,43 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
293302 @ Override public Object invokeMethod (String name , Object _args ) {
294303 Class <?> c = loadClass (prefix + clazz );
295304 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+ }
296318 if (name .equals ("new" )) {
297319 return InvokerHelper .invokeConstructorOf (c , args );
298320 } else {
299321 return InvokerHelper .invokeStaticMethod (c , name , args );
300322 }
301323 }
302324
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+
303342 // TODO putProperty for static field set
304343
305344 private Class <?> loadClass (String name ) {
0 commit comments