+ Move periodic task log files from JENKINS_HOME/*.log to JENKINS_HOME/logs/tasks/*.log and rotate them periodically rather than overwrite every execution
+ (issue 33068)
diff --git a/core/pom.xml b/core/pom.xml
index 436eb16ad4faabd66041b684aa88aa031f5075b6..2fa8dac83be87c1e9f9338aa41dc649743409d19 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -39,7 +39,7 @@ THE SOFTWARE.
true
- 1.237
+ 1.2392.5.6.SEC031.8.9
diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java
index d1973761d234d251a5f98ee12d3dbfed3ec5d313..70a458a653df3ac6252072e3d0b5e652b30b419b 100644
--- a/core/src/main/java/hudson/cli/CLIAction.java
+++ b/core/src/main/java/hudson/cli/CLIAction.java
@@ -78,7 +78,7 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy {
final String commandName = req.getRestOfPath().substring(1);
CLICommand command = CLICommand.clone(commandName);
if (command == null) {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND, "No such command " + commandName);
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND, "No such command");
return;
}
diff --git a/core/src/main/java/hudson/model/AsyncAperiodicWork.java b/core/src/main/java/hudson/model/AsyncAperiodicWork.java
index 9111e5fd474d5536879c86180272737fe26aa67c..8587f925c361e010ed118e2a1b9f663f9965faf1 100644
--- a/core/src/main/java/hudson/model/AsyncAperiodicWork.java
+++ b/core/src/main/java/hudson/model/AsyncAperiodicWork.java
@@ -25,11 +25,11 @@ package hudson.model;
import hudson.security.ACL;
import hudson.util.StreamTaskListener;
-
import java.io.File;
import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
-
import jenkins.model.Jenkins;
/**
@@ -40,8 +40,48 @@ import jenkins.model.Jenkins;
* @since 1.410
*/
public abstract class AsyncAperiodicWork extends AperiodicWork {
-
- /**
+ /**
+ * The default number of minutes after which to try and rotate the log file used by {@link #createListener()}.
+ * This value is controlled by the system property {@code hudson.model.AsyncAperiodicWork.logRotateMinutes}.
+ * Each individual AsyncAperiodicWork can also have a per-extension override using the system property
+ * based on their fully qualified class name with {@code .logRotateMinutes} appended.
+ *
+ * @since 1.651
+ */
+ private static final long LOG_ROTATE_MINUTES = Long.getLong(AsyncAperiodicWork.class.getName() +
+ ".logRotateMinutes", TimeUnit.DAYS.toMinutes(1));
+ /**
+ * The default file size after which to try and rotate the log file used by {@link #createListener()}.
+ * A value of {@code -1L} disables rotation based on file size.
+ * This value is controlled by the system property {@code hudson.model.AsyncAperiodicWork.logRotateSize}.
+ * Each individual AsyncAperiodicWork can also have a per-extension override using the system property
+ * based on their fully qualified class name with {@code .logRotateSize} appended.
+ *
+ * @since 1.651
+ */
+ private static final long LOG_ROTATE_SIZE = Long.getLong(AsyncAperiodicWork.class.getName() + ".logRotateSize",
+ -1L);
+ /**
+ * The number of milliseconds (since startup or previous rotation) after which to try and rotate the log file.
+ *
+ * @since 1.651
+ */
+ private final long logRotateMillis;
+ /**
+ * {@code -1L} disabled file size based log rotation, otherwise when starting an {@link #execute(TaskListener)},
+ * if the log file size is above this number of bytes then the log file will be rotated beforehand.
+ *
+ * @since 1.651
+ */
+ private final long logRotateSize;
+ /**
+ * The last time the log files were rotated. On start-up this will be {@link Long#MIN_VALUE} to ensure that the
+ * logs are always rotated every time Jenkins starts up to make it easier to correlate events with the main log.
+ *
+ * @since 1.651
+ */
+ private long lastRotateMillis = Long.MIN_VALUE;
+ /**
* Name of the work.
*/
public final String name;
@@ -50,6 +90,9 @@ public abstract class AsyncAperiodicWork extends AperiodicWork {
protected AsyncAperiodicWork(String name) {
this.name = name;
+ this.logRotateMillis = TimeUnit.MINUTES.toMillis(
+ Long.getLong(getClass().getName()+".logRotateMinutes", LOG_ROTATE_MINUTES));
+ this.logRotateSize = Long.getLong(getClass().getName() +".logRotateSize", LOG_ROTATE_SIZE);
}
/**
@@ -59,16 +102,18 @@ public abstract class AsyncAperiodicWork extends AperiodicWork {
public final void doAperiodicRun() {
try {
if(thread!=null && thread.isAlive()) {
- logger.log(Level.INFO, name+" thread is still running. Execution aborted.");
+ logger.log(getSlowLoggingLevel(), "{0} thread is still running. Execution aborted.", name);
return;
}
thread = new Thread(new Runnable() {
public void run() {
- logger.log(Level.INFO, "Started "+name);
+ logger.log(getNormalLoggingLevel(), "Started {0}", name);
long startTime = System.currentTimeMillis();
+ long stopTime;
StreamTaskListener l = createListener();
try {
+ l.getLogger().printf("Started at %tc%n", new Date(startTime));
ACL.impersonate(ACL.SYSTEM);
execute(l);
@@ -77,11 +122,16 @@ public abstract class AsyncAperiodicWork extends AperiodicWork {
} catch (InterruptedException e) {
e.printStackTrace(l.fatalError("aborted"));
} finally {
- l.closeQuietly();
+ stopTime = System.currentTimeMillis();
+ try {
+ l.getLogger().printf("Finished at %tc. %dms%n", new Date(stopTime), stopTime - startTime);
+ } finally {
+ l.closeQuietly();
+ }
}
- logger.log(Level.INFO, "Finished "+name+". "+
- (System.currentTimeMillis()-startTime)+" ms");
+ logger.log(getNormalLoggingLevel(), "Finished {0}. {1,number} ms",
+ new Object[]{name, stopTime - startTime});
}
},name+" thread");
thread.start();
@@ -91,8 +141,54 @@ public abstract class AsyncAperiodicWork extends AperiodicWork {
}
protected StreamTaskListener createListener() {
+ File f = getLogFile();
+ if (!f.getParentFile().isDirectory()) {
+ if (!f.getParentFile().mkdirs()) {
+ logger.log(getErrorLoggingLevel(), "Could not create directory {0}", f.getParentFile());
+ }
+ }
+ if (f.isFile()) {
+ if ((lastRotateMillis + logRotateMillis < System.currentTimeMillis())
+ || (logRotateSize > 0 && f.length() > logRotateSize)) {
+ lastRotateMillis = System.currentTimeMillis();
+ File prev = null;
+ for (int i = 5; i >= 0; i--) {
+ File curr = i == 0 ? f : new File(f.getParentFile(), f.getName() + "." + i);
+ if (curr.isFile()) {
+ if (prev != null && !prev.exists()) {
+ if (!curr.renameTo(prev)) {
+ logger.log(getErrorLoggingLevel(), "Could not rotate log files {0} to {1}",
+ new Object[]{curr, prev});
+ }
+ } else {
+ if (!curr.delete()) {
+ logger.log(getErrorLoggingLevel(), "Could not delete log file {0} to enable rotation",
+ curr);
+ }
+ }
+ }
+ prev = curr;
+ }
+ }
+ } else {
+ lastRotateMillis = System.currentTimeMillis();
+ // migrate old log files the first time we start-up
+ File oldFile = new File(Jenkins.getActiveInstance().getRootDir(), f.getName());
+ if (oldFile.isFile()) {
+ File newFile = new File(f.getParentFile(), f.getName() + ".1");
+ if (!newFile.isFile()) {
+ // if there has never been rotation then this is the first time
+ if (oldFile.renameTo(newFile)) {
+ logger.log(getNormalLoggingLevel(), "Moved {0} to {1}", new Object[]{oldFile, newFile});
+ } else {
+ logger.log(getErrorLoggingLevel(), "Could not move {0} to {1}",
+ new Object[]{oldFile, newFile});
+ }
+ }
+ }
+ }
try {
- return new StreamTaskListener(getLogFile());
+ return new StreamTaskListener(f, true, null);
} catch (IOException e) {
throw new Error(e);
}
@@ -102,7 +198,37 @@ public abstract class AsyncAperiodicWork extends AperiodicWork {
* Determines the log file that records the result of this task.
*/
protected File getLogFile() {
- return new File(Jenkins.getInstance().getRootDir(),name+".log");
+ return new File(Jenkins.getActiveInstance().getRootDir(),"logs/tasks/"+name+".log");
+ }
+
+ /**
+ * Returns the logging level at which normal messages are displayed.
+ *
+ * @return The logging level.
+ * @since 1.651
+ */
+ protected Level getNormalLoggingLevel() {
+ return Level.INFO;
+ }
+
+ /**
+ * Returns the logging level at which previous task still executing messages is displayed.
+ *
+ * @return The logging level.
+ * @since 1.651
+ */
+ protected Level getSlowLoggingLevel() {
+ return getNormalLoggingLevel();
+ }
+
+ /**
+ * Returns the logging level at which error messages are displayed.
+ *
+ * @return The logging level.
+ * @since 1.651
+ */
+ protected Level getErrorLoggingLevel() {
+ return Level.SEVERE;
}
/**
diff --git a/core/src/main/java/hudson/model/AsyncPeriodicWork.java b/core/src/main/java/hudson/model/AsyncPeriodicWork.java
index 9444887b5b1c262818920df69006021e291e5559..392f90abcb7d2ab06c2ebdcac2df18bd8b1cc9ad 100644
--- a/core/src/main/java/hudson/model/AsyncPeriodicWork.java
+++ b/core/src/main/java/hudson/model/AsyncPeriodicWork.java
@@ -2,12 +2,13 @@ package hudson.model;
import hudson.security.ACL;
import hudson.util.StreamTaskListener;
-import jenkins.model.Jenkins;
-
import java.io.File;
import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogRecord;
+import jenkins.model.Jenkins;
/**
* {@link PeriodicWork} that takes a long time to run.
@@ -20,6 +21,46 @@ import java.util.logging.LogRecord;
* @author Kohsuke Kawaguchi
*/
public abstract class AsyncPeriodicWork extends PeriodicWork {
+ /**
+ * The default number of minutes after which to try and rotate the log file used by {@link #createListener()}.
+ * This value is controlled by the system property {@code hudson.model.AsyncPeriodicWork.logRotateMinutes}.
+ * Each individual AsyncPeriodicWork can also have a per-extension override using the system property
+ * based on their fully qualified class name with {@code .logRotateMinutes} appended.
+ *
+ * @since 1.651
+ */
+ private static final long LOG_ROTATE_MINUTES = Long.getLong(AsyncPeriodicWork.class.getName() + ".logRotateMinutes",
+ TimeUnit.DAYS.toMinutes(1));
+ /**
+ * The default file size after which to try and rotate the log file used by {@link #createListener()}.
+ * A value of {@code -1L} disables rotation based on file size.
+ * This value is controlled by the system property {@code hudson.model.AsyncPeriodicWork.logRotateSize}.
+ * Each individual AsyncPeriodicWork can also have a per-extension override using the system property
+ * based on their fully qualified class name with {@code .logRotateSize} appended.
+ *
+ * @since 1.651
+ */
+ private static final long LOG_ROTATE_SIZE = Long.getLong(AsyncPeriodicWork.class.getName() + ".logRotateSize", -1L);
+ /**
+ * The number of milliseconds (since startup or previous rotation) after which to try and rotate the log file.
+ *
+ * @since 1.651
+ */
+ private final long logRotateMillis;
+ /**
+ * {@code -1L} disabled file size based log rotation, otherwise when starting an {@link #execute(TaskListener)},
+ * if the log file size is above this number of bytes then the log file will be rotated beforehand.
+ *
+ * @since 1.651
+ */
+ private final long logRotateSize;
+ /**
+ * The last time the log files were rotated. On start-up this will be {@link Long#MIN_VALUE} to ensure that the
+ * logs are always rotated every time Jenkins starts up to make it easier to correlate events with the main log.
+ *
+ * @since 1.651
+ */
+ private long lastRotateMillis = Long.MIN_VALUE;
/**
* Human readable name of the work.
*/
@@ -29,6 +70,9 @@ public abstract class AsyncPeriodicWork extends PeriodicWork {
protected AsyncPeriodicWork(String name) {
this.name = name;
+ this.logRotateMillis = TimeUnit.MINUTES.toMillis(
+ Long.getLong(getClass().getName() + ".logRotateMinutes", LOG_ROTATE_MINUTES));
+ this.logRotateSize = Long.getLong(getClass().getName() + ".logRotateSize", LOG_ROTATE_SIZE);
}
/**
@@ -45,9 +89,11 @@ public abstract class AsyncPeriodicWork extends PeriodicWork {
public void run() {
logger.log(getNormalLoggingLevel(), "Started {0}", name);
long startTime = System.currentTimeMillis();
+ long stopTime;
StreamTaskListener l = createListener();
try {
+ l.getLogger().printf("Started at %tc%n", new Date(startTime));
ACL.impersonate(ACL.SYSTEM);
execute(l);
@@ -56,11 +102,16 @@ public abstract class AsyncPeriodicWork extends PeriodicWork {
} catch (InterruptedException e) {
e.printStackTrace(l.fatalError("aborted"));
} finally {
- l.closeQuietly();
+ stopTime = System.currentTimeMillis();
+ try {
+ l.getLogger().printf("Finished at %tc. %dms%n", new Date(stopTime), stopTime - startTime);
+ } finally {
+ l.closeQuietly();
+ }
}
logger.log(getNormalLoggingLevel(), "Finished {0}. {1,number} ms",
- new Object[]{name, (System.currentTimeMillis()-startTime)});
+ new Object[]{name, stopTime - startTime});
}
},name+" thread");
thread.start();
@@ -73,8 +124,54 @@ public abstract class AsyncPeriodicWork extends PeriodicWork {
}
protected StreamTaskListener createListener() {
+ File f = getLogFile();
+ if (!f.getParentFile().isDirectory()) {
+ if (!f.getParentFile().mkdirs()) {
+ logger.log(getErrorLoggingLevel(), "Could not create directory {0}", f.getParentFile());
+ }
+ }
+ if (f.isFile()) {
+ if ((lastRotateMillis + logRotateMillis < System.currentTimeMillis())
+ || (logRotateSize > 0 && f.length() > logRotateSize)) {
+ lastRotateMillis = System.currentTimeMillis();
+ File prev = null;
+ for (int i = 5; i >= 0; i--) {
+ File curr = i == 0 ? f : new File(f.getParentFile(), f.getName() + "." + i);
+ if (curr.isFile()) {
+ if (prev != null && !prev.exists()) {
+ if (!curr.renameTo(prev)) {
+ logger.log(getErrorLoggingLevel(), "Could not rotate log files {0} to {1}",
+ new Object[]{curr, prev});
+ }
+ } else {
+ if (!curr.delete()) {
+ logger.log(getErrorLoggingLevel(), "Could not delete log file {0} to enable rotation",
+ curr);
+ }
+ }
+ }
+ prev = curr;
+ }
+ }
+ } else {
+ lastRotateMillis = System.currentTimeMillis();
+ // migrate old log files the first time we start-up
+ File oldFile = new File(Jenkins.getActiveInstance().getRootDir(), f.getName());
+ if (oldFile.isFile()) {
+ File newFile = new File(f.getParentFile(), f.getName() + ".1");
+ if (!newFile.isFile()) {
+ // if there has never been rotation then this is the first time
+ if (oldFile.renameTo(newFile)) {
+ logger.log(getNormalLoggingLevel(), "Moved {0} to {1}", new Object[]{oldFile, newFile});
+ } else {
+ logger.log(getErrorLoggingLevel(), "Could not move {0} to {1}",
+ new Object[]{oldFile, newFile});
+ }
+ }
+ }
+ }
try {
- return new StreamTaskListener(getLogFile());
+ return new StreamTaskListener(f, true, null);
} catch (IOException e) {
throw new Error(e);
}
@@ -84,7 +181,7 @@ public abstract class AsyncPeriodicWork extends PeriodicWork {
* Determines the log file that records the result of this task.
*/
protected File getLogFile() {
- return new File(Jenkins.getInstance().getRootDir(),name+".log");
+ return new File(Jenkins.getActiveInstance().getRootDir(),"logs/tasks/"+name+".log");
}
/**
diff --git a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java
index b801866fa671688bc5f1bd75247987929f6c927a..35447865e832bdbe419ff68b72f609b51cd2e0d4 100644
--- a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java
+++ b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java
@@ -5,6 +5,7 @@
*/
package hudson.security.csrf;
+import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
@@ -95,7 +96,9 @@ public class DefaultCrumbIssuer extends CrumbIssuer {
if (request instanceof HttpServletRequest) {
String newCrumb = issueCrumb(request, salt);
if ((newCrumb != null) && (crumb != null)) {
- return newCrumb.equals(crumb);
+ // String.equals() is not constant-time, but this is
+ return MessageDigest.isEqual(newCrumb.getBytes(Charset.forName("US-ASCII")),
+ crumb.getBytes(Charset.forName("US-ASCII")));
}
}
return false;
diff --git a/core/src/main/java/hudson/util/StreamTaskListener.java b/core/src/main/java/hudson/util/StreamTaskListener.java
index 4d6aea568d74bce91f31172e5150c0a866ebb2b6..5e4a294fb06069d87ed9987a0f6ef738b242235c 100644
--- a/core/src/main/java/hudson/util/StreamTaskListener.java
+++ b/core/src/main/java/hudson/util/StreamTaskListener.java
@@ -28,8 +28,6 @@ import hudson.console.ConsoleNote;
import hudson.console.HudsonExceptionNote;
import hudson.model.TaskListener;
import hudson.remoting.RemoteOutputStream;
-import org.kohsuke.stapler.framework.io.WriterOutputStream;
-
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
@@ -46,6 +44,7 @@ import java.io.Writer;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.kohsuke.stapler.framework.io.WriterOutputStream;
/**
* {@link TaskListener} that generates output into a single stream.
@@ -98,6 +97,22 @@ public class StreamTaskListener extends AbstractTaskListener implements Serializ
this(new FileOutputStream(out),charset);
}
+ /**
+ * Constructs a {@link StreamTaskListener} that sends the output to a specified file.
+ *
+ * @param out the file.
+ * @param append if {@code true}, then output will be written to the end of the file rather than the beginning.
+ * @param charset if non-{@code null} then the charset to use when writing.
+ * @throws IOException if the file could not be opened.
+ * @since 1.651
+ */
+ public StreamTaskListener(File out, boolean append, Charset charset) throws IOException {
+ // don't do buffering so that what's written to the listener
+ // gets reflected to the file immediately, which can then be
+ // served to the browser immediately
+ this(new FileOutputStream(out, append),charset);
+ }
+
public StreamTaskListener(Writer w) throws IOException {
this(new WriterOutputStream(w));
}
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index 325f4a8556f342e2bdb18e24641bac21c2ab00a8..bfe924e70ab2969956f52b41b13c3facb5a16660 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -47,6 +47,7 @@ import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import hudson.PluginManager;
import hudson.PluginWrapper;
import hudson.diagnosis.OldDataMonitor;
+import hudson.remoting.ClassFilter;
import hudson.util.xstream.ImmutableSetConverter;
import hudson.util.xstream.ImmutableSortedSetConverter;
import jenkins.model.Jenkins;
@@ -159,6 +160,8 @@ public class XStream2 extends XStream {
// but before reflection-based one kicks in.
registerConverter(new AssociatedConverterImpl(this), -10);
+ registerConverter(new BlacklistedTypesConverter(), PRIORITY_VERY_HIGH); // SECURITY-247 defense
+
registerConverter(new DynamicProxyConverter(getMapper()) { // SECURITY-105 defense
@Override public boolean canConvert(Class type) {
return /* this precedes NullConverter */ type != null && super.canConvert(type);
@@ -434,4 +437,30 @@ public class XStream2 extends XStream {
}
+ private static class BlacklistedTypesConverter implements Converter {
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ throw new UnsupportedOperationException("Refusing to marshal " + source.getClass().getName() + " for security reasons");
+ }
+
+ @Override
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ throw new ConversionException("Refusing to unmarshal " + reader.getNodeName() + " for security reasons");
+ }
+
+ @Override
+ public boolean canConvert(Class type) {
+ if (type == null) {
+ return false;
+ }
+ try {
+ ClassFilter.DEFAULT.check(type);
+ ClassFilter.DEFAULT.check(type.getName());
+ } catch (SecurityException se) {
+ // claim we can convert all the scary stuff so we can throw exceptions when attempting to do so
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java
index 8aef388cbd64d5554f1b4e7af6475e5c7574f2d7..82d3d9616e7a45d9ad4e54a0767acf592509a0f9 100644
--- a/core/src/main/java/jenkins/security/ApiTokenProperty.java
+++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java
@@ -41,6 +41,8 @@ import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.annotation.Nonnull;
import org.apache.commons.lang.StringUtils;
@@ -109,7 +111,10 @@ public class ApiTokenProperty extends UserProperty {
}
public boolean matchesPassword(String password) {
- return getApiTokenInsecure().equals(password);
+ String token = getApiTokenInsecure();
+ // String.equals isn't constant time, but this is
+ return MessageDigest.isEqual(password.getBytes(Charset.forName("US-ASCII")),
+ token.getBytes(Charset.forName("US-ASCII")));
}
private boolean hasPermissionToSeeToken() {
diff --git a/core/src/main/resources/hudson/ProxyConfiguration/help-name.html b/core/src/main/resources/hudson/ProxyConfiguration/help-name.html
index 858bf03249341805bd27bfe8e8b0a4b47d48e1a4..53291af38d9396b420f8aa23c6e322e0fe3ad933 100644
--- a/core/src/main/resources/hudson/ProxyConfiguration/help-name.html
+++ b/core/src/main/resources/hudson/ProxyConfiguration/help-name.html
@@ -6,8 +6,8 @@
to install plugins on behalf of you. Note that Jenkins uses HTTPS to communicate with the update center to download plugins.
- The value you submit here will be set as http.proxyHost system property. Leaving this field empty
- causes this property to be unset, which means Jenkins will try to connect to the host directly.
+ Leaving this field empty
+ means Jenkins will try to connect to the internet directly.
If you are unsure about the value, check the browser proxy configuration.
diff --git a/core/src/main/resources/hudson/ProxyConfiguration/help-name_de.html b/core/src/main/resources/hudson/ProxyConfiguration/help-name_de.html
index fb15d38b12778793c74f35569cf2f7e3dd45c06f..ebab2d935fd1e410f23fa0ce1623b83c9fe8fe0d 100644
--- a/core/src/main/resources/hudson/ProxyConfiguration/help-name_de.html
+++ b/core/src/main/resources/hudson/ProxyConfiguration/help-name_de.html
@@ -9,11 +9,9 @@
und Plugins herunterzuladen.
- Der Wert, den Sie hier angeben, wird als Systemeigenschaft http.proxyHost
- gesetzt. Wenn Sie dieses Feld freilassen, wird die Systemeigenschaft gelöscht.
- Jenkins versucht dann, das Update-Center direkt zu kontaktieren.
+ Wenn Sie dieses Feld freilassen, versucht Jenkins, das Update-Center direkt zu kontaktieren.
Wenn Sie sich nicht sicher sind, was Sie hier eintragen sollen, schauen Sie
in der Proxy-Konfiguration Ihres Browsers nach.
-
\ No newline at end of file
+
diff --git a/core/src/main/resources/hudson/ProxyConfiguration/help-name_fr.html b/core/src/main/resources/hudson/ProxyConfiguration/help-name_fr.html
index 7914a1700d3cdee5f2953f76135f9447c6d298cc..3c7b727592dfc5b4c826b8ca3226fd2513669e40 100644
--- a/core/src/main/resources/hudson/ProxyConfiguration/help-name_fr.html
+++ b/core/src/main/resources/hudson/ProxyConfiguration/help-name_fr.html
@@ -7,8 +7,7 @@
Notez que Jenkins utilise HTTPS pour communiquer avec le serveur de mise à jour pour télécharger les plugins.
- La valeur placée ici sera positionnée dans la propriété système http.proxyHost.
- Si ce champ est vide, la propriété ne sera pas positionnée; Jenkins tentera donc de se
+ Si ce champ est vide Jenkins tentera de se
connecter directement au site.
- If your Jenkins server sits behind a firewall and does not have the direct access to the internet,
- and if your server JVM is not configured appropriately
- (See JDK networking properties for more details)
- to enable internet connection, you can specify the HTTP proxy server name in this field to allow Jenkins
- to install plugins on behalf of you. Note that Jenkins uses HTTPS to communicate with the update center to download plugins.
-
-
- The value you submit here will be set as http.proxyHost system property. Leaving this field empty
- causes this property to be unset, which means Jenkins will try to connect to the host directly.
-
-
- If you are unsure about the value, check the browser proxy configuration.
-
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/model/Messages_pl.properties b/core/src/main/resources/hudson/model/Messages_pl.properties
index 5aa577d5eb334b2b3d67129308abdc253fd74ba9..87f49975e9a79169c91df61ede446d52526f56ef 100644
--- a/core/src/main/resources/hudson/model/Messages_pl.properties
+++ b/core/src/main/resources/hudson/model/Messages_pl.properties
@@ -38,7 +38,7 @@ Run.Summary.BrokenSinceThisBuild=nieudany od tego zadania
Run.Summary.BrokenSince=nieudany od zadania {0}
Run.Summary.Unknown=?
-UpdateCenter.DownloadButNotActivated=Pobrana pomy\u015Blnie. Zostanie w\u0142\u0105czony podczas kolejnego restartu Jenkinsa
+UpdateCenter.DownloadButNotActivated=Pobrana pomy\u015Blnie. Zostanie w\u0142\u0105czona podczas ponownego uruchomienia Jenkinsa
UpdateCenter.Status.CheckingInternet=Sprawdzanie po\u0142\u0105czenia z Internetem
UpdateCenter.Status.CheckingJavaNet=Sprawdzanie po\u0142\u0105czenia z centrum aktualizacji
UpdateCenter.Status.Success=Zako\u0144czono pomy\u015Blnie
diff --git a/core/src/main/resources/hudson/model/View/AsynchPeople/index_pl.properties b/core/src/main/resources/hudson/model/View/AsynchPeople/index_pl.properties
index 42d6479f65e58456710e68d7f5341e21a96cbf22..0c78d03b5ab3f64b4a56acef58cd5aa50faa3cae 100644
--- a/core/src/main/resources/hudson/model/View/AsynchPeople/index_pl.properties
+++ b/core/src/main/resources/hudson/model/View/AsynchPeople/index_pl.properties
@@ -26,5 +26,5 @@ On=Na
People=U\u017Cytkownicy
User\ Id=Identyfikator u\u017Cytkownika
Last\ Commit\ Activity=Ostatnia zmiana
-blurb=Prezentuje wszystkich uwierzytelnionych u\u017Cytkownik\u00F3w w\u0142\u0105cznie z identyfikuj\u0105cym loginem oraz osoby wymienione w opisach zarejestrowanych zmianach.
+blurb=Prezentuje wszystkich uwierzytelnionych u\u017Cytkownik\u00F3w w\u0142\u0105cznie z identyfikatorem oraz osoby wymienione w opisach zarejestrowanych zmian.
diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html
index 4ce69c94b967bc3055369d7a2929e3192164d259..d097fc419375b46c8af07f31023ec0f1b86fdb7c 100644
--- a/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html
+++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html
@@ -73,7 +73,7 @@
H/15 * * * *
# every ten minutes in the first half of every hour (three times, perhaps at :04, :14, :24)
H(0-29)/10 * * * *
-# once every two hours every weekday (perhaps at 10:38 AM, 12:38 PM, 2:38 PM, 4:38 PM)
+# once every two hours every weekday (perhaps at 9:38 AM, 11:38 AM, 1:38 PM, 3:38 PM)
H 9-16/2 * * 1-5
# once a day on the 1st and 15th of every month except December
H H 1,15 1-11 *
diff --git a/pom.xml b/pom.xml
index e548d13d6f304c08184cdb8f7d59f6edbe3ccf5b..8840184e5b3bb6ba26ef00f8d237654d90b975e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -179,7 +179,7 @@ THE SOFTWARE.
org.jenkins-ci.mainremoting
- 2.53.2
+ 2.53.3
diff --git a/test/pom.xml b/test/pom.xml
index 0c3e904f90f3ff232c3005cd9cb392ae29433d3b..0e86989eb8b22adf6075669d07680e978e0a4008 100644
--- a/test/pom.xml
+++ b/test/pom.xml
@@ -63,7 +63,7 @@ THE SOFTWARE.
${project.groupId}jenkins-test-harness
- 2.2
+ 2.5test
diff --git a/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java b/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java
index dec8e9d7663cd60564f1b47ec7c0e3e043b38750..1ae807ba07f75ccb84b3f89ca4887397244efb62 100644
--- a/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java
+++ b/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java
@@ -38,8 +38,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
@@ -147,9 +149,18 @@ public class ComputerConfigDotXmlTest {
@Override
public void write(int b) throws IOException {
-
baos.write(b);
}
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ throw new UnsupportedOperationException();
+ }
});
return baos;
@@ -162,15 +173,28 @@ public class ComputerConfigDotXmlTest {
private final InputStream inner;
public Stream(final InputStream inner) {
-
this.inner = inner;
}
@Override
public int read() throws IOException {
-
return inner.read();
}
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ throw new UnsupportedOperationException();
+ }
}
return new Stream(Computer.class.getResourceAsStream(name));
diff --git a/test/src/test/java/hudson/model/QueueTest.java b/test/src/test/java/hudson/model/QueueTest.java
index f8baa17890befc578d203aa10b060bcf194bdaaa..2a91020de226c64a21f9fc89e0dd0a2ef5b5a090 100644
--- a/test/src/test/java/hudson/model/QueueTest.java
+++ b/test/src/test/java/hudson/model/QueueTest.java
@@ -103,6 +103,10 @@ import org.apache.commons.io.FileUtils;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@@ -114,10 +118,6 @@ import org.jvnet.hudson.test.SleepBuilder;
import org.jvnet.hudson.test.TestBuilder;
import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.recipes.LocalData;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
/**
* @author Kohsuke Kawaguchi
@@ -283,12 +283,12 @@ public class QueueTest {
Server server = new Server();
- SocketConnector connector = new SocketConnector();
+ ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(new ServletHolder(new FileItemPersistenceTestServlet()),"/");
- server.addHandler(handler);
+ server.setHandler(handler);
server.start();
@@ -581,7 +581,6 @@ public class QueueTest {
* Make sure that the running build actually carries an credential.
*/
@Test public void accessControl() throws Exception {
- r.configureUserRealm();
FreeStyleProject p = r.createFreeStyleProject();
QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(p.getFullName(), alice)));
p.getBuildersList().add(new TestBuilder() {
@@ -608,7 +607,6 @@ public class QueueTest {
DumbSlave s1 = r.createSlave();
DumbSlave s2 = r.createSlave();
- r.configureUserRealm();
FreeStyleProject p = r.createFreeStyleProject();
QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(p.getFullName(), alice)));
p.getBuildersList().add(new TestBuilder() {
diff --git a/test/src/test/java/hudson/model/UpdateSiteTest.java b/test/src/test/java/hudson/model/UpdateSiteTest.java
index 1f0971ea628c5f8c673e300ce7c718e447f7025e..8e22d43f769a20cfb238b655314dcbef76b4d7fd 100644
--- a/test/src/test/java/hudson/model/UpdateSiteTest.java
+++ b/test/src/test/java/hudson/model/UpdateSiteTest.java
@@ -42,15 +42,16 @@ import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.*;
import org.apache.commons.io.FileUtils;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
-import org.mortbay.jetty.HttpConnection;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.handler.AbstractHandler;
public class UpdateSiteTest {
@@ -76,18 +77,17 @@ public class UpdateSiteTest {
@Before
public void setUpWebServer() throws Exception {
server = new Server();
- SocketConnector connector = new SocketConnector();
+ ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
server.setHandler(new AbstractHandler() {
- public void handle(String target, HttpServletRequest request,
- HttpServletResponse response, int dispatch) throws IOException,
- ServletException {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (target.startsWith(RELATIVE_BASE)) {
target = target.substring(RELATIVE_BASE.length());
}
String responseBody = getResource(target);
if (responseBody != null) {
- HttpConnection.getCurrentConnection().getRequest().setHandled(true);
+ baseRequest.setHandled(true);
response.setContentType("text/plain; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().write(responseBody.getBytes());
diff --git a/test/src/test/java/hudson/util/XStream2Security247Test.java b/test/src/test/java/hudson/util/XStream2Security247Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..d67434495f1e276d5d78dbea72d22ba0f030e746
--- /dev/null
+++ b/test/src/test/java/hudson/util/XStream2Security247Test.java
@@ -0,0 +1,136 @@
+package hudson.util;
+
+import hudson.model.Items;
+import org.apache.commons.io.*;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.when;
+
+public class XStream2Security247Test {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Rule
+ public TemporaryFolder f = new TemporaryFolder();
+
+ @Mock
+ private StaplerRequest req;
+
+ @Mock
+ private StaplerResponse rsp;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ @Issue("SECURITY-247")
+ public void testXmlLoad() throws Exception {
+ File exploitFile = f.newFile();
+ try {
+ // be extra sure there's no file already
+ if (exploitFile.exists() && !exploitFile.delete()) {
+ throw new IllegalStateException("file exists and cannot be deleted");
+ }
+ File tempJobDir = new File(j.jenkins.getRootDir(), "security247");
+
+ String exploitXml = org.apache.commons.io.IOUtils.toString(
+ XStream2Security247Test.class.getResourceAsStream(
+ "/hudson/util/XStream2Security247Test/config.xml"), "UTF-8");
+
+ exploitXml = exploitXml.replace("@TOKEN@", exploitFile.getAbsolutePath());
+
+ FileUtils.write(new File(tempJobDir, "config.xml"), exploitXml);
+
+ try {
+ Items.load(j.jenkins, tempJobDir);
+ } catch (Exception e) {
+ // ignore
+ }
+ assertFalse("no file should be created here", exploitFile.exists());
+ } finally {
+ exploitFile.delete();
+ }
+ }
+
+ @Test
+ @Issue("SECURITY-247")
+ public void testPostJobXml() throws Exception {
+ File exploitFile = f.newFile();
+ try {
+ // be extra sure there's no file already
+ if (exploitFile.exists() && !exploitFile.delete()) {
+ throw new IllegalStateException("file exists and cannot be deleted");
+ }
+ File tempJobDir = new File(j.jenkins.getRootDir(), "security247");
+
+ String exploitXml = org.apache.commons.io.IOUtils.toString(
+ XStream2Security247Test.class.getResourceAsStream(
+ "/hudson/util/XStream2Security247Test/config.xml"), "UTF-8");
+
+ exploitXml = exploitXml.replace("@TOKEN@", exploitFile.getAbsolutePath());
+
+ when(req.getMethod()).thenReturn("POST");
+ when(req.getInputStream()).thenReturn(new Stream(IOUtils.toInputStream(exploitXml)));
+ when(req.getContentType()).thenReturn("application/xml");
+ when(req.getParameter("name")).thenReturn("foo");
+
+ try {
+ j.jenkins.doCreateItem(req, rsp);
+ } catch (Exception e) {
+ // don't care
+ }
+
+ assertFalse("no file should be created here", exploitFile.exists());
+ } finally {
+ exploitFile.delete();
+ }
+ }
+
+ private static class Stream extends ServletInputStream {
+ private final InputStream inner;
+
+ public Stream(final InputStream inner) {
+ this.inner = inner;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return inner.read();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/test/src/test/java/jenkins/security/Security232Test.java b/test/src/test/java/jenkins/security/Security232Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..26ff457c4a655bc36c80f1b45337ba77bd698715
--- /dev/null
+++ b/test/src/test/java/jenkins/security/Security232Test.java
@@ -0,0 +1,256 @@
+package jenkins.security;
+
+import hudson.remoting.Callable;
+import hudson.remoting.Channel;
+import hudson.remoting.Channel.Mode;
+import hudson.remoting.ChannelBuilder;
+import hudson.remoting.ClassFilter;
+import hudson.remoting.JarLoader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.rmi.activation.ActivationDesc;
+import java.rmi.activation.ActivationID;
+import java.rmi.activation.ActivationInstantiator;
+import java.rmi.server.ObjID;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javax.net.SocketFactory;
+import static jenkins.security.security218.Payload.CommonsCollections1;
+import jenkins.security.security218.ysoserial.payloads.CommonsCollections1;
+import jenkins.security.security218.ysoserial.payloads.ObjectPayload;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import sun.reflect.ReflectionFactory;
+import sun.rmi.server.ActivationGroupImpl;
+import sun.rmi.server.UnicastRef2;
+import sun.rmi.server.Util;
+import sun.rmi.transport.LiveRef;
+import sun.rmi.transport.TransportConstants;
+import sun.rmi.transport.tcp.TCPEndpoint;
+
+/**
+ * @author mbechler, adapted for JUnit/JenkinsRule by jglick
+ */
+@Issue("SECURITY-232")
+public class Security232Test {
+
+ @Rule
+ public JenkinsRule r = new JenkinsRule();
+
+ @Test
+ public void commonsCollections1() throws Exception {
+ File pwned = new File(r.jenkins.getRootDir(), "pwned");
+
+ int jrmpPort = 12345;
+ URL u = r.getURL();
+
+ HttpURLConnection hc = (HttpURLConnection) u.openConnection();
+ int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port"));
+
+ InetSocketAddress isa = new InetSocketAddress(u.getHost(), clip);
+ Socket s = null;
+ Channel c = null;
+ try {
+ System.err.println("* Opening socket " + isa);
+ s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort());
+ s.setKeepAlive(true);
+ s.setTcpNoDelay(true);
+
+ System.err.println("* Opening channel");
+ OutputStream outputStream = s.getOutputStream();
+
+ DataOutputStream dos = new DataOutputStream(outputStream);
+
+ dos.writeUTF("Protocol:CLI-connect");
+
+ ExecutorService cp = Executors.newCachedThreadPool();
+ c = new ChannelBuilder("EXPLOIT", cp).withMode(Mode.BINARY).build(s.getInputStream(), outputStream);
+
+ System.err.println("* Channel open");
+
+ Class> reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest");
+
+ Constructor> reqCons = reqClass.getDeclaredConstructor(int.class, Method.class, Object[].class);
+ reqCons.setAccessible(true);
+
+ Object getJarLoader = reqCons
+ .newInstance(1, Class.forName("hudson.remoting.IChannel").getMethod("getProperty", Object.class), new Object[] {
+ JarLoader.class.getName() + ".ours"
+ });
+
+ Object call = c.call((Callable