diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index f367f4964c..4b2622e6ec 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -617,7 +617,7 @@ public final class FilePath implements Serializable { act(new SecureFileCallable() { private static final long serialVersionUID = 1L; public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - writing(f); + symlinking(f); Util.createSymlink(f.getParentFile(),target,f.getName(),listener); return null; } @@ -2692,6 +2692,17 @@ public final class FilePath implements Serializable { return f; } + /** + * Pass through 'f' after ensuring that we can create that symlink. + */ + private File symlinking(File f) { + FilePathFilter filter = filterNonNull(); + if (!f.exists()) + filter.create(f); + filter.symlink(f); + return f; + } + /** * Pass through 'f' after ensuring that we can delete that file. */ diff --git a/core/src/main/java/jenkins/FilePathFilter.java b/core/src/main/java/jenkins/FilePathFilter.java index 8d008e446a..4a1ed5337c 100644 --- a/core/src/main/java/jenkins/FilePathFilter.java +++ b/core/src/main/java/jenkins/FilePathFilter.java @@ -40,6 +40,13 @@ public abstract class FilePathFilter { */ public boolean write(File f) throws SecurityException { return false; } + /** + * Checks if a symlink can be created at 'f' + * + * On POSIX, this corresponds to the 'w' permission of the file itself. + */ + public boolean symlink(File f) throws SecurityException { return false; } + /** * Checks if the given directory can be created. * diff --git a/core/src/main/java/jenkins/FilePathFilterAggregator.java b/core/src/main/java/jenkins/FilePathFilterAggregator.java index 859d36d3a3..8bf4949e17 100644 --- a/core/src/main/java/jenkins/FilePathFilterAggregator.java +++ b/core/src/main/java/jenkins/FilePathFilterAggregator.java @@ -99,6 +99,15 @@ class FilePathFilterAggregator extends FilePathFilter { return defaultAction(); } + @Override + public boolean symlink(File f) throws SecurityException { + for (Entry e : all) { + if (e.filter.symlink(f)) + return true; + } + return defaultAction(); + } + @Override public boolean create(File f) throws SecurityException { for (Entry e : all) { diff --git a/core/src/main/java/jenkins/ReflectiveFilePathFilter.java b/core/src/main/java/jenkins/ReflectiveFilePathFilter.java index 9905524431..ce963943cd 100644 --- a/core/src/main/java/jenkins/ReflectiveFilePathFilter.java +++ b/core/src/main/java/jenkins/ReflectiveFilePathFilter.java @@ -26,6 +26,11 @@ public abstract class ReflectiveFilePathFilter extends FilePathFilter { return op("write", f); } + @Override + public boolean symlink(File f) throws SecurityException { + return op("symlink",f); + } + @Override public boolean mkdirs(File f) throws SecurityException { return op("mkdirs", f); diff --git a/core/src/main/java/jenkins/SoloFilePathFilter.java b/core/src/main/java/jenkins/SoloFilePathFilter.java index 419b50b0d9..c2cfa58ef0 100644 --- a/core/src/main/java/jenkins/SoloFilePathFilter.java +++ b/core/src/main/java/jenkins/SoloFilePathFilter.java @@ -42,6 +42,11 @@ public final class SoloFilePathFilter extends FilePathFilter { return noFalse("write",f,base.write(f)); } + @Override + public boolean symlink(File f) throws SecurityException { + return noFalse("symlink",f,base.write(f)); + } + @Override public boolean mkdirs(File f) throws SecurityException { return noFalse("mkdirs",f,base.mkdirs(f)); diff --git a/core/src/main/resources/jenkins/security/admin/filepath-filter.conf b/core/src/main/resources/jenkins/security/admin/filepath-filter.conf index 7ba39ef0ef..faa7cffa16 100644 --- a/core/src/main/resources/jenkins/security/admin/filepath-filter.conf +++ b/core/src/main/resources/jenkins/security/admin/filepath-filter.conf @@ -30,10 +30,11 @@ deny all /build.xml # - cobertura writes coverage.xml # - violations writes violations.xml and other content under violations/ # - dependency-check writes archive/artifacts.txt -allow all /.+ +# But not allowing deletion to prevent data loss and symlink to prevent jailbreaking. +allow create,mkdirs,read,stat,write /.+ # cobertura also writes out annotated sources to a dir under the job: -allow all /jobs/.+/cobertura.* +allow create,mkdirs,read,stat,write /jobs/.+/cobertura.* # all the other accesses that aren't specified here will be left upto other rules in this directory. # if no rules in those other files matches, then the access will be rejected.