Skip to content

Commit 39ca7f7

Browse files
authored
Merge pull request #298 from dwnusbaum/JENKINS-42214
[JENKINS-42214] SandboxInterceptor must account for static members being accessed via objects instead of class references
2 parents 50b005d + f0ea59e commit 39ca7f7

File tree

5 files changed

+131
-30
lines changed

5 files changed

+131
-30
lines changed

src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptor.java

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.util.logging.Logger;
4545
import edu.umd.cs.findbugs.annotations.CheckForNull;
4646
import edu.umd.cs.findbugs.annotations.NonNull;
47+
import java.lang.reflect.Modifier;
4748
import org.codehaus.groovy.runtime.DateGroovyMethods;
4849
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
4950
import org.codehaus.groovy.runtime.EncodingGroovyMethods;
@@ -157,12 +158,12 @@ final class SandboxInterceptor extends GroovyInterceptor {
157158
throw new MissingMethodException(method, receiver.getClass(), args);
158159
} else if (StaticWhitelist.isPermanentlyBlacklistedMethod(m)) {
159160
throw StaticWhitelist.rejectMethod(m);
160-
} else if (whitelist.permitsMethod(m, receiver, args)) {
161+
} else if (permitsMethod(whitelist, m, receiver, args)) {
161162
return super.onMethodCall(invoker, receiver, method, args);
162163
} else if (method.equals("invokeMethod") && args.length == 2 && args[0] instanceof String && args[1] instanceof Object[]) {
163164
throw StaticWhitelist.rejectMethod(m, EnumeratingWhitelist.getName(receiver.getClass()) + " " + args[0] + printArgumentTypes((Object[]) args[1]));
164165
} else {
165-
throw StaticWhitelist.rejectMethod(m);
166+
throw rejectMethod(m);
166167
}
167168
}
168169

@@ -206,11 +207,11 @@ final class SandboxInterceptor extends GroovyInterceptor {
206207
? setterMethods.get(0) // If there is only a single setter, the argument will be cast to match the declared parameter type.
207208
: GroovyCallSiteSelector.method(receiver, setter, valueArg); // If there are multiple setters, MultipleSetterProperty just calls invokeMethod.
208209
if (setterMethod != null) {
209-
if (whitelist.permitsMethod(setterMethod, receiver, valueArg)) {
210+
if (permitsMethod(whitelist, setterMethod, receiver, valueArg)) {
210211
preCheckArgumentCasts(setterMethod, valueArg);
211212
return super.onSetProperty(invoker, receiver, property, value);
212213
} else if (rejector == null) {
213-
rejector = () -> StaticWhitelist.rejectMethod(setterMethod);
214+
rejector = () -> rejectMethod(setterMethod);
214215
}
215216
}
216217
Object[] propertyValueArgs = new Object[] {property, value};
@@ -223,13 +224,13 @@ final class SandboxInterceptor extends GroovyInterceptor {
223224
rejector = () -> StaticWhitelist.rejectMethod(setPropertyMethod, receiver.getClass().getName() + "." + property);
224225
}
225226
}
226-
final Field instanceField = GroovyCallSiteSelector.field(receiver, property);
227-
if (instanceField != null) {
228-
if (whitelist.permitsFieldSet(instanceField, receiver, value)) {
229-
Checker.preCheckedCast(instanceField.getType(), value, false, false, false);
227+
final Field field = GroovyCallSiteSelector.field(receiver, property);
228+
if (field != null) {
229+
if (permitsFieldSet(whitelist, field, receiver, value)) {
230+
Checker.preCheckedCast(field.getType(), value, false, false, false);
230231
return super.onSetProperty(invoker, receiver, property, value);
231232
} else if (rejector == null) {
232-
rejector = () -> StaticWhitelist.rejectField(instanceField);
233+
rejector = () -> rejectField(field);
233234
}
234235
}
235236
if (receiver instanceof Class) {
@@ -276,19 +277,19 @@ final class SandboxInterceptor extends GroovyInterceptor {
276277
String getter = "get" + MetaClassHelper.capitalize(property);
277278
final Method getterMethod = GroovyCallSiteSelector.method(receiver, getter, noArgs);
278279
if (getterMethod != null) {
279-
if (whitelist.permitsMethod(getterMethod, receiver, noArgs)) {
280+
if (permitsMethod(whitelist, getterMethod, receiver, noArgs)) {
280281
return super.onGetProperty(invoker, receiver, property);
281282
} else if (rejector == null) {
282-
rejector = () -> StaticWhitelist.rejectMethod(getterMethod);
283+
rejector = () -> rejectMethod(getterMethod);
283284
}
284285
}
285286
String booleanGetter = "is" + MetaClassHelper.capitalize(property);
286287
final Method booleanGetterMethod = GroovyCallSiteSelector.method(receiver, booleanGetter, noArgs);
287288
if (booleanGetterMethod != null && booleanGetterMethod.getReturnType() == boolean.class) {
288-
if (whitelist.permitsMethod(booleanGetterMethod, receiver, noArgs)) {
289+
if (permitsMethod(whitelist, booleanGetterMethod, receiver, noArgs)) {
289290
return super.onGetProperty(invoker, receiver, property);
290291
} else if (rejector == null) {
291-
rejector = () -> StaticWhitelist.rejectMethod(booleanGetterMethod);
292+
rejector = () -> rejectMethod(booleanGetterMethod);
292293
}
293294
}
294295
// look for GDK methods
@@ -311,12 +312,12 @@ final class SandboxInterceptor extends GroovyInterceptor {
311312
}
312313
}
313314
}
314-
final Field instanceField = GroovyCallSiteSelector.field(receiver, property);
315-
if (instanceField != null) {
316-
if (whitelist.permitsFieldGet(instanceField, receiver)) {
315+
final Field field = GroovyCallSiteSelector.field(receiver, property);
316+
if (field != null) {
317+
if (permitsFieldGet(whitelist, field, receiver)) {
317318
return super.onGetProperty(invoker, receiver, property);
318319
} else if (rejector == null) {
319-
rejector = () -> StaticWhitelist.rejectField(instanceField);
320+
rejector = () -> rejectField(field);
320321
}
321322
}
322323
// GroovyObject property access
@@ -391,10 +392,10 @@ private interface Rejector {
391392
Rejector rejector = null;
392393
Field field = GroovyCallSiteSelector.field(receiver, attribute);
393394
if (field != null) {
394-
if (whitelist.permitsFieldGet(field, receiver)) {
395+
if (permitsFieldGet(whitelist, field, receiver)) {
395396
return super.onGetAttribute(invoker, receiver, attribute);
396397
} else {
397-
rejector = () -> StaticWhitelist.rejectField(field);
398+
rejector = () -> rejectField(field);
398399
}
399400
}
400401
if (receiver instanceof Class) {
@@ -414,11 +415,11 @@ private interface Rejector {
414415
Rejector rejector = null;
415416
Field field = GroovyCallSiteSelector.field(receiver, attribute);
416417
if (field != null) {
417-
if (whitelist.permitsFieldSet(field, receiver, value)) {
418+
if (permitsFieldSet(whitelist, field, receiver, value)) {
418419
Checker.preCheckedCast(field.getType(), value, false, false, false);
419420
return super.onSetAttribute(invoker, receiver, attribute, value);
420421
} else {
421-
rejector = () -> StaticWhitelist.rejectField(field);
422+
rejector = () -> rejectField(field);
422423
}
423424
}
424425
if (receiver instanceof Class) {
@@ -442,10 +443,10 @@ private interface Rejector {
442443
Object[] args = new Object[] {index};
443444
Method method = GroovyCallSiteSelector.method(receiver, "getAt", args);
444445
if (method != null) {
445-
if (whitelist.permitsMethod(method, receiver, args)) {
446+
if (permitsMethod(whitelist, method, receiver, args)) {
446447
return super.onGetArray(invoker, receiver, index);
447448
} else {
448-
throw StaticWhitelist.rejectMethod(method);
449+
throw rejectMethod(method);
449450
}
450451
}
451452
args = new Object[] {receiver, index};
@@ -469,10 +470,10 @@ private interface Rejector {
469470
Object[] args = new Object[] {index, value};
470471
Method method = GroovyCallSiteSelector.method(receiver, "putAt", args);
471472
if (method != null) {
472-
if (whitelist.permitsMethod(method, receiver, args)) {
473+
if (permitsMethod(whitelist, method, receiver, args)) {
473474
return super.onSetArray(invoker, receiver, index, value);
474475
} else {
475-
throw StaticWhitelist.rejectMethod(method);
476+
throw rejectMethod(method);
476477
}
477478
}
478479
args = new Object[] {receiver, index, value};
@@ -546,4 +547,39 @@ private static String printArgumentTypes(Object[] args) {
546547
}
547548
}
548549

550+
private static boolean permitsFieldGet(@NonNull Whitelist whitelist, @NonNull Field field, @NonNull Object receiver) {
551+
if (Modifier.isStatic(field.getModifiers())) {
552+
return whitelist.permitsStaticFieldGet(field);
553+
}
554+
return whitelist.permitsFieldGet(field, receiver);
555+
}
556+
557+
private static boolean permitsFieldSet(@NonNull Whitelist whitelist, @NonNull Field field, @NonNull Object receiver, @CheckForNull Object value) {
558+
if (Modifier.isStatic(field.getModifiers())) {
559+
return whitelist.permitsStaticFieldSet(field, value);
560+
}
561+
return whitelist.permitsFieldSet(field, receiver, value);
562+
}
563+
564+
private static boolean permitsMethod(@NonNull Whitelist whitelist, @NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
565+
if (Modifier.isStatic(method.getModifiers())) {
566+
return whitelist.permitsStaticMethod(method, args);
567+
}
568+
return whitelist.permitsMethod(method, receiver, args);
569+
}
570+
571+
public static RejectedAccessException rejectMethod(@NonNull Method m) {
572+
if (Modifier.isStatic(m.getModifiers())) {
573+
return StaticWhitelist.rejectStaticMethod(m);
574+
}
575+
return StaticWhitelist.rejectMethod(m);
576+
}
577+
578+
public static RejectedAccessException rejectField(@NonNull Field f) {
579+
if (Modifier.isStatic(f.getModifiers())) {
580+
return StaticWhitelist.rejectStaticField(f);
581+
}
582+
return StaticWhitelist.rejectField(f);
583+
}
584+
549585
}

src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/EnumeratingWhitelist.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,7 @@ boolean matches(Field f) {
424424
}
425425
@Override boolean exists() throws Exception {
426426
try {
427-
type(type).getField(field);
428-
return true;
427+
return !Modifier.isStatic(type(type).getField(field).getModifiers());
429428
} catch (NoSuchFieldException x) {
430429
return false;
431430
}
@@ -444,6 +443,13 @@ static class StaticFieldSignature extends FieldSignature {
444443
@Override public String toString() {
445444
return "staticField " + signaturePart();
446445
}
446+
@Override boolean exists() throws Exception {
447+
try {
448+
return Modifier.isStatic(type(type).getField(field).getModifiers());
449+
} catch (NoSuchFieldException x) {
450+
return false;
451+
}
452+
}
447453
}
448454

449455
}

src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/jenkins-whitelist

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ method hudson.scm.ChangeLogSet$Entry getMsg
2828
method hudson.scm.ChangeLogSet$Entry getMsgAnnotated
2929
method hudson.scm.ChangeLogSet$Entry getMsgEscaped
3030
method hudson.scm.ChangeLogSet$Entry getTimestamp
31-
field hudson.scm.EditType ADD
32-
field hudson.scm.EditType DELETE
33-
field hudson.scm.EditType EDIT
31+
staticField hudson.scm.EditType ADD
32+
staticField hudson.scm.EditType DELETE
33+
staticField hudson.scm.EditType EDIT
3434
method hudson.scm.EditType getName
3535
method hudson.tools.ToolInstallation getHome
3636
method hudson.tools.ToolInstallation getName

src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptorTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.util.Collections;
5353
import java.util.Date;
5454
import java.util.HashMap;
55+
import java.util.List;
5556
import java.util.Locale;
5657
import java.util.Map;
5758
import java.util.Properties;
@@ -1654,6 +1655,54 @@ public void staticAttributesAreNotShadowedByClassFields() throws Throwable {
16541655
assertEvaluate(new GenericWhitelist(), "foo", "class MyClass { static String name }; MyClass.@name = 'foo'");
16551656
}
16561657

1658+
@Issue("JENKINS-42214")
1659+
@Test
1660+
public void accessStaticMembersViaInstance() throws Throwable {
1661+
String fqcn = HasStaticMembers.class.getName();
1662+
// Make sure that we report the correct signature in RejectedAccessException.
1663+
assertRejected(new AnnotatedWhitelist(), "staticField " + fqcn + " FOO", "def o = new " + fqcn + "(); o.FOO");
1664+
assertRejected(new AnnotatedWhitelist(), "staticMethod " + fqcn + " getBAR", "def o = new " + fqcn + "(); o.BAR");
1665+
assertRejected(new AnnotatedWhitelist(), "staticMethod " + fqcn + " isBAZ", "def o = new " + fqcn + "(); o.BAZ");
1666+
assertRejected(new AnnotatedWhitelist(), "staticField " + fqcn + " FOO", "def o = new " + fqcn + "(); o.FOO = 1");
1667+
assertRejected(new AnnotatedWhitelist(), "staticMethod " + fqcn + " setBAR int", "def o = new " + fqcn + "(); o.BAR = 2");
1668+
assertRejected(new AnnotatedWhitelist(), "staticField " + fqcn + " FOO", "def o = new " + fqcn + "(); o.@FOO");
1669+
assertRejected(new AnnotatedWhitelist(), "staticField " + fqcn + " FOO", "def o = new " + fqcn + "(); o.@FOO = 1");
1670+
assertRejected(new AnnotatedWhitelist(), "staticMethod " + fqcn + " method", "def o = new " + fqcn + "(); o.method()");
1671+
assertRejected(new AnnotatedWhitelist(), "staticMethod " + fqcn + " getAt int", "def o = new " + fqcn + "(); o[0]");
1672+
assertRejected(new AnnotatedWhitelist(), "staticMethod " + fqcn + " putAt int java.lang.Object", "def o = new " + fqcn + "(); o[0] = null");
1673+
// Make sure that we check for the correct signature in allowlists.
1674+
assertEvaluate(HasStaticMembers.allowlist("staticField " + fqcn + " FOO"), 1,
1675+
"def o = new " + fqcn + "(); o.FOO = o.FOO");
1676+
assertEvaluate(HasStaticMembers.allowlist("staticField " + fqcn + " FOO"), 1,
1677+
"def o = new " + fqcn + "(); o.@FOO = o.@FOO");
1678+
assertEvaluate(HasStaticMembers.allowlist("staticMethod " + fqcn + " getBAR", "staticMethod " + fqcn + " setBAR int"), 2,
1679+
"def o = new " + fqcn + "(); o.BAR = o.BAR");
1680+
assertEvaluate(HasStaticMembers.allowlist("staticMethod " + fqcn + " isBAZ"), true,
1681+
"def o = new " + fqcn + "(); o.BAZ");
1682+
assertEvaluate(HasStaticMembers.allowlist("staticMethod " + fqcn + " method"), 3,
1683+
"def o = new " + fqcn + "(); o.method()");
1684+
assertEvaluate(HasStaticMembers.allowlist("staticMethod " + fqcn + " getAt int", "staticMethod " + fqcn + " putAt int java.lang.Object"), 3,
1685+
"def o = new " + fqcn + "(); o[0] = o[3]");
1686+
}
1687+
1688+
public static class HasStaticMembers {
1689+
@Whitelisted public HasStaticMembers() { }
1690+
public static int FOO = 1; // Groovy will access the field directly
1691+
public static int BAR = 2; // Groovy will access the field via getBAR and setBAR
1692+
public static int getBAR() { return BAR; }
1693+
public static void setBAR(int BAR) { HasStaticMembers.BAR = BAR; }
1694+
public static boolean BAZ = true; // Groovy will read the field via isBAZ
1695+
public static boolean isBAZ() { return BAZ; }
1696+
public static int method() { return 3; }
1697+
public static int getAt(int index) { return index; }
1698+
public static void putAt(int index, Object value) { }
1699+
public static Whitelist allowlist(String... signatures) throws Exception {
1700+
List<String> signaturesList = new ArrayList<>(Arrays.asList(signatures));
1701+
signaturesList.add("new " + HasStaticMembers.class.getName());
1702+
return new StaticWhitelist(signaturesList);
1703+
}
1704+
}
1705+
16571706
/**
16581707
* Checks that the annotation is blocked from being used in the provided script whether it is imported or used via
16591708
* fully-qualified class name.

src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/EnumeratingWhitelistTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import org.junit.Assert;
3535
import org.junit.Test;
36+
import org.jvnet.hudson.test.Issue;
3637
import static org.junit.Assert.*;
3738

3839
public class EnumeratingWhitelistTest {
@@ -197,4 +198,13 @@ public void testCachingWithWildcards() throws Exception {
197198
assertEquals(Boolean.TRUE, myList.permittedCache.get(EnumeratingWhitelist.canonicalFieldSig(f))); // Verifies it can cache that
198199
}
199200

201+
@Issue("JENKINS-42214")
202+
@Test public void fieldExists() throws Exception {
203+
assertTrue(new EnumeratingWhitelist.FieldSignature("hudson.model.Result", "color").exists());
204+
assertTrue(new EnumeratingWhitelist.StaticFieldSignature("hudson.model.Result", "ABORTED").exists());
205+
206+
assertFalse(new EnumeratingWhitelist.StaticFieldSignature("hudson.model.Result", "color").exists());
207+
assertFalse(new EnumeratingWhitelist.FieldSignature("hudson.model.Result", "ABORTED").exists());
208+
}
209+
200210
}

0 commit comments

Comments
 (0)