Merge pull request #15055 from rmuir/fun_with_windows

set ActiveProcessLimit=1 on windows
This commit is contained in:
Robert Muir 2015-11-27 16:57:34 -05:00
commit 29853744a3
3 changed files with 140 additions and 4 deletions

View File

@ -217,4 +217,88 @@ final class JNAKernel32Library {
* @return true if the function succeeds. * @return true if the function succeeds.
*/ */
native boolean CloseHandle(Pointer handle); 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<String> 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);
} }

View File

@ -47,7 +47,7 @@ import java.util.Map;
* Installs a limited form of secure computing mode, * Installs a limited form of secure computing mode,
* to filters system calls to block process execution. * to filters system calls to block process execution.
* <p> * <p>
* 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.
* <p> * <p>
* On Linux it currently supports amd64 and i386 architectures, requires Linux kernel 3.5 or above, and requires * 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. * {@code CONFIG_SECCOMP} and {@code CONFIG_SECCOMP_FILTER} compiled into the kernel.
@ -80,6 +80,8 @@ import java.util.Map;
* <li>{@code process-exec}</li> * <li>{@code process-exec}</li>
* </ul> * </ul>
* <p> * <p>
* On Windows, process creation is restricted with {@code SetInformationJobObject/ActiveProcessLimit}.
* <p>
* This is not intended as a sandbox. It is another level of security, mostly intended to annoy * 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. * security researchers and make their lives more difficult in achieving "remote execution" exploits.
* @see <a href="http://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt"> * @see <a href="http://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt">
@ -329,7 +331,8 @@ final class Seccomp {
case 1: break; // already set by caller case 1: break; // already set by caller
default: default:
int errno = Native.getLastError(); 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"); throw new UnsupportedOperationException("seccomp unavailable: requires kernel 3.5+ with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in");
} else { } else {
throw new UnsupportedOperationException("prctl(PR_GET_NO_NEW_PRIVS): " + JNACLibrary.strerror(errno)); 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"); 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. * Attempt to drop the capability to execute for the process.
* <p> * <p>
@ -581,6 +626,9 @@ final class Seccomp {
} else if (Constants.FREE_BSD || OPENBSD) { } else if (Constants.FREE_BSD || OPENBSD) {
bsdImpl(); bsdImpl();
return 1; return 1;
} else if (Constants.WINDOWS) {
windowsImpl();
return 1;
} else { } else {
throw new UnsupportedOperationException("syscall filtering not supported for OS: '" + Constants.OS_NAME + "'"); throw new UnsupportedOperationException("syscall filtering not supported for OS: '" + Constants.OS_NAME + "'");
} }

View File

@ -19,11 +19,15 @@
package org.elasticsearch.bootstrap; package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
/** Simple tests seccomp filter is working. */ /** Simple tests seccomp filter is working. */
public class SeccompTests extends ESTestCase { public class SeccompTests extends ESTestCase {
/** command to try to run in tests */
static final String EXECUTABLE = Constants.WINDOWS ? "calc" : "ls";
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
@ -44,7 +48,7 @@ public class SeccompTests extends ESTestCase {
public void testNoExecution() throws Exception { public void testNoExecution() throws Exception {
try { try {
Runtime.getRuntime().exec("ls"); Runtime.getRuntime().exec(EXECUTABLE);
fail("should not have been able to execute!"); fail("should not have been able to execute!");
} catch (Exception expected) { } catch (Exception expected) {
// we can't guarantee how its converted, currently its an IOException, like this: // we can't guarantee how its converted, currently its an IOException, like this:
@ -70,7 +74,7 @@ public class SeccompTests extends ESTestCase {
@Override @Override
public void run() { public void run() {
try { try {
Runtime.getRuntime().exec("ls"); Runtime.getRuntime().exec(EXECUTABLE);
fail("should not have been able to execute!"); fail("should not have been able to execute!");
} catch (Exception expected) { } catch (Exception expected) {
// ok // ok