Skip to content

Commit 0079bcd

Browse files
authored
Merge pull request #549 from jglick/ProxyWhitelist
Overhauled `ProxyWhitelist`
2 parents fd0d3f2 + 3872d58 commit 0079bcd

File tree

12 files changed

+124
-442
lines changed

12 files changed

+124
-442
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ public class AclAwareWhitelist extends Whitelist {
5151
*/
5252
public AclAwareWhitelist(Whitelist unrestricted, Whitelist restricted) {
5353
this.unrestricted = unrestricted;
54-
if (this.unrestricted instanceof EnumeratingWhitelist) {
55-
((EnumeratingWhitelist) this.unrestricted).precache();
56-
}
5754
this.restricted = restricted;
5855
}
5956

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

Lines changed: 39 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,23 @@
2424

2525
package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
2626

27+
import com.github.benmanes.caffeine.cache.Caffeine;
28+
import com.github.benmanes.caffeine.cache.LoadingCache;
29+
import edu.umd.cs.findbugs.annotations.CheckForNull;
30+
import edu.umd.cs.findbugs.annotations.NonNull;
31+
import java.lang.reflect.AccessibleObject;
2732
import java.lang.reflect.Array;
2833
import java.lang.reflect.Constructor;
2934
import java.lang.reflect.Field;
3035
import java.lang.reflect.Method;
3136
import java.lang.reflect.Modifier;
3237
import java.util.Arrays;
3338
import java.util.List;
34-
import java.util.concurrent.ConcurrentHashMap;
35-
39+
import java.util.function.BiPredicate;
40+
import java.util.function.Predicate;
41+
import java.util.function.Supplier;
3642
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
3743

38-
import edu.umd.cs.findbugs.annotations.CheckForNull;
39-
import edu.umd.cs.findbugs.annotations.NonNull;
40-
4144
/**
4245
* A whitelist based on listing signatures and searching them. Lists of signatures should not change
4346
* from invocation to invocation.
@@ -57,127 +60,55 @@ public abstract class EnumeratingWhitelist extends Whitelist {
5760

5861
protected abstract List<FieldSignature> staticFieldSignatures();
5962

60-
ConcurrentHashMap<String, Boolean> permittedCache = new ConcurrentHashMap<>(); // Not private to facilitate testing
61-
62-
@SafeVarargs
63-
private final void cacheSignatureList(List<Signature> ...sigs) {
64-
for (List<Signature> list : sigs) {
65-
for (Signature s : list) {
66-
if (!s.isWildcard()) { // Cache entries for wildcard signatures will never be accessed and just waste space
67-
permittedCache.put(s.toString(), Boolean.TRUE);
63+
private final Predicate<Method> methodCache = cache(this::methodSignatures, MethodSignature::matches);
64+
private final Predicate<Constructor<?>> constructorCache = cache(this::newSignatures, NewSignature::matches);
65+
private final Predicate<Method> staticMethodCache = cache(this::staticMethodSignatures, MethodSignature::matches);
66+
private final Predicate<Field> fieldCache = cache(this::fieldSignatures, FieldSignature::matches);
67+
private final Predicate<Field> staticFieldCache = cache(this::staticFieldSignatures, FieldSignature::matches);
68+
69+
private static <M extends AccessibleObject, S extends Signature> Predicate<M> cache(Supplier<List<S>> signatures, BiPredicate<S, M> matches) {
70+
// Unfortunately xxxSignatures() will return empty if called from superclass constructor,
71+
// since subclass constructors initialize lists, thus the need for Supplier.
72+
// Would be cleaner for EnumeratingWhitelist to take all signatures in its constructor,
73+
// and for StaticWhitelist to just be a utility with static constructor methods rather than a subclass.
74+
LoadingCache<M, Boolean> cache = Caffeine.newBuilder().weakKeys().build((M m) -> {
75+
for (S s : signatures.get()) {
76+
if (matches.test(s, m)) {
77+
return true;
6878
}
6979
}
70-
}
71-
}
72-
73-
/** Prepopulates the "permitted" cache, resetting if populated already. Should be called when method signatures change or after initialization. */
74-
final void precache() {
75-
if (!permittedCache.isEmpty()) {
76-
this.permittedCache.clear(); // No sense calling clearCache
77-
}
78-
cacheSignatureList((List)methodSignatures(), (List)(newSignatures()),
79-
(List)(staticMethodSignatures()), (List)(fieldSignatures()),
80-
(List)(staticFieldSignatures()));
81-
}
82-
83-
/** Frees up nearly all memory used for the cache. MUST BE CALLED if you change the result of the xxSignatures() methods. */
84-
final void clearCache() {
85-
this.permittedCache.clear();
86-
this.permittedCache = new ConcurrentHashMap<>();
80+
return false;
81+
});
82+
return m -> {
83+
if (signatures.get().isEmpty()) {
84+
return false; // shortcut
85+
}
86+
return cache.get(m);
87+
};
8788
}
8889

8990
@Override public final boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
90-
String key = canonicalMethodSig(method);
91-
Boolean b = permittedCache.get(key);
92-
if (b != null) {
93-
return b;
94-
}
95-
96-
boolean output = false;
97-
for (MethodSignature s : methodSignatures()) {
98-
if (s.matches(method)) {
99-
output = true;
100-
break;
101-
}
102-
}
103-
permittedCache.put(key, output);
104-
return output;
91+
return methodCache.test(method);
10592
}
10693

10794
@Override public final boolean permitsConstructor(@NonNull Constructor<?> constructor, @NonNull Object[] args) {
108-
String key = canonicalConstructorSig(constructor);
109-
Boolean b = permittedCache.get(key);
110-
if (b != null) {
111-
return b;
112-
}
113-
114-
boolean output = false;
115-
for (NewSignature s : newSignatures()) {
116-
if (s.matches(constructor)) {
117-
output = true;
118-
break;
119-
}
120-
}
121-
permittedCache.put(key, output);
122-
return output;
95+
return constructorCache.test(constructor);
12396
}
12497

12598
@Override public final boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
126-
String key = canonicalStaticMethodSig(method);
127-
Boolean b = permittedCache.get(key);
128-
if (b != null) {
129-
return b;
130-
}
131-
132-
boolean output = false;
133-
for (MethodSignature s : staticMethodSignatures()) {
134-
if (s.matches(method)) {
135-
output = true;
136-
break;
137-
}
138-
}
139-
permittedCache.put(key, output);
140-
return output;
99+
return staticMethodCache.test(method);
141100
}
142101

143102
@Override public final boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
144-
String key = canonicalFieldSig(field);
145-
Boolean b = permittedCache.get(key);
146-
if (b != null) {
147-
return b;
148-
}
149-
150-
boolean output = false;
151-
for (FieldSignature s : fieldSignatures()) {
152-
if (s.matches(field)) {
153-
output = true;
154-
break;
155-
}
156-
}
157-
permittedCache.put(key, output);
158-
return output;
103+
return fieldCache.test(field);
159104
}
160105

161106
@Override public final boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
162107
return permitsFieldGet(field, receiver);
163108
}
164109

165110
@Override public final boolean permitsStaticFieldGet(@NonNull Field field) {
166-
String key = canonicalStaticFieldSig(field);
167-
Boolean b = permittedCache.get(key);
168-
if (b != null) {
169-
return b;
170-
}
171-
172-
boolean output = false;
173-
for (FieldSignature s : staticFieldSignatures()) {
174-
if (s.matches(field)) {
175-
output = true;
176-
break;
177-
}
178-
}
179-
permittedCache.put(key, output);
180-
return output;
111+
return staticFieldCache.test(field);
181112
}
182113

183114
@Override public final boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
@@ -276,6 +207,8 @@ static String[] argumentTypes(Class<?>[] argumentTypes) {
276207
return s;
277208
}
278209

210+
// TODO move all these to StaticWhitelist
211+
279212
/** Canonical name for a field access. */
280213
static String canonicalFieldString(@NonNull Field field) {
281214
return getName(field.getDeclaringClass()) + ' ' + field.getName();
@@ -385,7 +318,7 @@ public NewSignature(String type, String[] argumentTypes) {
385318
public NewSignature(Class<?> type, Class<?>... argumentTypes) {
386319
this(getName(type), argumentTypes(argumentTypes));
387320
}
388-
boolean matches(Constructor c) {
321+
boolean matches(Constructor<?> c) {
389322
return getName(c.getDeclaringClass()).equals(type) && Arrays.equals(argumentTypes(c.getParameterTypes()), argumentTypes);
390323
}
391324
@Override String signaturePart() {

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,13 @@
2424

2525
package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
2626

27-
import hudson.Extension;
2827
import java.io.IOException;
29-
import org.kohsuke.accmod.Restricted;
30-
import org.kohsuke.accmod.restrictions.NoExternalUse;
3128

3229
/**
33-
* Includes entries useful for general kinds of scripts.
30+
* @deprecated replaced by {@link StaticWhitelist#stockWhitelists}, now used only in tests
3431
*/
35-
@Restricted(NoExternalUse.class)
36-
@Extension public final class GenericWhitelist extends ProxyWhitelist {
32+
@Deprecated
33+
public final class GenericWhitelist extends ProxyWhitelist {
3734

3835
public GenericWhitelist() throws IOException {
3936
super(StaticWhitelist.from(GenericWhitelist.class.getResource("generic-whitelist")));

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

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)