Skip to content

Commit a73c079

Browse files
authored
Merge pull request #555 from jglick/ApprovedWhitelist
Avoid race condition in `ApprovedWhitelist` reconfiguration
2 parents 13ba786 + 83df236 commit a73c079

File tree

3 files changed

+37
-36
lines changed

3 files changed

+37
-36
lines changed

Jenkinsfile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
configurations = [[platform: 'linux', jdk: 21]]
2+
if (env.CHANGE_ID == null) { // TODO https://github.com/jenkins-infra/helpdesk/issues/3931 workaround
3+
configurations += [platform: 'windows', jdk: 17]
4+
}
15
buildPlugin(
26
useContainerAgent: true,
3-
configurations: [
4-
[platform: 'linux', jdk: 21],
5-
[platform: 'windows', jdk: 17],
6-
])
7+
configurations: configurations
8+
)

src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
import java.security.MessageDigest;
5959
import java.security.NoSuchAlgorithmException;
6060
import java.util.ArrayList;
61-
import java.util.Collections;
6261
import java.util.Comparator;
6362
import java.util.HashMap;
6463
import java.util.Iterator;
@@ -76,7 +75,6 @@
7675

7776
import edu.umd.cs.findbugs.annotations.CheckForNull;
7877
import edu.umd.cs.findbugs.annotations.NonNull;
79-
import java.util.concurrent.atomic.AtomicBoolean;
8078
import jenkins.model.Jenkins;
8179
import net.sf.json.JSON;
8280
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
@@ -533,7 +531,11 @@ public synchronized void load() {
533531
if (changed) {
534532
save();
535533
}
536-
ApprovedWhitelist.configurationChanged();
534+
try {
535+
configurationChanged();
536+
} catch (IOException x) {
537+
LOG.log(Level.SEVERE, "Malformed signature entry in scriptApproval.xml: '" + x.getMessage() + "'");
538+
}
537539
}
538540

539541
private void clear() {
@@ -967,32 +969,30 @@ public synchronized String[] getApprovedScriptHashes() {
967969
return approvedScriptHashes.toArray(new String[approvedScriptHashes.size()]);
968970
}
969971

972+
private synchronized void configurationChanged() throws IOException {
973+
// Do not use lookupSingleton: ScriptApprovalLoadingTest.dynamicLoading
974+
ApprovedWhitelist instance = ExtensionList.lookup(Whitelist.class).get(ApprovedWhitelist.class);
975+
if (instance == null) {
976+
throw new IllegalStateException("Failed to find ApprovedWhitelist");
977+
}
978+
LOG.fine("resetting");
979+
synchronized (instance) {
980+
instance.pendingDelegate = new AclAwareWhitelist(new StaticWhitelist(approvedSignatures), new StaticWhitelist(aclApprovedSignatures));
981+
}
982+
}
983+
970984
@Restricted(NoExternalUse.class) // implementation
971985
@Extension public static final class ApprovedWhitelist extends ProxyWhitelist {
972986

973-
static void configurationChanged() {
974-
// Do not use lookupSingleton: ScriptApprovalLoadingTest.dynamicLoading
975-
ApprovedWhitelist instance = ExtensionList.lookup(Whitelist.class).get(ApprovedWhitelist.class);
976-
if (instance == null) {
977-
throw new IllegalStateException("Failed to find ApprovedWhitelist");
978-
}
979-
instance.initialized.set(false);
980-
}
981-
982-
private final AtomicBoolean initialized = new AtomicBoolean();
987+
private @CheckForNull Whitelist pendingDelegate;
983988

984-
@Override protected void beforePermits() {
985-
if (initialized.compareAndSet(false, true)) {
986-
try {
987-
ScriptApproval instance = ScriptApproval.get();
988-
Whitelist delegate;
989-
synchronized (instance) {
990-
delegate = new AclAwareWhitelist(new StaticWhitelist(instance.approvedSignatures), new StaticWhitelist(instance.aclApprovedSignatures));
991-
}
992-
reset(Set.of(delegate));
993-
} catch (IOException e) {
994-
LOG.log(Level.SEVERE, "Malformed signature entry in scriptApproval.xml: '" + e.getMessage() + "'");
995-
}
989+
@Override protected synchronized void beforePermits() {
990+
if (pendingDelegate != null) {
991+
LOG.fine("refreshing");
992+
reset(Set.of(pendingDelegate));
993+
pendingDelegate = null;
994+
} else {
995+
LOG.finer("no need to refresh");
996996
}
997997
}
998998

@@ -1136,8 +1136,8 @@ public Set<PendingSignature> getPendingSignatures() {
11361136
return pendingSignatures;
11371137
}
11381138

1139-
private String[][] reconfigure() throws IOException {
1140-
ApprovedWhitelist.configurationChanged();
1139+
private synchronized String[][] reconfigure() throws IOException {
1140+
configurationChanged();
11411141
return new String[][] {getApprovedSignatures(), getAclApprovedSignatures(), getDangerousApprovedSignatures()};
11421142
}
11431143

src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.util.logging.Level;
5050

5151
import static org.hamcrest.MatcherAssert.assertThat;
52+
import static org.hamcrest.Matchers.hasItemInArray;
5253
import static org.hamcrest.Matchers.is;
5354
import static org.hamcrest.Matchers.nullValue;
5455
import static org.junit.Assert.assertEquals;
@@ -57,7 +58,7 @@
5758

5859
public class ScriptApprovalTest extends AbstractApprovalTest<ScriptApprovalTest.Script> {
5960
@Rule
60-
public LoggerRule logging = new LoggerRule();
61+
public LoggerRule logging = new LoggerRule().record(ScriptApproval.class, Level.FINER).capture(100);
6162

6263
private static final String CLEAR_ALL_ID = "approvedScripts-clear";
6364

@@ -75,11 +76,9 @@ public class ScriptApprovalTest extends AbstractApprovalTest<ScriptApprovalTest.
7576
@Test
7677
@LocalData("malformedScriptApproval")
7778
public void malformedScriptApproval() throws Exception {
78-
logging.record(ScriptApproval.class, Level.FINER).capture(100);
7979
assertThat(Whitelist.all().permitsMethod(Jenkins.class.getMethod("get"), null, null), is(false));
80-
assertThat(logging.getRecords(), Matchers.hasSize(Matchers.equalTo(1)));
81-
assertEquals("Malformed signature entry in scriptApproval.xml: ' new java.lang.Exception java.lang.String'",
82-
logging.getRecords().get(0).getMessage());
80+
assertThat(logging.getRecords().stream().map(r -> r.getMessage()).toArray(String[]::new),
81+
hasItemInArray("Malformed signature entry in scriptApproval.xml: ' new java.lang.Exception java.lang.String'"));
8382
}
8483

8584
@Test @LocalData("dangerousApproved") public void dangerousApprovedSignatures() {

0 commit comments

Comments
 (0)