diff --git a/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java b/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java index 8924812e6d6..26e485802f4 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java @@ -217,4 +217,88 @@ final class JNAKernel32Library { * @return true if the function succeeds. */ native boolean CloseHandle(Pointer handle); + + /** + * Creates or opens a new job object + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682409%28v=vs.85%29.aspx + * + * @param jobAttributes security attributes + * @param name job name + * @return job handle if the function succeeds + */ + native Pointer CreateJobObjectW(Pointer jobAttributes, String name); + + /** + * Associates a process with an existing job + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms681949%28v=vs.85%29.aspx + * + * @param job job handle + * @param process process handle + * @return true if the function succeeds + */ + native boolean AssignProcessToJobObject(Pointer job, Pointer process); + + /** + * Basic limit information for a job object + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684147%28v=vs.85%29.aspx + */ + public static class JOBOBJECT_BASIC_LIMIT_INFORMATION extends Structure implements Structure.ByReference { + public long PerProcessUserTimeLimit; + public long PerJobUserTimeLimit; + public int LimitFlags; + public SizeT MinimumWorkingSetSize; + public SizeT MaximumWorkingSetSize; + public int ActiveProcessLimit; + public Pointer Affinity; + public int PriorityClass; + public int SchedulingClass; + + @Override + protected List getFieldOrder() { + return Arrays.asList(new String[] { + "PerProcessUserTimeLimit", "PerJobUserTimeLimit", "LimitFlags", "MinimumWorkingSetSize", + "MaximumWorkingSetSize", "ActiveProcessLimit", "Affinity", "PriorityClass", "SchedulingClass" + }); + } + } + + /** + * Constant for JOBOBJECT_BASIC_LIMIT_INFORMATION in Query/Set InformationJobObject + */ + static final int JOBOBJECT_BASIC_LIMIT_INFORMATION_CLASS = 2; + + /** + * Constant for LimitFlags, indicating a process limit has been set + */ + static final int JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 8; + + /** + * Get job limit and state information + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684925%28v=vs.85%29.aspx + * + * @param job job handle + * @param infoClass information class constant + * @param info pointer to information structure + * @param infoLength size of information structure + * @param returnLength length of data written back to structure (or null if not wanted) + * @return true if the function succeeds + */ + native boolean QueryInformationJobObject(Pointer job, int infoClass, Pointer info, int infoLength, Pointer returnLength); + + /** + * Set job limit and state information + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms686216%28v=vs.85%29.aspx + * + * @param job job handle + * @param infoClass information class constant + * @param info pointer to information structure + * @param infoLength size of information structure + * @return true if the function succeeds + */ + native boolean SetInformationJobObject(Pointer job, int infoClass, Pointer info, int infoLength); } diff --git a/core/src/main/java/org/elasticsearch/bootstrap/Seccomp.java b/core/src/main/java/org/elasticsearch/bootstrap/Seccomp.java index 3c8186f4ce1..9a4a26c74e3 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/Seccomp.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/Seccomp.java @@ -47,7 +47,7 @@ import java.util.Map; * Installs a limited form of secure computing mode, * to filters system calls to block process execution. *

- * This is only supported on the Linux, Solaris, FreeBSD, OpenBSD, and Mac OS X operating systems. + * This is supported on Linux, Solaris, FreeBSD, OpenBSD, Mac OS X, and Windows. *

* On Linux it currently supports amd64 and i386 architectures, requires Linux kernel 3.5 or above, and requires * {@code CONFIG_SECCOMP} and {@code CONFIG_SECCOMP_FILTER} compiled into the kernel. @@ -80,6 +80,8 @@ import java.util.Map; *

  • {@code process-exec}
  • * *

    + * On Windows, process creation is restricted with {@code SetInformationJobObject/ActiveProcessLimit}. + *

    * This is not intended as a sandbox. It is another level of security, mostly intended to annoy * security researchers and make their lives more difficult in achieving "remote execution" exploits. * @see @@ -329,7 +331,8 @@ final class Seccomp { case 1: break; // already set by caller default: int errno = Native.getLastError(); - if (errno == ENOSYS) { + if (errno == EINVAL) { + // friendly error, this will be the typical case for an old kernel throw new UnsupportedOperationException("seccomp unavailable: requires kernel 3.5+ with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in"); } else { throw new UnsupportedOperationException("prctl(PR_GET_NO_NEW_PRIVS): " + JNACLibrary.strerror(errno)); @@ -561,6 +564,48 @@ final class Seccomp { logger.debug("BSD RLIMIT_NPROC initialization successful"); } + // windows impl via job ActiveProcessLimit + + static void windowsImpl() { + if (!Constants.WINDOWS) { + throw new IllegalStateException("bug: should not be trying to initialize ActiveProcessLimit for an unsupported OS"); + } + + JNAKernel32Library lib = JNAKernel32Library.getInstance(); + + // create a new Job + Pointer job = lib.CreateJobObjectW(null, null); + if (job == null) { + throw new UnsupportedOperationException("CreateJobObject: " + Native.getLastError()); + } + + try { + // retrieve the current basic limits of the job + int clazz = JNAKernel32Library.JOBOBJECT_BASIC_LIMIT_INFORMATION_CLASS; + JNAKernel32Library.JOBOBJECT_BASIC_LIMIT_INFORMATION limits = new JNAKernel32Library.JOBOBJECT_BASIC_LIMIT_INFORMATION(); + limits.write(); + if (!lib.QueryInformationJobObject(job, clazz, limits.getPointer(), limits.size(), null)) { + throw new UnsupportedOperationException("QueryInformationJobObject: " + Native.getLastError()); + } + limits.read(); + // modify the number of active processes to be 1 (exactly the one process we will add to the job). + limits.ActiveProcessLimit = 1; + limits.LimitFlags = JNAKernel32Library.JOB_OBJECT_LIMIT_ACTIVE_PROCESS; + limits.write(); + if (!lib.SetInformationJobObject(job, clazz, limits.getPointer(), limits.size())) { + throw new UnsupportedOperationException("SetInformationJobObject: " + Native.getLastError()); + } + // assign ourselves to the job + if (!lib.AssignProcessToJobObject(job, lib.GetCurrentProcess())) { + throw new UnsupportedOperationException("AssignProcessToJobObject: " + Native.getLastError()); + } + } finally { + lib.CloseHandle(job); + } + + logger.debug("Windows ActiveProcessLimit initialization successful"); + } + /** * Attempt to drop the capability to execute for the process. *

    @@ -581,6 +626,9 @@ final class Seccomp { } else if (Constants.FREE_BSD || OPENBSD) { bsdImpl(); return 1; + } else if (Constants.WINDOWS) { + windowsImpl(); + return 1; } else { throw new UnsupportedOperationException("syscall filtering not supported for OS: '" + Constants.OS_NAME + "'"); } diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SeccompTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SeccompTests.java index da0530fca4a..a319aaabb70 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SeccompTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SeccompTests.java @@ -19,11 +19,15 @@ package org.elasticsearch.bootstrap; +import org.apache.lucene.util.Constants; import org.elasticsearch.test.ESTestCase; /** Simple tests seccomp filter is working. */ public class SeccompTests extends ESTestCase { + /** command to try to run in tests */ + static final String EXECUTABLE = Constants.WINDOWS ? "calc" : "ls"; + @Override public void setUp() throws Exception { super.setUp(); @@ -44,7 +48,7 @@ public class SeccompTests extends ESTestCase { public void testNoExecution() throws Exception { try { - Runtime.getRuntime().exec("ls"); + Runtime.getRuntime().exec(EXECUTABLE); fail("should not have been able to execute!"); } catch (Exception expected) { // we can't guarantee how its converted, currently its an IOException, like this: @@ -70,7 +74,7 @@ public class SeccompTests extends ESTestCase { @Override public void run() { try { - Runtime.getRuntime().exec("ls"); + Runtime.getRuntime().exec(EXECUTABLE); fail("should not have been able to execute!"); } catch (Exception expected) { // ok