diff --git a/.buildkite/pipelines/pull-request/bwc-snapshots-windows.yml b/.buildkite/pipelines/pull-request/bwc-snapshots-windows.yml
deleted file mode 100644
index d37bdf380f926..0000000000000
--- a/.buildkite/pipelines/pull-request/bwc-snapshots-windows.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-config:
- allow-labels: test-windows
-steps:
- - group: bwc-snapshots-windows
- steps:
- - label: "{{matrix.BWC_VERSION}} / bwc-snapshots-windows"
- key: "bwc-snapshots-windows"
- command: .\.buildkite\scripts\run-script.ps1 bash .buildkite/scripts/windows-run-gradle.sh
- env:
- GRADLE_TASK: "v{{matrix.BWC_VERSION}}#bwcTest"
- timeout_in_minutes: 300
- matrix:
- setup:
- BWC_VERSION: $SNAPSHOT_BWC_VERSIONS
- agents:
- provider: gcp
- image: family/elasticsearch-windows-2022
- machineType: custom-32-98304
- diskType: pd-ssd
- diskSizeGb: 350
diff --git a/docs/changelog/107047.yaml b/docs/changelog/107047.yaml
new file mode 100644
index 0000000000000..89caed6f55074
--- /dev/null
+++ b/docs/changelog/107047.yaml
@@ -0,0 +1,6 @@
+pr: 107047
+summary: "Search/Mapping: KnnVectorQueryBuilder support for allowUnmappedFields"
+area: Search
+type: bug
+issues:
+ - 106846
diff --git a/docs/changelog/110527.yaml b/docs/changelog/110527.yaml
new file mode 100644
index 0000000000000..3ab19ecaaaa76
--- /dev/null
+++ b/docs/changelog/110527.yaml
@@ -0,0 +1,5 @@
+pr: 110527
+summary: "ESQL: Add boolean support to Max and Min aggs"
+area: ES|QL
+type: feature
+issues: []
diff --git a/docs/changelog/110651.yaml b/docs/changelog/110651.yaml
new file mode 100644
index 0000000000000..c25c63ee0284a
--- /dev/null
+++ b/docs/changelog/110651.yaml
@@ -0,0 +1,5 @@
+pr: 110651
+summary: "Remove `default_field: message` from metrics index templates"
+area: Data streams
+type: enhancement
+issues: []
diff --git a/docs/changelog/110666.yaml b/docs/changelog/110666.yaml
new file mode 100644
index 0000000000000..d96f8e2024c81
--- /dev/null
+++ b/docs/changelog/110666.yaml
@@ -0,0 +1,5 @@
+pr: 110666
+summary: Removing the use of Stream::peek from `GeoIpDownloader::cleanDatabases`
+area: Ingest Node
+type: bug
+issues: []
diff --git a/docs/reference/esql/functions/description/max.asciidoc b/docs/reference/esql/functions/description/max.asciidoc
index ffc15dcd4c8bd..27a76ed69c3c0 100644
--- a/docs/reference/esql/functions/description/max.asciidoc
+++ b/docs/reference/esql/functions/description/max.asciidoc
@@ -2,4 +2,4 @@
*Description*
-The maximum value of a numeric field.
+The maximum value of a field.
diff --git a/docs/reference/esql/functions/description/min.asciidoc b/docs/reference/esql/functions/description/min.asciidoc
index 4f640854dbd37..406125b5761d1 100644
--- a/docs/reference/esql/functions/description/min.asciidoc
+++ b/docs/reference/esql/functions/description/min.asciidoc
@@ -2,4 +2,4 @@
*Description*
-The minimum value of a numeric field.
+The minimum value of a field.
diff --git a/docs/reference/esql/functions/kibana/definition/max.json b/docs/reference/esql/functions/kibana/definition/max.json
index aaa765ea79ce4..bc7380bd76dd4 100644
--- a/docs/reference/esql/functions/kibana/definition/max.json
+++ b/docs/reference/esql/functions/kibana/definition/max.json
@@ -2,12 +2,24 @@
"comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
"type" : "agg",
"name" : "max",
- "description" : "The maximum value of a numeric field.",
+ "description" : "The maximum value of a field.",
"signatures" : [
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
+ "type" : "boolean",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "boolean"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
"type" : "datetime",
"optional" : false,
"description" : ""
@@ -19,7 +31,7 @@
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
"type" : "double",
"optional" : false,
"description" : ""
@@ -31,7 +43,7 @@
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
"type" : "integer",
"optional" : false,
"description" : ""
@@ -43,7 +55,7 @@
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
"type" : "long",
"optional" : false,
"description" : ""
diff --git a/docs/reference/esql/functions/kibana/definition/min.json b/docs/reference/esql/functions/kibana/definition/min.json
index ff48c87ecb8ea..937391bf242ac 100644
--- a/docs/reference/esql/functions/kibana/definition/min.json
+++ b/docs/reference/esql/functions/kibana/definition/min.json
@@ -2,12 +2,24 @@
"comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
"type" : "agg",
"name" : "min",
- "description" : "The minimum value of a numeric field.",
+ "description" : "The minimum value of a field.",
"signatures" : [
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
+ "type" : "boolean",
+ "optional" : false,
+ "description" : ""
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "boolean"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
"type" : "datetime",
"optional" : false,
"description" : ""
@@ -19,7 +31,7 @@
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
"type" : "double",
"optional" : false,
"description" : ""
@@ -31,7 +43,7 @@
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
"type" : "integer",
"optional" : false,
"description" : ""
@@ -43,7 +55,7 @@
{
"params" : [
{
- "name" : "number",
+ "name" : "field",
"type" : "long",
"optional" : false,
"description" : ""
diff --git a/docs/reference/esql/functions/kibana/docs/max.md b/docs/reference/esql/functions/kibana/docs/max.md
index 9bda0fbbe972d..80e88885e7f34 100644
--- a/docs/reference/esql/functions/kibana/docs/max.md
+++ b/docs/reference/esql/functions/kibana/docs/max.md
@@ -3,7 +3,7 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
-->
### MAX
-The maximum value of a numeric field.
+The maximum value of a field.
```
FROM employees
diff --git a/docs/reference/esql/functions/kibana/docs/min.md b/docs/reference/esql/functions/kibana/docs/min.md
index 100abf0260d0d..38d13b97fd344 100644
--- a/docs/reference/esql/functions/kibana/docs/min.md
+++ b/docs/reference/esql/functions/kibana/docs/min.md
@@ -3,7 +3,7 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
-->
### MIN
-The minimum value of a numeric field.
+The minimum value of a field.
```
FROM employees
diff --git a/docs/reference/esql/functions/parameters/max.asciidoc b/docs/reference/esql/functions/parameters/max.asciidoc
index 91c56709d182a..8903aa1a472a3 100644
--- a/docs/reference/esql/functions/parameters/max.asciidoc
+++ b/docs/reference/esql/functions/parameters/max.asciidoc
@@ -2,5 +2,5 @@
*Parameters*
-`number`::
+`field`::
diff --git a/docs/reference/esql/functions/parameters/min.asciidoc b/docs/reference/esql/functions/parameters/min.asciidoc
index 91c56709d182a..8903aa1a472a3 100644
--- a/docs/reference/esql/functions/parameters/min.asciidoc
+++ b/docs/reference/esql/functions/parameters/min.asciidoc
@@ -2,5 +2,5 @@
*Parameters*
-`number`::
+`field`::
diff --git a/docs/reference/esql/functions/signature/max.svg b/docs/reference/esql/functions/signature/max.svg
index cfc7bfda2c0a0..dda43dfbfbba2 100644
--- a/docs/reference/esql/functions/signature/max.svg
+++ b/docs/reference/esql/functions/signature/max.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/reference/esql/functions/signature/min.svg b/docs/reference/esql/functions/signature/min.svg
index 31660b1490e7e..e654d3027fee8 100644
--- a/docs/reference/esql/functions/signature/min.svg
+++ b/docs/reference/esql/functions/signature/min.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/reference/esql/functions/types/max.asciidoc b/docs/reference/esql/functions/types/max.asciidoc
index cec61a56db87a..6515c6bfc48d2 100644
--- a/docs/reference/esql/functions/types/max.asciidoc
+++ b/docs/reference/esql/functions/types/max.asciidoc
@@ -4,7 +4,8 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
-number | result
+field | result
+boolean | boolean
datetime | datetime
double | double
integer | integer
diff --git a/docs/reference/esql/functions/types/min.asciidoc b/docs/reference/esql/functions/types/min.asciidoc
index cec61a56db87a..6515c6bfc48d2 100644
--- a/docs/reference/esql/functions/types/min.asciidoc
+++ b/docs/reference/esql/functions/types/min.asciidoc
@@ -4,7 +4,8 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
-number | result
+field | result
+boolean | boolean
datetime | datetime
double | double
integer | integer
diff --git a/docs/reference/search/multi-search-template-api.asciidoc b/docs/reference/search/multi-search-template-api.asciidoc
index c8eea52a6fd9b..b1c9518b1f2bc 100644
--- a/docs/reference/search/multi-search-template-api.asciidoc
+++ b/docs/reference/search/multi-search-template-api.asciidoc
@@ -22,9 +22,6 @@ PUT _scripts/my-search-template
},
"from": "{{from}}",
"size": "{{size}}"
- },
- "params": {
- "query_string": "My query string"
}
}
}
diff --git a/docs/reference/search/render-search-template-api.asciidoc b/docs/reference/search/render-search-template-api.asciidoc
index 1f259dddf6879..0c782f26068e6 100644
--- a/docs/reference/search/render-search-template-api.asciidoc
+++ b/docs/reference/search/render-search-template-api.asciidoc
@@ -22,9 +22,6 @@ PUT _scripts/my-search-template
},
"from": "{{from}}",
"size": "{{size}}"
- },
- "params": {
- "query_string": "My query string"
}
}
}
diff --git a/docs/reference/search/search-template-api.asciidoc b/docs/reference/search/search-template-api.asciidoc
index 038396e558607..c60b5281c05e5 100644
--- a/docs/reference/search/search-template-api.asciidoc
+++ b/docs/reference/search/search-template-api.asciidoc
@@ -21,9 +21,6 @@ PUT _scripts/my-search-template
},
"from": "{{from}}",
"size": "{{size}}"
- },
- "params": {
- "query_string": "My query string"
}
}
}
diff --git a/docs/reference/search/search-your-data/search-template.asciidoc b/docs/reference/search/search-your-data/search-template.asciidoc
index 7a7f09f4a37a7..489a03c0a6a2a 100644
--- a/docs/reference/search/search-your-data/search-template.asciidoc
+++ b/docs/reference/search/search-your-data/search-template.asciidoc
@@ -42,9 +42,6 @@ PUT _scripts/my-search-template
},
"from": "{{from}}",
"size": "{{size}}"
- },
- "params": {
- "query_string": "My query string"
}
}
}
diff --git a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaKernel32Library.java b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaKernel32Library.java
index 0bfdf959f7b58..2c7ec70f36eb3 100644
--- a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaKernel32Library.java
+++ b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaKernel32Library.java
@@ -13,6 +13,7 @@
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
+import com.sun.jna.Structure.ByReference;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;
@@ -98,6 +99,38 @@ public long Type() {
}
}
+ /**
+ * 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 JnaJobObjectBasicLimitInformation extends Structure implements ByReference, JobObjectBasicLimitInformation {
+ public byte[] _ignore1 = new byte[16];
+ public int LimitFlags;
+ public byte[] _ignore2 = new byte[20];
+ public int ActiveProcessLimit;
+ public byte[] _ignore3 = new byte[20];
+
+ public JnaJobObjectBasicLimitInformation() {
+ super(8);
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return List.of("_ignore1", "LimitFlags", "_ignore2", "ActiveProcessLimit", "_ignore3");
+ }
+
+ @Override
+ public void setLimitFlags(int v) {
+ LimitFlags = v;
+ }
+
+ @Override
+ public void setActiveProcessLimit(int v) {
+ ActiveProcessLimit = v;
+ }
+ }
+
/**
* JNA adaptation of {@link ConsoleCtrlHandler}
*/
@@ -128,6 +161,20 @@ private interface NativeFunctions extends StdCallLibrary {
int GetShortPathNameW(WString lpszLongPath, char[] lpszShortPath, int cchBuffer);
boolean SetConsoleCtrlHandler(StdCallLibrary.StdCallCallback handler, boolean add);
+
+ Pointer CreateJobObjectW(Pointer jobAttributes, String name);
+
+ boolean AssignProcessToJobObject(Pointer job, Pointer process);
+
+ boolean QueryInformationJobObject(
+ Pointer job,
+ int infoClass,
+ JnaJobObjectBasicLimitInformation info,
+ int infoLength,
+ Pointer returnLength
+ );
+
+ boolean SetInformationJobObject(Pointer job, int infoClass, JnaJobObjectBasicLimitInformation info, int infoLength);
}
private final NativeFunctions functions;
@@ -197,4 +244,42 @@ public boolean SetConsoleCtrlHandler(ConsoleCtrlHandler handler, boolean add) {
consoleCtrlHandlerCallback = new NativeHandlerCallback(handler);
return functions.SetConsoleCtrlHandler(consoleCtrlHandlerCallback, true);
}
+
+ @Override
+ public Handle CreateJobObjectW() {
+ return new JnaHandle(functions.CreateJobObjectW(null, null));
+ }
+
+ @Override
+ public boolean AssignProcessToJobObject(Handle job, Handle process) {
+ assert job instanceof JnaHandle;
+ assert process instanceof JnaHandle;
+ var jnaJob = (JnaHandle) job;
+ var jnaProcess = (JnaHandle) process;
+ return functions.AssignProcessToJobObject(jnaJob.pointer, jnaProcess.pointer);
+ }
+
+ @Override
+ public JobObjectBasicLimitInformation newJobObjectBasicLimitInformation() {
+ return new JnaJobObjectBasicLimitInformation();
+ }
+
+ @Override
+ public boolean QueryInformationJobObject(Handle job, int infoClass, JobObjectBasicLimitInformation info) {
+ assert job instanceof JnaHandle;
+ assert info instanceof JnaJobObjectBasicLimitInformation;
+ var jnaJob = (JnaHandle) job;
+ var jnaInfo = (JnaJobObjectBasicLimitInformation) info;
+ var ret = functions.QueryInformationJobObject(jnaJob.pointer, infoClass, jnaInfo, jnaInfo.size(), null);
+ return ret;
+ }
+
+ @Override
+ public boolean SetInformationJobObject(Handle job, int infoClass, JobObjectBasicLimitInformation info) {
+ assert job instanceof JnaHandle;
+ assert info instanceof JnaJobObjectBasicLimitInformation;
+ var jnaJob = (JnaHandle) job;
+ var jnaInfo = (JnaJobObjectBasicLimitInformation) info;
+ return functions.SetInformationJobObject(jnaJob.pointer, infoClass, jnaInfo, jnaInfo.size());
+ }
}
diff --git a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaLinuxCLibrary.java b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaLinuxCLibrary.java
new file mode 100644
index 0000000000000..742c666d59c23
--- /dev/null
+++ b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaLinuxCLibrary.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.nativeaccess.jna;
+
+import com.sun.jna.Library;
+import com.sun.jna.Memory;
+import com.sun.jna.Native;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class JnaLinuxCLibrary implements LinuxCLibrary {
+
+ @Structure.FieldOrder({ "len", "filter" })
+ public static final class JnaSockFProg extends Structure implements Structure.ByReference, SockFProg {
+ public short len; // number of filters
+ public Pointer filter; // filters
+
+ JnaSockFProg(SockFilter filters[]) {
+ len = (short) filters.length;
+ // serialize struct sock_filter * explicitly, its less confusing than the JNA magic we would need
+ Memory filter = new Memory(len * 8);
+ ByteBuffer bbuf = filter.getByteBuffer(0, len * 8);
+ bbuf.order(ByteOrder.nativeOrder()); // little endian
+ for (SockFilter f : filters) {
+ bbuf.putShort(f.code());
+ bbuf.put(f.jt());
+ bbuf.put(f.jf());
+ bbuf.putInt(f.k());
+ }
+ this.filter = filter;
+ }
+
+ @Override
+ public long address() {
+ return Pointer.nativeValue(getPointer());
+ }
+ }
+
+ private interface NativeFunctions extends Library {
+
+ /**
+ * maps to prctl(2)
+ */
+ int prctl(int option, NativeLong arg2, NativeLong arg3, NativeLong arg4, NativeLong arg5);
+
+ /**
+ * used to call seccomp(2), its too new...
+ * this is the only way, DON'T use it on some other architecture unless you know wtf you are doing
+ */
+ NativeLong syscall(NativeLong number, Object... args);
+ }
+
+ private final NativeFunctions functions;
+
+ JnaLinuxCLibrary() {
+ try {
+ this.functions = Native.load("c", NativeFunctions.class);
+ } catch (UnsatisfiedLinkError e) {
+ throw new UnsupportedOperationException(
+ "seccomp unavailable: could not link methods. requires kernel 3.5+ "
+ + "with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in"
+ );
+ }
+ }
+
+ @Override
+ public SockFProg newSockFProg(SockFilter[] filters) {
+ var prog = new JnaSockFProg(filters);
+ prog.write();
+ return prog;
+ }
+
+ @Override
+ public int prctl(int option, long arg2, long arg3, long arg4, long arg5) {
+ return functions.prctl(option, new NativeLong(arg2), new NativeLong(arg3), new NativeLong(arg4), new NativeLong(arg5));
+ }
+
+ @Override
+ public long syscall(long number, int operation, int flags, long address) {
+ return functions.syscall(new NativeLong(number), operation, flags, address).longValue();
+ }
+}
diff --git a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaMacCLibrary.java b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaMacCLibrary.java
new file mode 100644
index 0000000000000..f416cf862b417
--- /dev/null
+++ b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaMacCLibrary.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.nativeaccess.jna;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.ptr.PointerByReference;
+
+import org.elasticsearch.nativeaccess.lib.MacCLibrary;
+
+class JnaMacCLibrary implements MacCLibrary {
+ static class JnaErrorReference implements ErrorReference {
+ final PointerByReference ref = new PointerByReference();
+
+ @Override
+ public String toString() {
+ return ref.getValue().getString(0);
+ }
+ }
+
+ private interface NativeFunctions extends Library {
+ int sandbox_init(String profile, long flags, PointerByReference errorbuf);
+
+ void sandbox_free_error(Pointer errorbuf);
+ }
+
+ private final NativeFunctions functions;
+
+ JnaMacCLibrary() {
+ this.functions = Native.load("c", NativeFunctions.class);
+ }
+
+ @Override
+ public ErrorReference newErrorReference() {
+ return new JnaErrorReference();
+ }
+
+ @Override
+ public int sandbox_init(String profile, long flags, ErrorReference errorbuf) {
+ assert errorbuf instanceof JnaErrorReference;
+ var jnaErrorbuf = (JnaErrorReference) errorbuf;
+ return functions.sandbox_init(profile, flags, jnaErrorbuf.ref);
+ }
+
+ @Override
+ public void sandbox_free_error(ErrorReference errorbuf) {
+ assert errorbuf instanceof JnaErrorReference;
+ var jnaErrorbuf = (JnaErrorReference) errorbuf;
+ functions.sandbox_free_error(jnaErrorbuf.ref.getValue());
+ }
+
+}
diff --git a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaNativeLibraryProvider.java b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaNativeLibraryProvider.java
index 9d34b1ba617e8..454581ae70b51 100644
--- a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaNativeLibraryProvider.java
+++ b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaNativeLibraryProvider.java
@@ -10,6 +10,8 @@
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
import org.elasticsearch.nativeaccess.lib.Kernel32Library;
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary;
+import org.elasticsearch.nativeaccess.lib.MacCLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.PosixCLibrary;
@@ -30,6 +32,10 @@ public JnaNativeLibraryProvider() {
JnaJavaLibrary::new,
PosixCLibrary.class,
JnaPosixCLibrary::new,
+ LinuxCLibrary.class,
+ JnaLinuxCLibrary::new,
+ MacCLibrary.class,
+ JnaMacCLibrary::new,
Kernel32Library.class,
JnaKernel32Library::new,
SystemdLibrary.class,
diff --git a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaPosixCLibrary.java b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaPosixCLibrary.java
index 7e8e4f23ab034..03a7b9c0869be 100644
--- a/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaPosixCLibrary.java
+++ b/libs/native/jna/src/main/java/org/elasticsearch/nativeaccess/jna/JnaPosixCLibrary.java
@@ -39,6 +39,50 @@ public long rlim_cur() {
public long rlim_max() {
return rlim_max.longValue();
}
+
+ @Override
+ public void rlim_cur(long v) {
+ rlim_cur.setValue(v);
+ }
+
+ @Override
+ public void rlim_max(long v) {
+ rlim_max.setValue(v);
+ }
+ }
+
+ public static class JnaFStore extends Structure implements Structure.ByReference, FStore {
+
+ public int fst_flags = 0;
+ public int fst_posmode = 0;
+ public NativeLong fst_offset = new NativeLong(0);
+ public NativeLong fst_length = new NativeLong(0);
+ public NativeLong fst_bytesalloc = new NativeLong(0);
+
+ @Override
+ public void set_flags(int flags) {
+ this.fst_flags = flags;
+ }
+
+ @Override
+ public void set_posmode(int posmode) {
+ this.fst_posmode = posmode;
+ }
+
+ @Override
+ public void set_offset(long offset) {
+ fst_offset.setValue(offset);
+ }
+
+ @Override
+ public void set_length(long length) {
+ fst_length.setValue(length);
+ }
+
+ @Override
+ public long bytesalloc() {
+ return fst_bytesalloc.longValue();
+ }
}
private interface NativeFunctions extends Library {
@@ -46,8 +90,12 @@ private interface NativeFunctions extends Library {
int getrlimit(int resource, JnaRLimit rlimit);
+ int setrlimit(int resource, JnaRLimit rlimit);
+
int mlockall(int flags);
+ int fcntl(int fd, int cmd, JnaFStore fst);
+
String strerror(int errno);
}
@@ -74,11 +122,30 @@ public int getrlimit(int resource, RLimit rlimit) {
return functions.getrlimit(resource, jnaRlimit);
}
+ @Override
+ public int setrlimit(int resource, RLimit rlimit) {
+ assert rlimit instanceof JnaRLimit;
+ var jnaRlimit = (JnaRLimit) rlimit;
+ return functions.setrlimit(resource, jnaRlimit);
+ }
+
@Override
public int mlockall(int flags) {
return functions.mlockall(flags);
}
+ @Override
+ public FStore newFStore() {
+ return new JnaFStore();
+ }
+
+ @Override
+ public int fcntl(int fd, int cmd, FStore fst) {
+ assert fst instanceof JnaFStore;
+ var jnaFst = (JnaFStore) fst;
+ return functions.fcntl(fd, cmd, jnaFst);
+ }
+
@Override
public String strerror(int errno) {
return functions.strerror(errno);
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/AbstractNativeAccess.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/AbstractNativeAccess.java
index 80a18a2bc8aa0..c10f57a900ff7 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/AbstractNativeAccess.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/AbstractNativeAccess.java
@@ -22,6 +22,7 @@ abstract class AbstractNativeAccess implements NativeAccess {
private final JavaLibrary javaLib;
private final Zstd zstd;
protected boolean isMemoryLocked = false;
+ protected ExecSandboxState execSandboxState = ExecSandboxState.NONE;
protected AbstractNativeAccess(String name, NativeLibraryProvider libraryProvider) {
this.name = name;
@@ -53,4 +54,9 @@ public CloseableByteBuffer newBuffer(int len) {
public boolean isMemoryLocked() {
return isMemoryLocked;
}
+
+ @Override
+ public ExecSandboxState getExecSandboxState() {
+ return execSandboxState;
+ }
}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/LinuxNativeAccess.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/LinuxNativeAccess.java
index 7948dad1df4ad..c50e639c94d27 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/LinuxNativeAccess.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/LinuxNativeAccess.java
@@ -8,15 +8,88 @@
package org.elasticsearch.nativeaccess;
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary;
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary.SockFProg;
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary.SockFilter;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.SystemdLibrary;
+import java.util.Map;
+
class LinuxNativeAccess extends PosixNativeAccess {
- Systemd systemd;
+ /** the preferred method is seccomp(2), since we can apply to all threads of the process */
+ static final int SECCOMP_SET_MODE_FILTER = 1; // since Linux 3.17
+ static final int SECCOMP_FILTER_FLAG_TSYNC = 1; // since Linux 3.17
+
+ /** otherwise, we can use prctl(2), which will at least protect ES application threads */
+ static final int PR_GET_NO_NEW_PRIVS = 39; // since Linux 3.5
+ static final int PR_SET_NO_NEW_PRIVS = 38; // since Linux 3.5
+ static final int PR_GET_SECCOMP = 21; // since Linux 2.6.23
+ static final int PR_SET_SECCOMP = 22; // since Linux 2.6.23
+ static final long SECCOMP_MODE_FILTER = 2; // since Linux Linux 3.5
+
+ // BPF "macros" and constants
+ static final int BPF_LD = 0x00;
+ static final int BPF_W = 0x00;
+ static final int BPF_ABS = 0x20;
+ static final int BPF_JMP = 0x05;
+ static final int BPF_JEQ = 0x10;
+ static final int BPF_JGE = 0x30;
+ static final int BPF_JGT = 0x20;
+ static final int BPF_RET = 0x06;
+ static final int BPF_K = 0x00;
+
+ static SockFilter BPF_STMT(int code, int k) {
+ return new SockFilter((short) code, (byte) 0, (byte) 0, k);
+ }
+
+ static SockFilter BPF_JUMP(int code, int k, int jt, int jf) {
+ return new SockFilter((short) code, (byte) jt, (byte) jf, k);
+ }
+
+ static final int SECCOMP_RET_ERRNO = 0x00050000;
+ static final int SECCOMP_RET_DATA = 0x0000FFFF;
+ static final int SECCOMP_RET_ALLOW = 0x7FFF0000;
+
+ // some errno constants for error checking/handling
+ static final int EACCES = 0x0D;
+ static final int EFAULT = 0x0E;
+ static final int EINVAL = 0x16;
+ static final int ENOSYS = 0x26;
+
+ // offsets that our BPF checks
+ // check with offsetof() when adding a new arch, move to Arch if different.
+ static final int SECCOMP_DATA_NR_OFFSET = 0x00;
+ static final int SECCOMP_DATA_ARCH_OFFSET = 0x04;
+
+ record Arch(
+ int audit, // AUDIT_ARCH_XXX constant from linux/audit.h
+ int limit, // syscall limit (necessary for blacklisting on amd64, to ban 32-bit syscalls)
+ int fork, // __NR_fork
+ int vfork, // __NR_vfork
+ int execve, // __NR_execve
+ int execveat, // __NR_execveat
+ int seccomp // __NR_seccomp
+ ) {}
+
+ /** supported architectures for seccomp keyed by os.arch */
+ private static final Map ARCHITECTURES;
+ static {
+ ARCHITECTURES = Map.of(
+ "amd64",
+ new Arch(0xC000003E, 0x3FFFFFFF, 57, 58, 59, 322, 317),
+ "aarch64",
+ new Arch(0xC00000B7, 0xFFFFFFFF, 1079, 1071, 221, 281, 277)
+ );
+ }
+
+ private final LinuxCLibrary linuxLibc;
+ private final Systemd systemd;
LinuxNativeAccess(NativeLibraryProvider libraryProvider) {
super("Linux", libraryProvider, new PosixConstants(-1L, 9, 1, 8));
+ this.linuxLibc = libraryProvider.getLibrary(LinuxCLibrary.class);
this.systemd = new Systemd(libraryProvider.getLibrary(SystemdLibrary.class));
}
@@ -46,4 +119,197 @@ protected void logMemoryLimitInstructions() {
\t{} hard memlock unlimited""", user, user, user);
logger.warn("If you are logged in interactively, you will have to re-login for the new limits to take effect.");
}
+
+ /**
+ * Installs exec system call filtering for Linux.
+ *
+ * On Linux exec system call filtering currently supports amd64 and aarch64 architectures.
+ * It requires Linux kernel 3.5 or above, and {@code CONFIG_SECCOMP} and {@code CONFIG_SECCOMP_FILTER}
+ * compiled into the kernel.
+ *
+ * On Linux BPF Filters are installed using either {@code seccomp(2)} (3.17+) or {@code prctl(2)} (3.5+). {@code seccomp(2)}
+ * is preferred, as it allows filters to be applied to any existing threads in the process, and one motivation
+ * here is to protect against bugs in the JVM. Otherwise, code will fall back to the {@code prctl(2)} method
+ * which will at least protect elasticsearch application threads.
+ *
+ * Linux BPF filters will return {@code EACCES} (Access Denied) for the following system calls:
+ *
+ *
{@code execve}
+ *
{@code fork}
+ *
{@code vfork}
+ *
{@code execveat}
+ *
+ * @see
+ * * http://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt
+ */
+ @Override
+ public void tryInstallExecSandbox() {
+ // first be defensive: we can give nice errors this way, at the very least.
+ // also, some of these security features get backported to old versions, checking kernel version here is a big no-no!
+ String archId = System.getProperty("os.arch");
+ final Arch arch = ARCHITECTURES.get(archId);
+ if (arch == null) {
+ throw new UnsupportedOperationException("seccomp unavailable: '" + archId + "' architecture unsupported");
+ }
+
+ // try to check system calls really are who they claim
+ // you never know (e.g. https://chromium.googlesource.com/chromium/src.git/+/master/sandbox/linux/seccomp-bpf/sandbox_bpf.cc#57)
+ final int bogusArg = 0xf7a46a5c;
+
+ // test seccomp(BOGUS)
+ long ret = linuxLibc.syscall(arch.seccomp, bogusArg, 0, 0);
+ if (ret != -1) {
+ throw new UnsupportedOperationException("seccomp unavailable: seccomp(BOGUS_OPERATION) returned " + ret);
+ } else {
+ int errno = libc.errno();
+ switch (errno) {
+ case ENOSYS:
+ break; // ok
+ case EINVAL:
+ break; // ok
+ default:
+ throw new UnsupportedOperationException("seccomp(BOGUS_OPERATION): " + libc.strerror(errno));
+ }
+ }
+
+ // test seccomp(VALID, BOGUS)
+ ret = linuxLibc.syscall(arch.seccomp, SECCOMP_SET_MODE_FILTER, bogusArg, 0);
+ if (ret != -1) {
+ throw new UnsupportedOperationException("seccomp unavailable: seccomp(SECCOMP_SET_MODE_FILTER, BOGUS_FLAG) returned " + ret);
+ } else {
+ int errno = libc.errno();
+ switch (errno) {
+ case ENOSYS:
+ break; // ok
+ case EINVAL:
+ break; // ok
+ default:
+ throw new UnsupportedOperationException("seccomp(SECCOMP_SET_MODE_FILTER, BOGUS_FLAG): " + libc.strerror(errno));
+ }
+ }
+
+ // test prctl(BOGUS)
+ ret = linuxLibc.prctl(bogusArg, 0, 0, 0, 0);
+ if (ret != -1) {
+ throw new UnsupportedOperationException("seccomp unavailable: prctl(BOGUS_OPTION) returned " + ret);
+ } else {
+ int errno = libc.errno();
+ switch (errno) {
+ case ENOSYS:
+ break; // ok
+ case EINVAL:
+ break; // ok
+ default:
+ throw new UnsupportedOperationException("prctl(BOGUS_OPTION): " + libc.strerror(errno));
+ }
+ }
+
+ // now just normal defensive checks
+
+ // check for GET_NO_NEW_PRIVS
+ switch (linuxLibc.prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0)) {
+ case 0:
+ break; // not yet set
+ case 1:
+ break; // already set by caller
+ default:
+ int errno = libc.errno();
+ 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): " + libc.strerror(errno));
+ }
+ }
+ // check for SECCOMP
+ switch (linuxLibc.prctl(PR_GET_SECCOMP, 0, 0, 0, 0)) {
+ case 0:
+ break; // not yet set
+ case 2:
+ break; // already in filter mode by caller
+ default:
+ int errno = libc.errno();
+ if (errno == EINVAL) {
+ throw new UnsupportedOperationException(
+ "seccomp unavailable: CONFIG_SECCOMP not compiled into kernel,"
+ + " CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed"
+ );
+ } else {
+ throw new UnsupportedOperationException("prctl(PR_GET_SECCOMP): " + libc.strerror(errno));
+ }
+ }
+ // check for SECCOMP_MODE_FILTER
+ if (linuxLibc.prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0, 0, 0) != 0) {
+ int errno = libc.errno();
+ switch (errno) {
+ case EFAULT:
+ break; // available
+ case EINVAL:
+ throw new UnsupportedOperationException(
+ "seccomp unavailable: CONFIG_SECCOMP_FILTER not"
+ + " compiled into kernel, CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed"
+ );
+ default:
+ throw new UnsupportedOperationException("prctl(PR_SET_SECCOMP): " + libc.strerror(errno));
+ }
+ }
+
+ // ok, now set PR_SET_NO_NEW_PRIVS, needed to be able to set a seccomp filter as ordinary user
+ if (linuxLibc.prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
+ throw new UnsupportedOperationException("prctl(PR_SET_NO_NEW_PRIVS): " + libc.strerror(libc.errno()));
+ }
+
+ // check it worked
+ if (linuxLibc.prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) != 1) {
+ throw new UnsupportedOperationException(
+ "seccomp filter did not really succeed: prctl(PR_GET_NO_NEW_PRIVS): " + libc.strerror(libc.errno())
+ );
+ }
+
+ // BPF installed to check arch, limit, then syscall.
+ // See https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt for details.
+ SockFilter insns[] = {
+ /* 1 */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, SECCOMP_DATA_ARCH_OFFSET), //
+ /* 2 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.audit, 0, 7), // if (arch != audit) goto fail;
+ /* 3 */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, SECCOMP_DATA_NR_OFFSET), //
+ /* 4 */ BPF_JUMP(BPF_JMP + BPF_JGT + BPF_K, arch.limit, 5, 0), // if (syscall > LIMIT) goto fail;
+ /* 5 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.fork, 4, 0), // if (syscall == FORK) goto fail;
+ /* 6 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.vfork, 3, 0), // if (syscall == VFORK) goto fail;
+ /* 7 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.execve, 2, 0), // if (syscall == EXECVE) goto fail;
+ /* 8 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.execveat, 1, 0), // if (syscall == EXECVEAT) goto fail;
+ /* 9 */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), // pass: return OK;
+ /* 10 */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (EACCES & SECCOMP_RET_DATA)), // fail: return EACCES;
+ };
+ // seccomp takes a long, so we pass it one explicitly to keep the JNA simple
+ SockFProg prog = linuxLibc.newSockFProg(insns);
+
+ int method = 1;
+ // install filter, if this works, after this there is no going back!
+ // first try it with seccomp(SECCOMP_SET_MODE_FILTER), falling back to prctl()
+ if (linuxLibc.syscall(arch.seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, prog.address()) != 0) {
+ method = 0;
+ int errno1 = libc.errno();
+ if (logger.isDebugEnabled()) {
+ logger.debug("seccomp(SECCOMP_SET_MODE_FILTER): {}, falling back to prctl(PR_SET_SECCOMP)...", libc.strerror(errno1));
+ }
+ if (linuxLibc.prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog.address(), 0, 0) != 0) {
+ int errno2 = libc.errno();
+ throw new UnsupportedOperationException(
+ "seccomp(SECCOMP_SET_MODE_FILTER): " + libc.strerror(errno1) + ", prctl(PR_SET_SECCOMP): " + libc.strerror(errno2)
+ );
+ }
+ }
+
+ // now check that the filter was really installed, we should be in filter mode.
+ if (linuxLibc.prctl(PR_GET_SECCOMP, 0, 0, 0, 0) != 2) {
+ throw new UnsupportedOperationException(
+ "seccomp filter installation did not really succeed. seccomp(PR_GET_SECCOMP): " + libc.strerror(libc.errno())
+ );
+ }
+
+ logger.debug("Linux seccomp filter installation successful, threads: [{}]", method == 1 ? "all" : "app");
+ execSandboxState = method == 1 ? ExecSandboxState.ALL_THREADS : ExecSandboxState.EXISTING_THREADS;
+ }
}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/MacNativeAccess.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/MacNativeAccess.java
index 0388c66d3962f..c53b7ba6ac2f0 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/MacNativeAccess.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/MacNativeAccess.java
@@ -8,12 +8,30 @@
package org.elasticsearch.nativeaccess;
+import org.elasticsearch.core.IOUtils;
+import org.elasticsearch.core.SuppressForbidden;
+import org.elasticsearch.nativeaccess.lib.MacCLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
+import org.elasticsearch.nativeaccess.lib.PosixCLibrary.RLimit;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
class MacNativeAccess extends PosixNativeAccess {
+ /** The only supported flag... */
+ static final int SANDBOX_NAMED = 1;
+ /** Allow everything except process fork and execution */
+ static final String SANDBOX_RULES = "(version 1) (allow default) (deny process-fork) (deny process-exec)";
+
+ private final MacCLibrary macLibc;
+
MacNativeAccess(NativeLibraryProvider libraryProvider) {
super("MacOS", libraryProvider, new PosixConstants(9223372036854775807L, 5, 1, 6));
+ this.macLibc = libraryProvider.getLibrary(MacCLibrary.class);
}
@Override
@@ -25,4 +43,69 @@ protected long getMaxThreads() {
protected void logMemoryLimitInstructions() {
// we don't have instructions for macos
}
+
+ /**
+ * Installs exec system call filtering on MacOS.
+ *
+ * Two different methods of filtering are used. Since MacOS is BSD based, process creation
+ * is first restricted with {@code setrlimit(RLIMIT_NPROC)}.
+ *
+ * Additionally, on Mac OS X Leopard or above, a custom {@code sandbox(7)} ("Seatbelt") profile is installed that
+ * denies the following rules:
+ *
+ *
{@code process-fork}
+ *
{@code process-exec}
+ *
+ * @see
+ * * https://reverse.put.as/wp-content/uploads/2011/06/The-Apple-Sandbox-BHDC2011-Paper.pdf
+ */
+ @Override
+ public void tryInstallExecSandbox() {
+ initBsdSandbox();
+ initMacSandbox();
+ execSandboxState = ExecSandboxState.ALL_THREADS;
+ }
+
+ @SuppressForbidden(reason = "Java tmp dir is ok")
+ private static Path createTempRulesFile() throws IOException {
+ return Files.createTempFile("es", "sb");
+ }
+
+ private void initMacSandbox() {
+ // write rules to a temporary file, which will be passed to sandbox_init()
+ Path rules;
+ try {
+ rules = createTempRulesFile();
+ Files.write(rules, Collections.singleton(SANDBOX_RULES));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ try {
+ var errorRef = macLibc.newErrorReference();
+ int ret = macLibc.sandbox_init(rules.toAbsolutePath().toString(), SANDBOX_NAMED, errorRef);
+ // if sandbox_init() fails, add the message from the OS (e.g. syntax error) and free the buffer
+ if (ret != 0) {
+ RuntimeException e = new UnsupportedOperationException("sandbox_init(): " + errorRef.toString());
+ macLibc.sandbox_free_error(errorRef);
+ throw e;
+ }
+ logger.debug("OS X seatbelt initialization successful");
+ } finally {
+ IOUtils.deleteFilesIgnoringExceptions(rules);
+ }
+ }
+
+ private void initBsdSandbox() {
+ RLimit limit = libc.newRLimit();
+ limit.rlim_cur(0);
+ limit.rlim_max(0);
+ // not a standard limit, means something different on linux, etc!
+ final int RLIMIT_NPROC = 7;
+ if (libc.setrlimit(RLIMIT_NPROC, limit) != 0) {
+ throw new UnsupportedOperationException("RLIMIT_NPROC unavailable: " + libc.strerror(libc.errno()));
+ }
+
+ logger.debug("BSD RLIMIT_NPROC initialization successful");
+ }
}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/NativeAccess.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/NativeAccess.java
index 7f91d0425af47..61935ac93c5a3 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/NativeAccess.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/NativeAccess.java
@@ -44,6 +44,16 @@ static NativeAccess instance() {
*/
boolean isMemoryLocked();
+ /**
+ * Attempts to install a system call filter to block process execution.
+ */
+ void tryInstallExecSandbox();
+
+ /**
+ * Return whether installing the exec system call filters was successful, and to what degree.
+ */
+ ExecSandboxState getExecSandboxState();
+
Systemd systemd();
/**
@@ -71,4 +81,16 @@ default WindowsFunctions getWindowsFunctions() {
* @return the buffer
*/
CloseableByteBuffer newBuffer(int len);
+
+ /**
+ * Possible stats for execution filtering.
+ */
+ enum ExecSandboxState {
+ /** No execution filtering */
+ NONE,
+ /** Exec is blocked for threads that were already created */
+ EXISTING_THREADS,
+ /** Exec is blocked for all current and future threads */
+ ALL_THREADS
+ }
}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/NoopNativeAccess.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/NoopNativeAccess.java
index c0eed4a9ce09b..fc186cb03b0d9 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/NoopNativeAccess.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/NoopNativeAccess.java
@@ -41,6 +41,16 @@ public boolean isMemoryLocked() {
return false;
}
+ @Override
+ public void tryInstallExecSandbox() {
+ logger.warn("Cannot install system call filter because native access is not available");
+ }
+
+ @Override
+ public ExecSandboxState getExecSandboxState() {
+ return ExecSandboxState.NONE;
+ }
+
@Override
public Systemd systemd() {
logger.warn("Cannot get systemd access because native access is not available");
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/WindowsNativeAccess.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/WindowsNativeAccess.java
index 843cc73fbed02..a9ccd15330595 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/WindowsNativeAccess.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/WindowsNativeAccess.java
@@ -27,6 +27,16 @@ class WindowsNativeAccess extends AbstractNativeAccess {
public static final int PAGE_GUARD = 0x0100;
public static final int MEM_COMMIT = 0x1000;
+ /**
+ * Constant for JOBOBJECT_BASIC_LIMIT_INFORMATION in Query/Set InformationJobObject
+ */
+ private static final int JOBOBJECT_BASIC_LIMIT_INFORMATION_CLASS = 2;
+
+ /**
+ * Constant for LimitFlags, indicating a process limit has been set
+ */
+ private static final int JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 8;
+
private final Kernel32Library kernel;
private final WindowsFunctions windowsFunctions;
@@ -68,6 +78,47 @@ public void tryLockMemory() {
// note: no need to close the process handle because GetCurrentProcess returns a pseudo handle
}
+ /**
+ * Install exec system call filtering on Windows.
+ *
+ * Process creation is restricted with {@code SetInformationJobObject/ActiveProcessLimit}.
+ *
+ * Note: This is not intended as a real sandbox. It is another level of security, mostly intended to annoy
+ * security researchers and make their lives more difficult in achieving "remote execution" exploits.
+ */
+ @Override
+ public void tryInstallExecSandbox() {
+ // create a new Job
+ Handle job = kernel.CreateJobObjectW();
+ if (job == null) {
+ throw new UnsupportedOperationException("CreateJobObject: " + kernel.GetLastError());
+ }
+
+ try {
+ // retrieve the current basic limits of the job
+ int clazz = JOBOBJECT_BASIC_LIMIT_INFORMATION_CLASS;
+ var info = kernel.newJobObjectBasicLimitInformation();
+ if (kernel.QueryInformationJobObject(job, clazz, info) == false) {
+ throw new UnsupportedOperationException("QueryInformationJobObject: " + kernel.GetLastError());
+ }
+ // modify the number of active processes to be 1 (exactly the one process we will add to the job).
+ info.setActiveProcessLimit(1);
+ info.setLimitFlags(JOB_OBJECT_LIMIT_ACTIVE_PROCESS);
+ if (kernel.SetInformationJobObject(job, clazz, info) == false) {
+ throw new UnsupportedOperationException("SetInformationJobObject: " + kernel.GetLastError());
+ }
+ // assign ourselves to the job
+ if (kernel.AssignProcessToJobObject(job, kernel.GetCurrentProcess()) == false) {
+ throw new UnsupportedOperationException("AssignProcessToJobObject: " + kernel.GetLastError());
+ }
+ } finally {
+ kernel.CloseHandle(job);
+ }
+
+ execSandboxState = ExecSandboxState.ALL_THREADS;
+ logger.debug("Windows ActiveProcessLimit initialization successful");
+ }
+
@Override
public ProcessLimits getProcessLimits() {
return new ProcessLimits(ProcessLimits.UNKNOWN, ProcessLimits.UNKNOWN, ProcessLimits.UNKNOWN);
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/Kernel32Library.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/Kernel32Library.java
index 43337f4532bed..dd786b56087e2 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/Kernel32Library.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/Kernel32Library.java
@@ -101,4 +101,65 @@ interface MemoryBasicInformation {
* @see SetConsoleCtrlHandler docs
*/
boolean SetConsoleCtrlHandler(ConsoleCtrlHandler handler, boolean add);
+
+ /**
+ * Creates or opens a new job object
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682409%28v=vs.85%29.aspx
+ * Note: the two params to this are omitted because all implementations pass null for them both
+ *
+ * @return job handle if the function succeeds
+ */
+ Handle CreateJobObjectW();
+
+ /**
+ * 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
+ */
+ boolean AssignProcessToJobObject(Handle job, Handle process);
+
+ /**
+ * Basic limit information for a job object
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684147%28v=vs.85%29.aspx
+ */
+ interface JobObjectBasicLimitInformation {
+ void setLimitFlags(int v);
+
+ void setActiveProcessLimit(int v);
+ }
+
+ JobObjectBasicLimitInformation newJobObjectBasicLimitInformation();
+
+ /**
+ * Get job limit and state information
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684925%28v=vs.85%29.aspx
+ * Note: The infoLength parameter is omitted because implementions handle passing it
+ * Note: The returnLength parameter is omitted because all implementations pass null
+ *
+ * @param job job handle
+ * @param infoClass information class constant
+ * @param info pointer to information structure
+ * @return true if the function succeeds
+ */
+ boolean QueryInformationJobObject(Handle job, int infoClass, JobObjectBasicLimitInformation info);
+
+ /**
+ * Set job limit and state information
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms686216%28v=vs.85%29.aspx
+ * Note: The infoLength parameter is omitted because implementions handle passing it
+ *
+ * @param job job handle
+ * @param infoClass information class constant
+ * @param info pointer to information structure
+ * @return true if the function succeeds
+ */
+ boolean SetInformationJobObject(Handle job, int infoClass, JobObjectBasicLimitInformation info);
}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/LinuxCLibrary.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/LinuxCLibrary.java
new file mode 100644
index 0000000000000..2a7b10ff3588f
--- /dev/null
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/LinuxCLibrary.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.nativeaccess.lib;
+
+public non-sealed interface LinuxCLibrary extends NativeLibrary {
+
+ /**
+ * Corresponds to struct sock_filter
+ * @param code insn
+ * @param jt number of insn to jump (skip) if true
+ * @param jf number of insn to jump (skip) if false
+ * @param k additional data
+ */
+ record SockFilter(short code, byte jt, byte jf, int k) {}
+
+ interface SockFProg {
+ long address();
+ }
+
+ SockFProg newSockFProg(SockFilter filters[]);
+
+ /**
+ * maps to prctl(2)
+ */
+ int prctl(int option, long arg2, long arg3, long arg4, long arg5);
+
+ /**
+ * used to call seccomp(2), its too new...
+ * this is the only way, DON'T use it on some other architecture unless you know wtf you are doing
+ */
+ long syscall(long number, int operation, int flags, long address);
+}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/MacCLibrary.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/MacCLibrary.java
new file mode 100644
index 0000000000000..b2b2db9c71c90
--- /dev/null
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/MacCLibrary.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.nativeaccess.lib;
+
+public non-sealed interface MacCLibrary extends NativeLibrary {
+ interface ErrorReference {}
+
+ ErrorReference newErrorReference();
+
+ /**
+ * maps to sandbox_init(3), since Leopard
+ */
+ int sandbox_init(String profile, long flags, ErrorReference errorbuf);
+
+ /**
+ * releases memory when an error occurs during initialization (e.g. syntax bug)
+ */
+ void sandbox_free_error(ErrorReference errorbuf);
+}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/NativeLibrary.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/NativeLibrary.java
index d8098a78935b8..faa0e861dc63f 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/NativeLibrary.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/NativeLibrary.java
@@ -9,4 +9,5 @@
package org.elasticsearch.nativeaccess.lib;
/** A marker interface for libraries that can be loaded by {@link org.elasticsearch.nativeaccess.lib.NativeLibraryProvider} */
-public sealed interface NativeLibrary permits JavaLibrary, PosixCLibrary, Kernel32Library, SystemdLibrary, VectorLibrary, ZstdLibrary {}
+public sealed interface NativeLibrary permits JavaLibrary, PosixCLibrary, LinuxCLibrary, MacCLibrary, Kernel32Library, SystemdLibrary,
+ VectorLibrary, ZstdLibrary {}
diff --git a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/PosixCLibrary.java b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/PosixCLibrary.java
index 96e2a0d0e1cdf..d8db5fa070126 100644
--- a/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/PosixCLibrary.java
+++ b/libs/native/src/main/java/org/elasticsearch/nativeaccess/lib/PosixCLibrary.java
@@ -26,6 +26,10 @@ interface RLimit {
long rlim_cur();
long rlim_max();
+
+ void rlim_cur(long v);
+
+ void rlim_max(long v);
}
/**
@@ -41,6 +45,8 @@ interface RLimit {
*/
int getrlimit(int resource, RLimit rlimit);
+ int setrlimit(int resource, RLimit rlimit);
+
/**
* Lock all the current process's virtual address space into RAM.
* @param flags flags determining how memory will be locked
@@ -49,6 +55,22 @@ interface RLimit {
*/
int mlockall(int flags);
+ interface FStore {
+ void set_flags(int flags); /* IN: flags word */
+
+ void set_posmode(int posmode); /* IN: indicates offset field */
+
+ void set_offset(long offset); /* IN: start of the region */
+
+ void set_length(long length); /* IN: size of the region */
+
+ long bytesalloc(); /* OUT: number of bytes allocated */
+ }
+
+ FStore newFStore();
+
+ int fcntl(int fd, int cmd, FStore fst);
+
/**
* Return a string description for an error.
*
diff --git a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkKernel32Library.java b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkKernel32Library.java
index bbfd26bd061d0..f5eb5238dad93 100644
--- a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkKernel32Library.java
+++ b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkKernel32Library.java
@@ -72,6 +72,22 @@ class JdkKernel32Library implements Kernel32Library {
"handle",
ConsoleCtrlHandler_handle$fd
);
+ private static final MethodHandle CreateJobObjectW$mh = downcallHandleWithError(
+ "CreateJobObjectW",
+ FunctionDescriptor.of(ADDRESS, ADDRESS, ADDRESS)
+ );
+ private static final MethodHandle AssignProcessToJobObject$mh = downcallHandleWithError(
+ "AssignProcessToJobObject",
+ FunctionDescriptor.of(JAVA_BOOLEAN, ADDRESS, ADDRESS)
+ );
+ private static final MethodHandle QueryInformationJobObject$mh = downcallHandleWithError(
+ "QueryInformationJobObject",
+ FunctionDescriptor.of(JAVA_BOOLEAN, ADDRESS, JAVA_INT, ADDRESS, JAVA_INT, ADDRESS)
+ );
+ private static final MethodHandle SetInformationJobObject$mh = downcallHandleWithError(
+ "SetInformationJobObject",
+ FunctionDescriptor.of(JAVA_BOOLEAN, ADDRESS, JAVA_INT, ADDRESS, JAVA_INT)
+ );
private static MethodHandle downcallHandleWithError(String function, FunctionDescriptor functionDescriptor) {
return downcallHandle(function, functionDescriptor, CAPTURE_GETLASTERROR_OPTION);
@@ -146,6 +162,37 @@ public long Type() {
}
}
+ static class JdkJobObjectBasicLimitInformation implements JobObjectBasicLimitInformation {
+ private static final MemoryLayout layout = MemoryLayout.structLayout(
+ paddingLayout(16),
+ JAVA_INT,
+ paddingLayout(20),
+ JAVA_INT,
+ paddingLayout(20)
+ ).withByteAlignment(8);
+
+ private static final VarHandle LimitFlags$vh = varHandleWithoutOffset(layout, groupElement(1));
+ private static final VarHandle ActiveProcessLimit$vh = varHandleWithoutOffset(layout, groupElement(3));
+
+ private final MemorySegment segment;
+
+ JdkJobObjectBasicLimitInformation() {
+ var arena = Arena.ofAuto();
+ this.segment = arena.allocate(layout);
+ segment.fill((byte) 0);
+ }
+
+ @Override
+ public void setLimitFlags(int v) {
+ LimitFlags$vh.set(segment, v);
+ }
+
+ @Override
+ public void setActiveProcessLimit(int v) {
+ ActiveProcessLimit$vh.set(segment, v);
+ }
+ }
+
private final MemorySegment lastErrorState;
JdkKernel32Library() {
@@ -262,4 +309,73 @@ public boolean SetConsoleCtrlHandler(ConsoleCtrlHandler handler, boolean add) {
throw new AssertionError(t);
}
}
+
+ @Override
+ public Handle CreateJobObjectW() {
+ try {
+ return new JdkHandle((MemorySegment) CreateJobObjectW$mh.invokeExact(lastErrorState, MemorySegment.NULL, MemorySegment.NULL));
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
+ @Override
+ public boolean AssignProcessToJobObject(Handle job, Handle process) {
+ assert job instanceof JdkHandle;
+ assert process instanceof JdkHandle;
+ var jdkJob = (JdkHandle) job;
+ var jdkProcess = (JdkHandle) process;
+
+ try {
+ return (boolean) AssignProcessToJobObject$mh.invokeExact(lastErrorState, jdkJob.address, jdkProcess.address);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
+ @Override
+ public JobObjectBasicLimitInformation newJobObjectBasicLimitInformation() {
+ return new JdkJobObjectBasicLimitInformation();
+ }
+
+ @Override
+ public boolean QueryInformationJobObject(Handle job, int infoClass, JobObjectBasicLimitInformation info) {
+ assert job instanceof JdkHandle;
+ assert info instanceof JdkJobObjectBasicLimitInformation;
+ var jdkJob = (JdkHandle) job;
+ var jdkInfo = (JdkJobObjectBasicLimitInformation) info;
+
+ try {
+ return (boolean) QueryInformationJobObject$mh.invokeExact(
+ lastErrorState,
+ jdkJob.address,
+ infoClass,
+ jdkInfo.segment,
+ (int) jdkInfo.segment.byteSize(),
+ MemorySegment.NULL
+ );
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
+ @Override
+ public boolean SetInformationJobObject(Handle job, int infoClass, JobObjectBasicLimitInformation info) {
+ assert job instanceof JdkHandle;
+ assert info instanceof JdkJobObjectBasicLimitInformation;
+ var jdkJob = (JdkHandle) job;
+ var jdkInfo = (JdkJobObjectBasicLimitInformation) info;
+
+ try {
+ return (boolean) SetInformationJobObject$mh.invokeExact(
+ lastErrorState,
+ jdkJob.address,
+ infoClass,
+ jdkInfo.segment,
+ (int) jdkInfo.segment.byteSize()
+ );
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
}
diff --git a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkLinuxCLibrary.java b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkLinuxCLibrary.java
new file mode 100644
index 0000000000000..700941e7e1db0
--- /dev/null
+++ b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkLinuxCLibrary.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.nativeaccess.jdk;
+
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
+
+import static java.lang.foreign.MemoryLayout.paddingLayout;
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_BYTE;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
+import static java.lang.foreign.ValueLayout.JAVA_SHORT;
+import static org.elasticsearch.nativeaccess.jdk.JdkPosixCLibrary.CAPTURE_ERRNO_OPTION;
+import static org.elasticsearch.nativeaccess.jdk.JdkPosixCLibrary.downcallHandleWithErrno;
+import static org.elasticsearch.nativeaccess.jdk.JdkPosixCLibrary.errnoState;
+import static org.elasticsearch.nativeaccess.jdk.LinkerHelper.downcallHandle;
+
+class JdkLinuxCLibrary implements LinuxCLibrary {
+ private static final MethodHandle prctl$mh;
+ static {
+ try {
+ prctl$mh = downcallHandleWithErrno(
+ "prctl",
+ FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG)
+ );
+ } catch (UnsatisfiedLinkError e) {
+ throw new UnsupportedOperationException(
+ "seccomp unavailable: could not link methods. requires kernel 3.5+ "
+ + "with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in"
+ );
+ }
+ }
+ private static final MethodHandle syscall$mh = downcallHandle(
+ "syscall",
+ FunctionDescriptor.of(JAVA_LONG, JAVA_LONG, JAVA_INT, JAVA_INT, JAVA_LONG),
+ CAPTURE_ERRNO_OPTION,
+ Linker.Option.firstVariadicArg(1)
+ );
+
+ private static class JdkSockFProg implements SockFProg {
+ private static final MemoryLayout layout = MemoryLayout.structLayout(JAVA_SHORT, paddingLayout(6), ADDRESS);
+
+ private final MemorySegment segment;
+
+ JdkSockFProg(SockFilter filters[]) {
+ Arena arena = Arena.ofAuto();
+ this.segment = arena.allocate(layout);
+ var instSegment = arena.allocate(filters.length * 8L);
+ segment.set(JAVA_SHORT, 0, (short) filters.length);
+ segment.set(ADDRESS, 8, instSegment);
+
+ int offset = 0;
+ for (SockFilter f : filters) {
+ instSegment.set(JAVA_SHORT, offset, f.code());
+ instSegment.set(JAVA_BYTE, offset + 2, f.jt());
+ instSegment.set(JAVA_BYTE, offset + 3, f.jf());
+ instSegment.set(JAVA_INT, offset + 4, f.k());
+ offset += 8;
+ }
+ }
+
+ @Override
+ public long address() {
+ return segment.address();
+ }
+ }
+
+ @Override
+ public SockFProg newSockFProg(SockFilter[] filters) {
+ return new JdkSockFProg(filters);
+ }
+
+ @Override
+ public int prctl(int option, long arg2, long arg3, long arg4, long arg5) {
+ try {
+ return (int) prctl$mh.invokeExact(errnoState, option, arg2, arg3, arg4, arg5);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
+ @Override
+ public long syscall(long number, int operation, int flags, long address) {
+ try {
+ return (long) syscall$mh.invokeExact(errnoState, number, operation, flags, address);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+}
diff --git a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkMacCLibrary.java b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkMacCLibrary.java
new file mode 100644
index 0000000000000..b946ca3ca4353
--- /dev/null
+++ b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkMacCLibrary.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.nativeaccess.jdk;
+
+import org.elasticsearch.nativeaccess.lib.MacCLibrary;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
+import static org.elasticsearch.nativeaccess.jdk.LinkerHelper.downcallHandle;
+
+class JdkMacCLibrary implements MacCLibrary {
+
+ private static final MethodHandle sandbox_init$mh = downcallHandle(
+ "sandbox_init",
+ FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG, ADDRESS)
+ );
+ private static final MethodHandle sandbox_free_error$mh = downcallHandle("sandbox_free_error", FunctionDescriptor.ofVoid(ADDRESS));
+
+ private static class JdkErrorReference implements ErrorReference {
+ final Arena arena = Arena.ofConfined();
+ final MemorySegment segment = arena.allocate(ValueLayout.ADDRESS);
+
+ MemorySegment deref() {
+ return segment.get(ADDRESS, 0);
+ }
+
+ @Override
+ public String toString() {
+ return deref().reinterpret(Long.MAX_VALUE).getUtf8String(0);
+ }
+ }
+
+ @Override
+ public ErrorReference newErrorReference() {
+ return new JdkErrorReference();
+ }
+
+ @Override
+ public int sandbox_init(String profile, long flags, ErrorReference errorbuf) {
+ assert errorbuf instanceof JdkErrorReference;
+ var jdkErrorbuf = (JdkErrorReference) errorbuf;
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment nativeProfile = MemorySegmentUtil.allocateString(arena, profile);
+ return (int) sandbox_init$mh.invokeExact(nativeProfile, flags, jdkErrorbuf.segment);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
+ @Override
+ public void sandbox_free_error(ErrorReference errorbuf) {
+ assert errorbuf instanceof JdkErrorReference;
+ var jdkErrorbuf = (JdkErrorReference) errorbuf;
+ try {
+ sandbox_free_error$mh.invokeExact(jdkErrorbuf.deref());
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+}
diff --git a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkNativeLibraryProvider.java b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkNativeLibraryProvider.java
index d76170a55284c..cbd43a394379b 100644
--- a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkNativeLibraryProvider.java
+++ b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkNativeLibraryProvider.java
@@ -10,6 +10,8 @@
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
import org.elasticsearch.nativeaccess.lib.Kernel32Library;
+import org.elasticsearch.nativeaccess.lib.LinuxCLibrary;
+import org.elasticsearch.nativeaccess.lib.MacCLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.PosixCLibrary;
import org.elasticsearch.nativeaccess.lib.SystemdLibrary;
@@ -28,6 +30,10 @@ public JdkNativeLibraryProvider() {
JdkJavaLibrary::new,
PosixCLibrary.class,
JdkPosixCLibrary::new,
+ LinuxCLibrary.class,
+ JdkLinuxCLibrary::new,
+ MacCLibrary.class,
+ JdkMacCLibrary::new,
Kernel32Library.class,
JdkKernel32Library::new,
SystemdLibrary.class,
diff --git a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkPosixCLibrary.java b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkPosixCLibrary.java
index 43ec9425ccfaa..1a65225873c1d 100644
--- a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkPosixCLibrary.java
+++ b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkPosixCLibrary.java
@@ -43,7 +43,12 @@ class JdkPosixCLibrary implements PosixCLibrary {
"getrlimit",
FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS)
);
+ private static final MethodHandle setrlimit$mh = downcallHandleWithErrno(
+ "setrlimit",
+ FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS)
+ );
private static final MethodHandle mlockall$mh = downcallHandleWithErrno("mlockall", FunctionDescriptor.of(JAVA_INT, JAVA_INT));
+ private static final MethodHandle fcntl$mh = downcallHandle("fcntl", FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT, ADDRESS));
static final MemorySegment errnoState = Arena.ofAuto().allocate(CAPTURE_ERRNO_LAYOUT);
@@ -91,6 +96,17 @@ public int getrlimit(int resource, RLimit rlimit) {
}
}
+ @Override
+ public int setrlimit(int resource, RLimit rlimit) {
+ assert rlimit instanceof JdkRLimit;
+ var jdkRlimit = (JdkRLimit) rlimit;
+ try {
+ return (int) setrlimit$mh.invokeExact(errnoState, resource, jdkRlimit.segment);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
@Override
public int mlockall(int flags) {
try {
@@ -100,6 +116,22 @@ public int mlockall(int flags) {
}
}
+ @Override
+ public FStore newFStore() {
+ return new JdkFStore();
+ }
+
+ @Override
+ public int fcntl(int fd, int cmd, FStore fst) {
+ assert fst instanceof JdkFStore;
+ var jdkFst = (JdkFStore) fst;
+ try {
+ return (int) fcntl$mh.invokeExact(errnoState, fd, cmd, jdkFst.segment);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
static class JdkRLimit implements RLimit {
private static final MemoryLayout layout = MemoryLayout.structLayout(JAVA_LONG, JAVA_LONG);
private static final VarHandle rlim_cur$vh = varHandleWithoutOffset(layout, groupElement(0));
@@ -122,9 +154,60 @@ public long rlim_max() {
return (long) rlim_max$vh.get(segment);
}
+ @Override
+ public void rlim_cur(long v) {
+ rlim_cur$vh.set(segment, v);
+ }
+
+ @Override
+ public void rlim_max(long v) {
+ rlim_max$vh.set(segment, v);
+ }
+
@Override
public String toString() {
return "JdkRLimit[rlim_cur=" + rlim_cur() + ", rlim_max=" + rlim_max();
}
}
+
+ private static class JdkFStore implements FStore {
+ private static final MemoryLayout layout = MemoryLayout.structLayout(JAVA_INT, JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG);
+ private static final VarHandle st_flags$vh = layout.varHandle(groupElement(0));
+ private static final VarHandle st_posmode$vh = layout.varHandle(groupElement(1));
+ private static final VarHandle st_offset$vh = layout.varHandle(groupElement(2));
+ private static final VarHandle st_length$vh = layout.varHandle(groupElement(3));
+ private static final VarHandle st_bytesalloc$vh = layout.varHandle(groupElement(4));
+
+ private final MemorySegment segment;
+
+ JdkFStore() {
+ var arena = Arena.ofAuto();
+ this.segment = arena.allocate(layout);
+ }
+
+ @Override
+ public void set_flags(int flags) {
+ st_flags$vh.set(segment, flags);
+ }
+
+ @Override
+ public void set_posmode(int posmode) {
+ st_posmode$vh.set(segment, posmode);
+ }
+
+ @Override
+ public void set_offset(long offset) {
+ st_offset$vh.get(segment, offset);
+ }
+
+ @Override
+ public void set_length(long length) {
+ st_length$vh.set(segment, length);
+ }
+
+ @Override
+ public long bytesalloc() {
+ return (long) st_bytesalloc$vh.get(segment);
+ }
+ }
}
diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SystemCallFilterTests.java b/libs/native/src/test/java/org/elasticsearch/nativeaccess/SystemCallFilterTests.java
similarity index 84%
rename from qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SystemCallFilterTests.java
rename to libs/native/src/test/java/org/elasticsearch/nativeaccess/SystemCallFilterTests.java
index c62522880869b..d4bac13990898 100644
--- a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/SystemCallFilterTests.java
+++ b/libs/native/src/test/java/org/elasticsearch/nativeaccess/SystemCallFilterTests.java
@@ -6,12 +6,16 @@
* Side Public License, v 1.
*/
-package org.elasticsearch.bootstrap;
+package org.elasticsearch.nativeaccess;
import org.apache.lucene.util.Constants;
import org.elasticsearch.test.ESTestCase;
+import static org.apache.lucene.tests.util.LuceneTestCase.assumeTrue;
+import static org.junit.Assert.fail;
+
/** Simple tests system call filter is working. */
+@ESTestCase.WithoutSecurityManager
public class SystemCallFilterTests extends ESTestCase {
/** command to try to run in tests */
@@ -20,15 +24,18 @@ public class SystemCallFilterTests extends ESTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
- assumeTrue("requires system call filter installation", Natives.isSystemCallFilterInstalled());
+ assumeTrue(
+ "requires system call filter installation",
+ NativeAccess.instance().getExecSandboxState() != NativeAccess.ExecSandboxState.NONE
+ );
// otherwise security manager will block the execution, no fun
assumeTrue("cannot test with security manager enabled", System.getSecurityManager() == null);
// otherwise, since we don't have TSYNC support, rules are not applied to the test thread
// (randomizedrunner class initialization happens in its own thread, after the test thread is created)
// instead we just forcefully run it for the test thread here.
- if (JNANatives.LOCAL_SYSTEM_CALL_FILTER_ALL == false) {
+ if (NativeAccess.instance().getExecSandboxState() != NativeAccess.ExecSandboxState.ALL_THREADS) {
try {
- SystemCallFilter.init(createTempDir());
+ NativeAccess.instance().tryInstallExecSandbox();
} catch (Exception e) {
throw new RuntimeException("unable to forcefully apply system call filter to test thread", e);
}
diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDownloader.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDownloader.java
index 5239e96856b7f..13394a2a0c7cc 100644
--- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDownloader.java
+++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDownloader.java
@@ -318,22 +318,19 @@ public void requestReschedule() {
}
private void cleanDatabases() {
- long expiredDatabases = state.getDatabases()
+ List> expiredDatabases = state.getDatabases()
.entrySet()
.stream()
.filter(e -> e.getValue().isValid(clusterService.state().metadata().settings()) == false)
- .peek(e -> {
- String name = e.getKey();
- Metadata meta = e.getValue();
- deleteOldChunks(name, meta.lastChunk() + 1);
- state = state.put(
- name,
- new Metadata(meta.lastUpdate(), meta.firstChunk(), meta.lastChunk(), meta.md5(), meta.lastCheck() - 1)
- );
- updateTaskState();
- })
- .count();
- stats = stats.expiredDatabases((int) expiredDatabases);
+ .toList();
+ expiredDatabases.forEach(e -> {
+ String name = e.getKey();
+ Metadata meta = e.getValue();
+ deleteOldChunks(name, meta.lastChunk() + 1);
+ state = state.put(name, new Metadata(meta.lastUpdate(), meta.firstChunk(), meta.lastChunk(), meta.md5(), meta.lastCheck() - 1));
+ updateTaskState();
+ });
+ stats = stats.expiredDatabases(expiredDatabases.size());
}
@Override
diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpDownloaderTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpDownloaderTests.java
index 4834c581e9386..6a83fe69473f7 100644
--- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpDownloaderTests.java
+++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpDownloaderTests.java
@@ -30,11 +30,17 @@
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.index.reindex.BulkByScrollResponse;
+import org.elasticsearch.index.reindex.DeleteByQueryAction;
+import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.ingest.geoip.stats.GeoIpDownloaderStats;
import org.elasticsearch.node.Node;
+import org.elasticsearch.persistent.PersistentTaskResponse;
import org.elasticsearch.persistent.PersistentTaskState;
import org.elasticsearch.persistent.PersistentTasksCustomMetadata;
import org.elasticsearch.persistent.PersistentTasksCustomMetadata.PersistentTask;
+import org.elasticsearch.persistent.PersistentTasksService;
+import org.elasticsearch.persistent.UpdatePersistentTaskStatusAction;
import org.elasticsearch.telemetry.metric.MeterRegistry;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.client.NoOpClient;
@@ -49,6 +55,9 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -63,6 +72,8 @@
import static org.elasticsearch.ingest.geoip.GeoIpDownloader.MAX_CHUNK_SIZE;
import static org.elasticsearch.tasks.TaskId.EMPTY_TASK_ID;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@@ -76,8 +87,9 @@ public class GeoIpDownloaderTests extends ESTestCase {
private GeoIpDownloader geoIpDownloader;
@Before
- public void setup() {
+ public void setup() throws IOException {
httpClient = mock(HttpClient.class);
+ when(httpClient.getBytes(anyString())).thenReturn("[]".getBytes(StandardCharsets.UTF_8));
clusterService = mock(ClusterService.class);
threadPool = new ThreadPool(Settings.builder().put(Node.NODE_NAME_SETTING.getKey(), "test").build(), MeterRegistry.NOOP);
when(clusterService.getClusterSettings()).thenReturn(
@@ -109,7 +121,13 @@ public void setup() {
() -> GeoIpDownloaderTaskExecutor.POLL_INTERVAL_SETTING.getDefault(Settings.EMPTY),
() -> GeoIpDownloaderTaskExecutor.EAGER_DOWNLOAD_SETTING.getDefault(Settings.EMPTY),
() -> true
- );
+ ) {
+ {
+ GeoIpTaskParams geoIpTaskParams = mock(GeoIpTaskParams.class);
+ when(geoIpTaskParams.getWriteableName()).thenReturn(GeoIpDownloader.GEOIP_DOWNLOADER);
+ init(new PersistentTasksService(clusterService, threadPool, client), null, null, 0);
+ }
+ };
}
@After
@@ -541,6 +559,78 @@ public void testUpdateDatabasesIndexNotReady() {
verifyNoInteractions(httpClient);
}
+ public void testThatRunDownloaderDeletesExpiredDatabases() {
+ /*
+ * This test puts some expired databases and some non-expired ones into the GeoIpTaskState, and then calls runDownloader(), making
+ * sure that the expired databases have been deleted.
+ */
+ AtomicInteger updatePersistentTaskStateCount = new AtomicInteger(0);
+ AtomicInteger deleteCount = new AtomicInteger(0);
+ int expiredDatabasesCount = randomIntBetween(1, 100);
+ int unexpiredDatabasesCount = randomIntBetween(0, 100);
+ Map databases = new HashMap<>();
+ for (int i = 0; i < expiredDatabasesCount; i++) {
+ databases.put("expiredDatabase" + i, newGeoIpTaskStateMetadata(true));
+ }
+ for (int i = 0; i < unexpiredDatabasesCount; i++) {
+ databases.put("unexpiredDatabase" + i, newGeoIpTaskStateMetadata(false));
+ }
+ GeoIpTaskState geoIpTaskState = new GeoIpTaskState(databases);
+ geoIpDownloader.setState(geoIpTaskState);
+ client.addHandler(
+ UpdatePersistentTaskStatusAction.INSTANCE,
+ (UpdatePersistentTaskStatusAction.Request request, ActionListener taskResponseListener) -> {
+ PersistentTasksCustomMetadata.Assignment assignment = mock(PersistentTasksCustomMetadata.Assignment.class);
+ PersistentTasksCustomMetadata.PersistentTask> persistentTask = new PersistentTasksCustomMetadata.PersistentTask<>(
+ GeoIpDownloader.GEOIP_DOWNLOADER,
+ GeoIpDownloader.GEOIP_DOWNLOADER,
+ new GeoIpTaskParams(),
+ request.getAllocationId(),
+ assignment
+ );
+ updatePersistentTaskStateCount.incrementAndGet();
+ taskResponseListener.onResponse(new PersistentTaskResponse(new PersistentTask<>(persistentTask, request.getState())));
+ }
+ );
+ client.addHandler(
+ DeleteByQueryAction.INSTANCE,
+ (DeleteByQueryRequest request, ActionListener flushResponseActionListener) -> {
+ deleteCount.incrementAndGet();
+ }
+ );
+ geoIpDownloader.runDownloader();
+ assertThat(geoIpDownloader.getStatus().getExpiredDatabases(), equalTo(expiredDatabasesCount));
+ for (int i = 0; i < expiredDatabasesCount; i++) {
+ // This currently fails because we subtract one millisecond from the lastChecked time
+ // assertThat(geoIpDownloader.state.getDatabases().get("expiredDatabase" + i).lastCheck(), equalTo(-1L));
+ }
+ for (int i = 0; i < unexpiredDatabasesCount; i++) {
+ assertThat(
+ geoIpDownloader.state.getDatabases().get("unexpiredDatabase" + i).lastCheck(),
+ greaterThanOrEqualTo(Instant.now().minus(30, ChronoUnit.DAYS).toEpochMilli())
+ );
+ }
+ assertThat(deleteCount.get(), equalTo(expiredDatabasesCount));
+ assertThat(updatePersistentTaskStateCount.get(), equalTo(expiredDatabasesCount));
+ geoIpDownloader.runDownloader();
+ /*
+ * The following two lines assert current behavior that might not be desirable -- we continue to delete expired databases every
+ * time that runDownloader runs. This seems unnecessary.
+ */
+ assertThat(deleteCount.get(), equalTo(expiredDatabasesCount * 2));
+ assertThat(updatePersistentTaskStateCount.get(), equalTo(expiredDatabasesCount * 2));
+ }
+
+ private GeoIpTaskState.Metadata newGeoIpTaskStateMetadata(boolean expired) {
+ Instant lastChecked;
+ if (expired) {
+ lastChecked = Instant.now().minus(randomIntBetween(31, 100), ChronoUnit.DAYS);
+ } else {
+ lastChecked = Instant.now().minus(randomIntBetween(0, 29), ChronoUnit.DAYS);
+ }
+ return new GeoIpTaskState.Metadata(0, 0, 0, randomAlphaOfLength(20), lastChecked.toEpochMilli());
+ }
+
private static class MockClient extends NoOpClient {
private final Map, BiConsumer extends ActionRequest, ? extends ActionListener>>> handlers = new HashMap<>();
diff --git a/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java b/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java
index d53c379a37644..72b48c5903629 100644
--- a/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java
+++ b/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java
@@ -14,6 +14,7 @@
import org.elasticsearch.action.ActionRunnable;
import org.elasticsearch.cluster.metadata.RepositoryMetadata;
import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.ReferenceDocs;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.blobstore.BlobPath;
import org.elasticsearch.common.blobstore.BlobStore;
@@ -443,4 +444,19 @@ protected void doClose() {
}
super.doClose();
}
+
+ @Override
+ public String getAnalysisFailureExtraDetail() {
+ return Strings.format(
+ """
+ Elasticsearch observed the storage system underneath this repository behaved incorrectly which indicates it is not \
+ suitable for use with Elasticsearch snapshots. Typically this happens when using storage other than AWS S3 which \
+ incorrectly claims to be S3-compatible. If so, please report this incompatibility to your storage supplier. Do not report \
+ Elasticsearch issues involving storage systems which claim to be S3-compatible unless you can demonstrate that the same \
+ issue exists when using a genuine AWS S3 repository. See [%s] for further information about repository analysis, and [%s] \
+ for further information about support for S3-compatible repository implementations.""",
+ ReferenceDocs.SNAPSHOT_REPOSITORY_ANALYSIS,
+ ReferenceDocs.S3_COMPATIBLE_REPOSITORIES
+ );
+ }
}
diff --git a/modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java b/modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java
index fcb0e82505dac..4bbc791e5fe21 100644
--- a/modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java
+++ b/modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java
@@ -11,6 +11,7 @@
import com.amazonaws.services.s3.AbstractAmazonS3;
import org.elasticsearch.cluster.metadata.RepositoryMetadata;
+import org.elasticsearch.common.ReferenceDocs;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
@@ -28,6 +29,7 @@
import java.util.Map;
+import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@@ -152,4 +154,24 @@ private S3Repository createS3Repo(RepositoryMetadata metadata) {
);
}
+ public void testAnalysisFailureDetail() {
+ try (
+ S3Repository s3repo = createS3Repo(
+ new RepositoryMetadata("dummy-repo", "mock", Settings.builder().put(S3Repository.BUCKET_SETTING.getKey(), "bucket").build())
+ )
+ ) {
+ assertThat(
+ s3repo.getAnalysisFailureExtraDetail(),
+ allOf(
+ containsString("storage system underneath this repository behaved incorrectly"),
+ containsString("incorrectly claims to be S3-compatible"),
+ containsString("report this incompatibility to your storage supplier"),
+ containsString("unless you can demonstrate that the same issue exists when using a genuine AWS S3 repository"),
+ containsString(ReferenceDocs.SNAPSHOT_REPOSITORY_ANALYSIS.toString()),
+ containsString(ReferenceDocs.S3_COMPATIBLE_REPOSITORIES.toString())
+ )
+ );
+ }
+ }
+
}
diff --git a/muted-tests.yml b/muted-tests.yml
index ccbdb68fbb8c7..18943a03219a9 100644
--- a/muted-tests.yml
+++ b/muted-tests.yml
@@ -47,21 +47,9 @@ tests:
- class: "org.elasticsearch.xpack.test.rest.XPackRestIT"
issue: "https://github.com/elastic/elasticsearch/issues/109687"
method: "test {p0=sql/translate/Translate SQL}"
-- class: org.elasticsearch.action.search.SearchProgressActionListenerIT
- method: testSearchProgressWithHits
- issue: https://github.com/elastic/elasticsearch/issues/109830
-- class: "org.elasticsearch.xpack.security.ScrollHelperIntegTests"
- issue: "https://github.com/elastic/elasticsearch/issues/109905"
- method: "testFetchAllEntities"
-- class: "org.elasticsearch.xpack.esql.action.AsyncEsqlQueryActionIT"
- issue: "https://github.com/elastic/elasticsearch/issues/109944"
- method: "testBasicAsyncExecution"
- class: "org.elasticsearch.action.admin.indices.rollover.RolloverIT"
issue: "https://github.com/elastic/elasticsearch/issues/110034"
method: "testRolloverWithClosedWriteIndex"
-- class: org.elasticsearch.xpack.transform.transforms.TransformIndexerTests
- method: testMaxPageSearchSizeIsResetToConfiguredValue
- issue: https://github.com/elastic/elasticsearch/issues/109844
- class: org.elasticsearch.index.store.FsDirectoryFactoryTests
method: testStoreDirectory
issue: https://github.com/elastic/elasticsearch/issues/110210
@@ -70,18 +58,12 @@ tests:
issue: https://github.com/elastic/elasticsearch/issues/110211
- class: "org.elasticsearch.rest.RestControllerIT"
issue: "https://github.com/elastic/elasticsearch/issues/110225"
-- class: "org.elasticsearch.xpack.security.authz.store.NativePrivilegeStoreCacheTests"
- issue: "https://github.com/elastic/elasticsearch/issues/110227"
- method: "testGetPrivilegesUsesCache"
- class: org.elasticsearch.upgrades.SecurityIndexRolesMetadataMigrationIT
method: testMetadataMigratedAfterUpgrade
issue: https://github.com/elastic/elasticsearch/issues/110232
- class: org.elasticsearch.compute.lucene.ValueSourceReaderTypeConversionTests
method: testLoadAll
issue: https://github.com/elastic/elasticsearch/issues/110244
-- class: org.elasticsearch.action.search.SearchProgressActionListenerIT
- method: testSearchProgressWithQuery
- issue: https://github.com/elastic/elasticsearch/issues/109867
- class: org.elasticsearch.backwards.SearchWithMinCompatibleSearchNodeIT
method: testMinVersionAsNewVersion
issue: https://github.com/elastic/elasticsearch/issues/95384
@@ -91,9 +73,6 @@ tests:
- class: org.elasticsearch.backwards.SearchWithMinCompatibleSearchNodeIT
method: testMinVersionAsOldVersion
issue: https://github.com/elastic/elasticsearch/issues/109454
-- class: org.elasticsearch.search.aggregations.bucket.terms.RareTermsIT
- method: testSingleValuedString
- issue: https://github.com/elastic/elasticsearch/issues/110388
- class: "org.elasticsearch.xpack.searchablesnapshots.FrozenSearchableSnapshotsIntegTests"
issue: "https://github.com/elastic/elasticsearch/issues/110408"
method: "testCreateAndRestorePartialSearchableSnapshot"
diff --git a/qa/ccs-rolling-upgrade-remote-cluster/build.gradle b/qa/ccs-rolling-upgrade-remote-cluster/build.gradle
index c48674831c422..b63522daa4b4c 100644
--- a/qa/ccs-rolling-upgrade-remote-cluster/build.gradle
+++ b/qa/ccs-rolling-upgrade-remote-cluster/build.gradle
@@ -58,7 +58,11 @@ BuildParams.bwcVersions.withWireCompatible { bwcVersion, baseName ->
dependsOn "processTestResources"
mustRunAfter("precommit")
doFirst {
- localCluster.get().nextNodeToNextVersion()
+ def cluster = localCluster.get()
+ cluster.nodes.forEach { node ->
+ node.getAllTransportPortURI()
+ }
+ cluster.nextNodeToNextVersion()
}
}
diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java
new file mode 100644
index 0000000000000..c80911fe5fbcf
--- /dev/null
+++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.upgrades;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+
+import org.elasticsearch.client.Request;
+import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.core.SuppressForbidden;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.test.cluster.FeatureFlag;
+import org.elasticsearch.test.cluster.local.DefaultLocalClusterSpecBuilder;
+import org.elasticsearch.test.cluster.local.distribution.DistributionType;
+import org.elasticsearch.test.cluster.util.Version;
+import org.elasticsearch.test.cluster.util.resource.Resource;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class FileSettingsUpgradeIT extends ParameterizedRollingUpgradeTestCase {
+
+ @BeforeClass
+ public static void checkVersion() {
+ assumeTrue("Only valid when upgrading from pre-file settings", getOldClusterTestVersion().before(new Version(8, 4, 0)));
+ }
+
+ private static final String settingsJSON = """
+ {
+ "metadata": {
+ "version": "1",
+ "compatibility": "8.4.0"
+ },
+ "state": {
+ "cluster_settings": {
+ "indices.recovery.max_bytes_per_sec": "50mb"
+ }
+ }
+ }""";
+
+ private static final TemporaryFolder repoDirectory = new TemporaryFolder();
+
+ private static final ElasticsearchCluster cluster = new DefaultLocalClusterSpecBuilder().distribution(DistributionType.DEFAULT)
+ .version(getOldClusterTestVersion())
+ .nodes(NODE_NUM)
+ .setting("path.repo", new Supplier<>() {
+ @Override
+ @SuppressForbidden(reason = "TemporaryFolder only has io.File methods, not nio.File")
+ public String get() {
+ return repoDirectory.getRoot().getPath();
+ }
+ })
+ .setting("xpack.security.enabled", "false")
+ .feature(FeatureFlag.TIME_SERIES_MODE)
+ .configFile("operator/settings.json", Resource.fromString(settingsJSON))
+ .build();
+
+ @ClassRule
+ public static TestRule ruleChain = RuleChain.outerRule(repoDirectory).around(cluster);
+
+ public FileSettingsUpgradeIT(@Name("upgradedNodes") int upgradedNodes) {
+ super(upgradedNodes);
+ }
+
+ @Override
+ protected ElasticsearchCluster getUpgradeCluster() {
+ return cluster;
+ }
+
+ public void testFileSettingsApplied() throws IOException {
+ if (isUpgradedCluster()) {
+ // the nodes have all been upgraded. Check they read the file settings ok
+ Map response = responseAsMap(adminClient().performRequest(new Request("GET", "/_cluster/settings")));
+ assertThat(XContentMapValues.extractValue(response, "persistent", "indices", "recovery", "max_bytes_per_sec"), equalTo("50mb"));
+ }
+ }
+}
diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/capabilities.json b/rest-api-spec/src/main/resources/rest-api-spec/api/capabilities.json
index 28c341d9983cc..a96be0d63834e 100644
--- a/rest-api-spec/src/main/resources/rest-api-spec/api/capabilities.json
+++ b/rest-api-spec/src/main/resources/rest-api-spec/api/capabilities.json
@@ -1,7 +1,7 @@
{
"capabilities": {
"documentation": {
- "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/capabilities.html",
+ "url": "https://github.com/elastic/elasticsearch/blob/main/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/README.asciidoc#require-or-skip-api-capabilities",
"description": "Checks if the specified combination of method, API, parameters, and arbitrary capabilities are supported"
},
"stability": "experimental",
diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml
index 7f0c24e217d14..825bcecf33fce 100644
--- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml
+++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml
@@ -287,6 +287,9 @@ setup:
- requires:
cluster_features: "gte_v8.4.0"
reason: 'kNN added to search endpoint in 8.4'
+ - skip:
+ cluster_features: "gte_v8.16.0"
+ reason: 'non-existent field handling improved in 8.16'
- do:
catch: bad_request
search:
@@ -298,9 +301,28 @@ setup:
query_vector: [ -0.5, 90.0, -10, 14.8, -156.0 ]
k: 2
num_candidates: 3
+
- match: { error.root_cause.0.type: "query_shard_exception" }
- match: { error.root_cause.0.reason: "failed to create query: field [nonexistent] does not exist in the mapping" }
+---
+"Test nonexistent field is match none":
+ - requires:
+ cluster_features: "gte_v8.16.0"
+ reason: 'non-existent field handling improved in 8.16'
+ - do:
+ search:
+ index: test
+ body:
+ fields: [ "name" ]
+ knn:
+ field: nonexistent
+ query_vector: [ -0.5, 90.0, -10, 14.8, -156.0 ]
+ k: 2
+ num_candidates: 3
+
+ - length: {hits.hits: 0}
+
---
"KNN Vector similarity search only":
- requires:
diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/45_knn_search_byte.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/45_knn_search_byte.yml
index 983ac2719e71b..806e5ff73b355 100644
--- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/45_knn_search_byte.yml
+++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/45_knn_search_byte.yml
@@ -148,6 +148,9 @@ setup:
---
"Test nonexistent field":
+ - skip:
+ cluster_features: 'gte_v8.16.0'
+ reason: 'non-existent field handling improved in 8.16'
- do:
catch: bad_request
search:
@@ -159,8 +162,26 @@ setup:
query_vector: [ 1, 0, 0, 0, -1 ]
k: 2
num_candidates: 3
+
- match: { error.root_cause.0.type: "query_shard_exception" }
- match: { error.root_cause.0.reason: "failed to create query: field [nonexistent] does not exist in the mapping" }
+---
+"Test nonexistent field is match none":
+ - requires:
+ cluster_features: 'gte_v8.16.0'
+ reason: 'non-existent field handling improved in 8.16'
+ - do:
+ search:
+ index: test
+ body:
+ fields: [ "name" ]
+ knn:
+ field: nonexistent
+ query_vector: [ 1, 0, 0, 0, -1 ]
+ k: 2
+ num_candidates: 3
+
+ - length: {hits.hits: 0}
---
"Vector similarity search only":
diff --git a/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java b/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java
index c2d1cdae85cd9..0368dec76df0e 100644
--- a/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java
+++ b/server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java
@@ -49,6 +49,7 @@
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.util.CollectionUtils;
+import org.elasticsearch.common.util.FeatureFlag;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.concurrent.CountDown;
import org.elasticsearch.common.util.concurrent.EsExecutors;
@@ -121,6 +122,8 @@ public class TransportSearchAction extends HandledTransportAction SHARD_COUNT_LIMIT_SETTING = Setting.longSetting(
"action.search.shard_count.limit",
diff --git a/server/src/main/java/org/elasticsearch/bootstrap/BootstrapChecks.java b/server/src/main/java/org/elasticsearch/bootstrap/BootstrapChecks.java
index a60262ff4a097..84811362c08e6 100644
--- a/server/src/main/java/org/elasticsearch/bootstrap/BootstrapChecks.java
+++ b/server/src/main/java/org/elasticsearch/bootstrap/BootstrapChecks.java
@@ -584,7 +584,7 @@ public BootstrapCheckResult check(BootstrapContext context) {
// visible for testing
boolean isSystemCallFilterInstalled() {
- return Natives.isSystemCallFilterInstalled();
+ return NativeAccess.instance().getExecSandboxState() != NativeAccess.ExecSandboxState.NONE;
}
@Override
@@ -608,7 +608,7 @@ public BootstrapCheckResult check(BootstrapContext context) {
// visible for testing
boolean isSystemCallFilterInstalled() {
- return Natives.isSystemCallFilterInstalled();
+ return NativeAccess.instance().getExecSandboxState() != NativeAccess.ExecSandboxState.NONE;
}
// visible for testing
diff --git a/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java b/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java
index f8ad9dd59650c..005375bf38540 100644
--- a/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java
+++ b/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java
@@ -27,16 +27,6 @@ public final class BootstrapInfo {
/** no instantiation */
private BootstrapInfo() {}
- /**
- * Returns true if we successfully loaded native libraries.
- *
- * If this returns false, then native operations such as locking
- * memory did not work.
- */
- public static boolean isNativesAvailable() {
- return Natives.JNA_AVAILABLE;
- }
-
/**
* Returns true if we were able to lock the process's address space.
*/
@@ -44,13 +34,6 @@ public static boolean isMemoryLocked() {
return NativeAccess.instance().isMemoryLocked();
}
- /**
- * Returns true if system call filter is installed (supported systems only)
- */
- public static boolean isSystemCallFilterInstalled() {
- return Natives.isSystemCallFilterInstalled();
- }
-
/**
* Returns information about the console (tty) attached to the server process, or {@code null}
* if no console is attached.
diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java
index 082e1dd9257e0..3fc659cb8065d 100644
--- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java
+++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java
@@ -293,7 +293,7 @@ static void initializeNatives(final Path tmpFile, final boolean mlockAll, final
*
* TODO: should we fail hard here if system call filters fail to install, or remain lenient in non-production environments?
*/
- Natives.tryInstallSystemCallFilter(tmpFile);
+ nativeAccess.tryInstallExecSandbox();
}
// mlockall if requested
@@ -316,13 +316,6 @@ static void initializeNatives(final Path tmpFile, final boolean mlockAll, final
}
}
- // force remainder of JNA to be loaded (if available).
- try {
- JNAKernel32Library.getInstance();
- } catch (Exception ignored) {
- // we've already logged this.
- }
-
// init lucene random seed. it will use /dev/urandom where available:
StringHelper.randomId();
diff --git a/server/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java b/server/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java
deleted file mode 100644
index 01d9a122138f1..0000000000000
--- a/server/src/main/java/org/elasticsearch/bootstrap/JNAKernel32Library.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-package org.elasticsearch.bootstrap;
-
-import com.sun.jna.IntegerType;
-import com.sun.jna.Native;
-import com.sun.jna.NativeLong;
-import com.sun.jna.Pointer;
-import com.sun.jna.Structure;
-import com.sun.jna.WString;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.lucene.util.Constants;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Library for Windows/Kernel32
- */
-final class JNAKernel32Library {
-
- private static final Logger logger = LogManager.getLogger(JNAKernel32Library.class);
-
- // Native library instance must be kept around for the same reason.
- private static final class Holder {
- private static final JNAKernel32Library instance = new JNAKernel32Library();
- }
-
- private JNAKernel32Library() {
- if (Constants.WINDOWS) {
- try {
- Native.register("kernel32");
- logger.debug("windows/Kernel32 library loaded");
- } catch (NoClassDefFoundError e) {
- logger.warn("JNA not found. native methods and handlers will be disabled.");
- } catch (UnsatisfiedLinkError e) {
- logger.warn("unable to link Windows/Kernel32 library. native methods and handlers will be disabled.");
- }
- }
- }
-
- static JNAKernel32Library getInstance() {
- return Holder.instance;
- }
-
- /**
- * Memory protection constraints
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786%28v=vs.85%29.aspx
- */
- public static final int PAGE_NOACCESS = 0x0001;
- public static final int PAGE_GUARD = 0x0100;
- public static final int MEM_COMMIT = 0x1000;
-
- /**
- * Contains information about a range of pages in the virtual address space of a process.
- * The VirtualQuery and VirtualQueryEx functions use this structure.
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/aa366775%28v=vs.85%29.aspx
- */
- public static class MemoryBasicInformation extends Structure {
- public Pointer BaseAddress;
- public Pointer AllocationBase;
- public NativeLong AllocationProtect;
- public SizeT RegionSize;
- public NativeLong State;
- public NativeLong Protect;
- public NativeLong Type;
-
- @Override
- protected List getFieldOrder() {
- return Arrays.asList("BaseAddress", "AllocationBase", "AllocationProtect", "RegionSize", "State", "Protect", "Type");
- }
- }
-
- public static class SizeT extends IntegerType {
-
- // JNA requires this no-arg constructor to be public,
- // otherwise it fails to register kernel32 library
- public SizeT() {
- this(0);
- }
-
- SizeT(long value) {
- super(Native.SIZE_T_SIZE, value);
- }
-
- }
-
- /**
- * Locks the specified region of the process's virtual address space into physical
- * memory, ensuring that subsequent access to the region will not incur a page fault.
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/aa366895%28v=vs.85%29.aspx
- *
- * @param address A pointer to the base address of the region of pages to be locked.
- * @param size The size of the region to be locked, in bytes.
- * @return true if the function succeeds
- */
- native boolean VirtualLock(Pointer address, SizeT size);
-
- /**
- * Retrieves information about a range of pages within the virtual address space of a specified process.
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/aa366907%28v=vs.85%29.aspx
- *
- * @param handle A handle to the process whose memory information is queried.
- * @param address A pointer to the base address of the region of pages to be queried.
- * @param memoryInfo A pointer to a structure in which information about the specified page range is returned.
- * @param length The size of the buffer pointed to by the memoryInfo parameter, in bytes.
- * @return the actual number of bytes returned in the information buffer.
- */
- native int VirtualQueryEx(Pointer handle, Pointer address, MemoryBasicInformation memoryInfo, int length);
-
- /**
- * Sets the minimum and maximum working set sizes for the specified process.
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/ms686234%28v=vs.85%29.aspx
- *
- * @param handle A handle to the process whose working set sizes is to be set.
- * @param minSize The minimum working set size for the process, in bytes.
- * @param maxSize The maximum working set size for the process, in bytes.
- * @return true if the function succeeds.
- */
- native boolean SetProcessWorkingSetSize(Pointer handle, SizeT minSize, SizeT maxSize);
-
- /**
- * Retrieves a pseudo handle for the current process.
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683179%28v=vs.85%29.aspx
- *
- * @return a pseudo handle to the current process.
- */
- native Pointer GetCurrentProcess();
-
- /**
- * Closes an open object handle.
- *
- * https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211%28v=vs.85%29.aspx
- *
- * @param handle A valid handle to an open object.
- * @return true if the function succeeds.
- */
- native boolean CloseHandle(Pointer handle);
-
- /**
- * Retrieves the short path form of the specified path. See
- * {@code GetShortPathName}.
- *
- * @param lpszLongPath the path string
- * @param lpszShortPath a buffer to receive the short name
- * @param cchBuffer the size of the buffer
- * @return the length of the string copied into {@code lpszShortPath}, otherwise zero for failure
- */
- native int GetShortPathNameW(WString lpszLongPath, char[] lpszShortPath, int cchBuffer);
-
- /**
- * 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(
- "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/server/src/main/java/org/elasticsearch/bootstrap/JNANatives.java b/server/src/main/java/org/elasticsearch/bootstrap/JNANatives.java
deleted file mode 100644
index ba4e90ee2c6c1..0000000000000
--- a/server/src/main/java/org/elasticsearch/bootstrap/JNANatives.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-package org.elasticsearch.bootstrap;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.nio.file.Path;
-
-/**
- * This class performs the actual work with JNA and library bindings to call native methods. It should only be used after
- * we are sure that the JNA classes are available to the JVM
- */
-class JNANatives {
-
- /** no instantiation */
- private JNANatives() {}
-
- private static final Logger logger = LogManager.getLogger(JNANatives.class);
-
- // Set to true, in case native system call filter install was successful
- static boolean LOCAL_SYSTEM_CALL_FILTER = false;
- // Set to true, in case policy can be applied to all threads of the process (even existing ones)
- // otherwise they are only inherited for new threads (ES app threads)
- static boolean LOCAL_SYSTEM_CALL_FILTER_ALL = false;
-
- static void tryInstallSystemCallFilter(Path tmpFile) {
- try {
- int ret = SystemCallFilter.init(tmpFile);
- LOCAL_SYSTEM_CALL_FILTER = true;
- if (ret == 1) {
- LOCAL_SYSTEM_CALL_FILTER_ALL = true;
- }
- } catch (Exception e) {
- // this is likely to happen unless the kernel is newish, its a best effort at the moment
- // so we log stacktrace at debug for now...
- if (logger.isDebugEnabled()) {
- logger.debug("unable to install syscall filter", e);
- }
- logger.warn("unable to install syscall filter: ", e);
- }
- }
-
-}
diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Natives.java b/server/src/main/java/org/elasticsearch/bootstrap/Natives.java
deleted file mode 100644
index c792d1e0bfad0..0000000000000
--- a/server/src/main/java/org/elasticsearch/bootstrap/Natives.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-package org.elasticsearch.bootstrap;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.elasticsearch.common.ReferenceDocs;
-
-import java.lang.invoke.MethodHandles;
-import java.nio.file.Path;
-import java.util.Locale;
-
-/**
- * The Natives class is a wrapper class that checks if the classes necessary for calling native methods are available on
- * startup. If they are not available, this class will avoid calling code that loads these classes.
- */
-final class Natives {
- /** no instantiation */
- private Natives() {}
-
- private static final Logger logger = LogManager.getLogger(Natives.class);
-
- // marker to determine if the JNA class files are available to the JVM
- static final boolean JNA_AVAILABLE;
-
- static {
- boolean v = false;
- try {
- // load one of the main JNA classes to see if the classes are available. this does not ensure that all native
- // libraries are available, only the ones necessary by JNA to function
- MethodHandles.publicLookup().ensureInitialized(com.sun.jna.Native.class);
- v = true;
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- } catch (UnsatisfiedLinkError e) {
- logger.warn(
- String.format(
- Locale.ROOT,
- "unable to load JNA native support library, native methods will be disabled. See %s",
- ReferenceDocs.EXECUTABLE_JNA_TMPDIR
- ),
- e
- );
- }
- JNA_AVAILABLE = v;
- }
-
- static void tryInstallSystemCallFilter(Path tmpFile) {
- if (JNA_AVAILABLE == false) {
- logger.warn("cannot install system call filter because JNA is not available");
- return;
- }
- JNANatives.tryInstallSystemCallFilter(tmpFile);
- }
-
- static boolean isSystemCallFilterInstalled() {
- if (JNA_AVAILABLE == false) {
- return false;
- }
- return JNANatives.LOCAL_SYSTEM_CALL_FILTER;
- }
-
-}
diff --git a/server/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java b/server/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java
deleted file mode 100644
index 0ab855d1d5f3a..0000000000000
--- a/server/src/main/java/org/elasticsearch/bootstrap/SystemCallFilter.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-package org.elasticsearch.bootstrap;
-
-import com.sun.jna.Library;
-import com.sun.jna.Memory;
-import com.sun.jna.Native;
-import com.sun.jna.NativeLong;
-import com.sun.jna.Pointer;
-import com.sun.jna.Structure;
-import com.sun.jna.ptr.PointerByReference;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.lucene.util.Constants;
-import org.elasticsearch.core.IOUtils;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Installs a system call filter to block process execution.
- *
- * 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.
- *
- * On Linux BPF Filters are installed using either {@code seccomp(2)} (3.17+) or {@code prctl(2)} (3.5+). {@code seccomp(2)}
- * is preferred, as it allows filters to be applied to any existing threads in the process, and one motivation
- * here is to protect against bugs in the JVM. Otherwise, code will fall back to the {@code prctl(2)} method
- * which will at least protect elasticsearch application threads.
- *
- * Linux BPF filters will return {@code EACCES} (Access Denied) for the following system calls:
- *
- *
{@code execve}
- *
{@code fork}
- *
{@code vfork}
- *
{@code execveat}
- *
- *
- * On Solaris 10 or higher, the following privileges are dropped with {@code priv_set(3C)}:
- *
- *
{@code PRIV_PROC_FORK}
- *
{@code PRIV_PROC_EXEC}
- *
- *
- * On BSD systems, process creation is restricted with {@code setrlimit(RLIMIT_NPROC)}.
- *
- * On Mac OS X Leopard or above, a custom {@code sandbox(7)} ("Seatbelt") profile is installed that
- * denies the following rules:
- *
- *
{@code process-fork}
- *
{@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
- * http://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt
- * @see
- * https://reverse.put.as/wp-content/uploads/2011/06/The-Apple-Sandbox-BHDC2011-Paper.pdf
- * @see
- * https://docs.oracle.com/cd/E23824_01/html/821-1456/prbac-2.html
- */
-// not an example of how to write code!!!
-final class SystemCallFilter {
- private static final Logger logger = LogManager.getLogger(SystemCallFilter.class);
-
- // Linux implementation, based on seccomp(2) or prctl(2) with bpf filtering
-
- /** Access to non-standard Linux libc methods */
- interface LinuxLibrary extends Library {
- /**
- * maps to prctl(2)
- */
- int prctl(int option, NativeLong arg2, NativeLong arg3, NativeLong arg4, NativeLong arg5);
-
- /**
- * used to call seccomp(2), its too new...
- * this is the only way, DON'T use it on some other architecture unless you know wtf you are doing
- */
- NativeLong syscall(NativeLong number, Object... args);
- }
-
- // null if unavailable or something goes wrong.
- private static final LinuxLibrary linux_libc;
-
- static {
- LinuxLibrary lib = null;
- if (Constants.LINUX) {
- try {
- lib = Native.loadLibrary("c", LinuxLibrary.class);
- } catch (UnsatisfiedLinkError e) {
- logger.warn("unable to link C library. native methods (seccomp) will be disabled.", e);
- }
- }
- linux_libc = lib;
- }
-
- /** the preferred method is seccomp(2), since we can apply to all threads of the process */
- static final int SECCOMP_SET_MODE_FILTER = 1; // since Linux 3.17
- static final int SECCOMP_FILTER_FLAG_TSYNC = 1; // since Linux 3.17
-
- /** otherwise, we can use prctl(2), which will at least protect ES application threads */
- static final int PR_GET_NO_NEW_PRIVS = 39; // since Linux 3.5
- static final int PR_SET_NO_NEW_PRIVS = 38; // since Linux 3.5
- static final int PR_GET_SECCOMP = 21; // since Linux 2.6.23
- static final int PR_SET_SECCOMP = 22; // since Linux 2.6.23
- static final long SECCOMP_MODE_FILTER = 2; // since Linux Linux 3.5
-
- /** corresponds to struct sock_filter */
- static final class SockFilter {
- short code; // insn
- byte jt; // number of insn to jump (skip) if true
- byte jf; // number of insn to jump (skip) if false
- int k; // additional data
-
- SockFilter(short code, byte jt, byte jf, int k) {
- this.code = code;
- this.jt = jt;
- this.jf = jf;
- this.k = k;
- }
- }
-
- /** corresponds to struct sock_fprog */
- public static final class SockFProg extends Structure implements Structure.ByReference {
- public short len; // number of filters
- public Pointer filter; // filters
-
- SockFProg(SockFilter filters[]) {
- len = (short) filters.length;
- // serialize struct sock_filter * explicitly, its less confusing than the JNA magic we would need
- Memory filter = new Memory(len * 8);
- ByteBuffer bbuf = filter.getByteBuffer(0, len * 8);
- bbuf.order(ByteOrder.nativeOrder()); // little endian
- for (SockFilter f : filters) {
- bbuf.putShort(f.code);
- bbuf.put(f.jt);
- bbuf.put(f.jf);
- bbuf.putInt(f.k);
- }
- this.filter = filter;
- }
-
- @Override
- protected List getFieldOrder() {
- return Arrays.asList("len", "filter");
- }
- }
-
- // BPF "macros" and constants
- static final int BPF_LD = 0x00;
- static final int BPF_W = 0x00;
- static final int BPF_ABS = 0x20;
- static final int BPF_JMP = 0x05;
- static final int BPF_JEQ = 0x10;
- static final int BPF_JGE = 0x30;
- static final int BPF_JGT = 0x20;
- static final int BPF_RET = 0x06;
- static final int BPF_K = 0x00;
-
- static SockFilter BPF_STMT(int code, int k) {
- return new SockFilter((short) code, (byte) 0, (byte) 0, k);
- }
-
- static SockFilter BPF_JUMP(int code, int k, int jt, int jf) {
- return new SockFilter((short) code, (byte) jt, (byte) jf, k);
- }
-
- static final int SECCOMP_RET_ERRNO = 0x00050000;
- static final int SECCOMP_RET_DATA = 0x0000FFFF;
- static final int SECCOMP_RET_ALLOW = 0x7FFF0000;
-
- // some errno constants for error checking/handling
- static final int EACCES = 0x0D;
- static final int EFAULT = 0x0E;
- static final int EINVAL = 0x16;
- static final int ENOSYS = 0x26;
-
- // offsets that our BPF checks
- // check with offsetof() when adding a new arch, move to Arch if different.
- static final int SECCOMP_DATA_NR_OFFSET = 0x00;
- static final int SECCOMP_DATA_ARCH_OFFSET = 0x04;
-
- record Arch(
- int audit, // AUDIT_ARCH_XXX constant from linux/audit.h
- int limit, // syscall limit (necessary for blacklisting on amd64, to ban 32-bit syscalls)
- int fork, // __NR_fork
- int vfork, // __NR_vfork
- int execve, // __NR_execve
- int execveat, // __NR_execveat
- int seccomp // __NR_seccomp
- ) {}
-
- /** supported architectures map keyed by os.arch */
- private static final Map ARCHITECTURES;
- static {
- ARCHITECTURES = Map.of(
- "amd64",
- new Arch(0xC000003E, 0x3FFFFFFF, 57, 58, 59, 322, 317),
- "aarch64",
- new Arch(0xC00000B7, 0xFFFFFFFF, 1079, 1071, 221, 281, 277)
- );
- }
-
- /** invokes prctl() from linux libc library */
- private static int linux_prctl(int option, long arg2, long arg3, long arg4, long arg5) {
- return linux_libc.prctl(option, new NativeLong(arg2), new NativeLong(arg3), new NativeLong(arg4), new NativeLong(arg5));
- }
-
- /** invokes syscall() from linux libc library */
- private static long linux_syscall(long number, Object... args) {
- return linux_libc.syscall(new NativeLong(number), args).longValue();
- }
-
- /** try to install our BPF filters via seccomp() or prctl() to block execution */
- private static int linuxImpl() {
- // first be defensive: we can give nice errors this way, at the very least.
- // also, some of these security features get backported to old versions, checking kernel version here is a big no-no!
- final Arch arch = ARCHITECTURES.get(Constants.OS_ARCH);
- boolean supported = Constants.LINUX && arch != null;
- if (supported == false) {
- throw new UnsupportedOperationException("seccomp unavailable: '" + Constants.OS_ARCH + "' architecture unsupported");
- }
-
- // we couldn't link methods, could be some really ancient kernel (e.g. < 2.1.57) or some bug
- if (linux_libc == null) {
- throw new UnsupportedOperationException(
- "seccomp unavailable: could not link methods. requires kernel 3.5+ "
- + "with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in"
- );
- }
-
- // try to check system calls really are who they claim
- // you never know (e.g. https://chromium.googlesource.com/chromium/src.git/+/master/sandbox/linux/seccomp-bpf/sandbox_bpf.cc#57)
- final int bogusArg = 0xf7a46a5c;
-
- // test seccomp(BOGUS)
- long ret = linux_syscall(arch.seccomp, bogusArg);
- if (ret != -1) {
- throw new UnsupportedOperationException("seccomp unavailable: seccomp(BOGUS_OPERATION) returned " + ret);
- } else {
- int errno = Native.getLastError();
- switch (errno) {
- case ENOSYS:
- break; // ok
- case EINVAL:
- break; // ok
- default:
- throw new UnsupportedOperationException("seccomp(BOGUS_OPERATION): " + JNACLibrary.strerror(errno));
- }
- }
-
- // test seccomp(VALID, BOGUS)
- ret = linux_syscall(arch.seccomp, SECCOMP_SET_MODE_FILTER, bogusArg);
- if (ret != -1) {
- throw new UnsupportedOperationException("seccomp unavailable: seccomp(SECCOMP_SET_MODE_FILTER, BOGUS_FLAG) returned " + ret);
- } else {
- int errno = Native.getLastError();
- switch (errno) {
- case ENOSYS:
- break; // ok
- case EINVAL:
- break; // ok
- default:
- throw new UnsupportedOperationException("seccomp(SECCOMP_SET_MODE_FILTER, BOGUS_FLAG): " + JNACLibrary.strerror(errno));
- }
- }
-
- // test prctl(BOGUS)
- ret = linux_prctl(bogusArg, 0, 0, 0, 0);
- if (ret != -1) {
- throw new UnsupportedOperationException("seccomp unavailable: prctl(BOGUS_OPTION) returned " + ret);
- } else {
- int errno = Native.getLastError();
- switch (errno) {
- case ENOSYS:
- break; // ok
- case EINVAL:
- break; // ok
- default:
- throw new UnsupportedOperationException("prctl(BOGUS_OPTION): " + JNACLibrary.strerror(errno));
- }
- }
-
- // now just normal defensive checks
-
- // check for GET_NO_NEW_PRIVS
- switch (linux_prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0)) {
- case 0:
- break; // not yet set
- case 1:
- break; // already set by caller
- default:
- int errno = Native.getLastError();
- 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));
- }
- }
- // check for SECCOMP
- switch (linux_prctl(PR_GET_SECCOMP, 0, 0, 0, 0)) {
- case 0:
- break; // not yet set
- case 2:
- break; // already in filter mode by caller
- default:
- int errno = Native.getLastError();
- if (errno == EINVAL) {
- throw new UnsupportedOperationException(
- "seccomp unavailable: CONFIG_SECCOMP not compiled into kernel,"
- + " CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed"
- );
- } else {
- throw new UnsupportedOperationException("prctl(PR_GET_SECCOMP): " + JNACLibrary.strerror(errno));
- }
- }
- // check for SECCOMP_MODE_FILTER
- if (linux_prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0, 0, 0) != 0) {
- int errno = Native.getLastError();
- switch (errno) {
- case EFAULT:
- break; // available
- case EINVAL:
- throw new UnsupportedOperationException(
- "seccomp unavailable: CONFIG_SECCOMP_FILTER not"
- + " compiled into kernel, CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed"
- );
- default:
- throw new UnsupportedOperationException("prctl(PR_SET_SECCOMP): " + JNACLibrary.strerror(errno));
- }
- }
-
- // ok, now set PR_SET_NO_NEW_PRIVS, needed to be able to set a seccomp filter as ordinary user
- if (linux_prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
- throw new UnsupportedOperationException("prctl(PR_SET_NO_NEW_PRIVS): " + JNACLibrary.strerror(Native.getLastError()));
- }
-
- // check it worked
- if (linux_prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) != 1) {
- throw new UnsupportedOperationException(
- "seccomp filter did not really succeed: prctl(PR_GET_NO_NEW_PRIVS): " + JNACLibrary.strerror(Native.getLastError())
- );
- }
-
- // BPF installed to check arch, limit, then syscall.
- // See https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt for details.
- SockFilter insns[] = {
- /* 1 */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, SECCOMP_DATA_ARCH_OFFSET), //
- /* 2 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.audit, 0, 7), // if (arch != audit) goto fail;
- /* 3 */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, SECCOMP_DATA_NR_OFFSET), //
- /* 4 */ BPF_JUMP(BPF_JMP + BPF_JGT + BPF_K, arch.limit, 5, 0), // if (syscall > LIMIT) goto fail;
- /* 5 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.fork, 4, 0), // if (syscall == FORK) goto fail;
- /* 6 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.vfork, 3, 0), // if (syscall == VFORK) goto fail;
- /* 7 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.execve, 2, 0), // if (syscall == EXECVE) goto fail;
- /* 8 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch.execveat, 1, 0), // if (syscall == EXECVEAT) goto fail;
- /* 9 */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), // pass: return OK;
- /* 10 */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (EACCES & SECCOMP_RET_DATA)), // fail: return EACCES;
- };
- // seccomp takes a long, so we pass it one explicitly to keep the JNA simple
- SockFProg prog = new SockFProg(insns);
- prog.write();
- long pointer = Pointer.nativeValue(prog.getPointer());
-
- int method = 1;
- // install filter, if this works, after this there is no going back!
- // first try it with seccomp(SECCOMP_SET_MODE_FILTER), falling back to prctl()
- if (linux_syscall(arch.seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, new NativeLong(pointer)) != 0) {
- method = 0;
- int errno1 = Native.getLastError();
- if (logger.isDebugEnabled()) {
- logger.debug(
- "seccomp(SECCOMP_SET_MODE_FILTER): {}, falling back to prctl(PR_SET_SECCOMP)...",
- JNACLibrary.strerror(errno1)
- );
- }
- if (linux_prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, pointer, 0, 0) != 0) {
- int errno2 = Native.getLastError();
- throw new UnsupportedOperationException(
- "seccomp(SECCOMP_SET_MODE_FILTER): "
- + JNACLibrary.strerror(errno1)
- + ", prctl(PR_SET_SECCOMP): "
- + JNACLibrary.strerror(errno2)
- );
- }
- }
-
- // now check that the filter was really installed, we should be in filter mode.
- if (linux_prctl(PR_GET_SECCOMP, 0, 0, 0, 0) != 2) {
- throw new UnsupportedOperationException(
- "seccomp filter installation did not really succeed. seccomp(PR_GET_SECCOMP): "
- + JNACLibrary.strerror(Native.getLastError())
- );
- }
-
- logger.debug("Linux seccomp filter installation successful, threads: [{}]", method == 1 ? "all" : "app");
- return method;
- }
-
- // OS X implementation via sandbox(7)
-
- /** Access to non-standard OS X libc methods */
- interface MacLibrary extends Library {
- /**
- * maps to sandbox_init(3), since Leopard
- */
- int sandbox_init(String profile, long flags, PointerByReference errorbuf);
-
- /**
- * releases memory when an error occurs during initialization (e.g. syntax bug)
- */
- void sandbox_free_error(Pointer errorbuf);
- }
-
- // null if unavailable, or something goes wrong.
- private static final MacLibrary libc_mac;
-
- static {
- MacLibrary lib = null;
- if (Constants.MAC_OS_X) {
- try {
- lib = Native.loadLibrary("c", MacLibrary.class);
- } catch (UnsatisfiedLinkError e) {
- logger.warn("unable to link C library. native methods (seatbelt) will be disabled.", e);
- }
- }
- libc_mac = lib;
- }
-
- /** The only supported flag... */
- static final int SANDBOX_NAMED = 1;
- /** Allow everything except process fork and execution */
- static final String SANDBOX_RULES = "(version 1) (allow default) (deny process-fork) (deny process-exec)";
-
- /** try to install our custom rule profile into sandbox_init() to block execution */
- private static void macImpl(Path tmpFile) throws IOException {
- // first be defensive: we can give nice errors this way, at the very least.
- boolean supported = Constants.MAC_OS_X;
- if (supported == false) {
- throw new IllegalStateException("bug: should not be trying to initialize seatbelt for an unsupported OS");
- }
-
- // we couldn't link methods, could be some really ancient OS X (< Leopard) or some bug
- if (libc_mac == null) {
- throw new UnsupportedOperationException("seatbelt unavailable: could not link methods. requires Leopard or above.");
- }
-
- // write rules to a temporary file, which will be passed to sandbox_init()
- Path rules = Files.createTempFile(tmpFile, "es", "sb");
- Files.write(rules, Collections.singleton(SANDBOX_RULES));
-
- boolean success = false;
- try {
- PointerByReference errorRef = new PointerByReference();
- int ret = libc_mac.sandbox_init(rules.toAbsolutePath().toString(), SANDBOX_NAMED, errorRef);
- // if sandbox_init() fails, add the message from the OS (e.g. syntax error) and free the buffer
- if (ret != 0) {
- Pointer errorBuf = errorRef.getValue();
- RuntimeException e = new UnsupportedOperationException("sandbox_init(): " + errorBuf.getString(0));
- libc_mac.sandbox_free_error(errorBuf);
- throw e;
- }
- logger.debug("OS X seatbelt initialization successful");
- success = true;
- } finally {
- if (success) {
- Files.delete(rules);
- } else {
- IOUtils.deleteFilesIgnoringExceptions(rules);
- }
- }
- }
-
- // Solaris implementation via priv_set(3C)
-
- /** Access to non-standard Solaris libc methods */
- interface SolarisLibrary extends Library {
- /**
- * see priv_set(3C), a convenience method for setppriv(2).
- */
- int priv_set(int op, String which, String... privs);
- }
-
- // null if unavailable, or something goes wrong.
- private static final SolarisLibrary libc_solaris;
-
- static {
- SolarisLibrary lib = null;
- if (Constants.SUN_OS) {
- try {
- lib = Native.loadLibrary("c", SolarisLibrary.class);
- } catch (UnsatisfiedLinkError e) {
- logger.warn("unable to link C library. native methods (priv_set) will be disabled.", e);
- }
- }
- libc_solaris = lib;
- }
-
- // constants for priv_set(2)
- static final int PRIV_OFF = 1;
- static final String PRIV_ALLSETS = null;
- // see privileges(5) for complete list of these
- static final String PRIV_PROC_FORK = "proc_fork";
- static final String PRIV_PROC_EXEC = "proc_exec";
-
- static void solarisImpl() {
- // first be defensive: we can give nice errors this way, at the very least.
- boolean supported = Constants.SUN_OS;
- if (supported == false) {
- throw new IllegalStateException("bug: should not be trying to initialize priv_set for an unsupported OS");
- }
-
- // we couldn't link methods, could be some really ancient Solaris or some bug
- if (libc_solaris == null) {
- throw new UnsupportedOperationException("priv_set unavailable: could not link methods. requires Solaris 10+");
- }
-
- // drop a null-terminated list of privileges
- if (libc_solaris.priv_set(PRIV_OFF, PRIV_ALLSETS, PRIV_PROC_FORK, PRIV_PROC_EXEC, null) != 0) {
- throw new UnsupportedOperationException("priv_set unavailable: priv_set(): " + JNACLibrary.strerror(Native.getLastError()));
- }
-
- logger.debug("Solaris priv_set initialization successful");
- }
-
- // BSD implementation via setrlimit(2)
-
- // TODO: add OpenBSD to Lucene Constants
- // TODO: JNA doesn't have netbsd support, but this mechanism should work there too.
- static final boolean OPENBSD = Constants.OS_NAME.startsWith("OpenBSD");
-
- // not a standard limit, means something different on linux, etc!
- static final int RLIMIT_NPROC = 7;
-
- static void bsdImpl() {
- boolean supported = Constants.FREE_BSD || OPENBSD || Constants.MAC_OS_X;
- if (supported == false) {
- throw new IllegalStateException("bug: should not be trying to initialize RLIMIT_NPROC for an unsupported OS");
- }
-
- JNACLibrary.Rlimit limit = new JNACLibrary.Rlimit();
- limit.rlim_cur.setValue(0);
- limit.rlim_max.setValue(0);
- if (JNACLibrary.setrlimit(RLIMIT_NPROC, limit) != 0) {
- throw new UnsupportedOperationException("RLIMIT_NPROC unavailable: " + JNACLibrary.strerror(Native.getLastError()));
- }
-
- logger.debug("BSD RLIMIT_NPROC initialization successful");
- }
-
- // windows impl via job ActiveProcessLimit
-
- static void windowsImpl() {
- if (Constants.WINDOWS == false) {
- 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) == false) {
- 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()) == false) {
- throw new UnsupportedOperationException("SetInformationJobObject: " + Native.getLastError());
- }
- // assign ourselves to the job
- if (lib.AssignProcessToJobObject(job, lib.GetCurrentProcess()) == false) {
- 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.
- *