VYPR
Moderate severityNVD Advisory· Published May 1, 2025· Updated May 1, 2025

Elasticsearch Uncontrolled Resource Consumption vulnerability

CVE-2024-52979

Description

Uncontrolled Resource Consumption in Elasticsearch while evaluating specifically crafted search templates with Mustache functions can lead to Denial of Service by causing the Elasticsearch node to crash.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.elasticsearch:elasticsearchMaven
< 7.17.257.17.25
org.elasticsearch:elasticsearchMaven
>= 8.0.0-alpha1, < 8.16.08.16.0

Affected products

1

Patches

2
f9b6b57d1d0f

[7.17] Add a size limit to outputs from mustache (#114002) (#114705)

https://github.com/elastic/elasticsearchSimon CooperOct 14, 2024via ghsa
16 files changed · +149 21
  • docs/changelog/114002.yaml+5 0 added
    @@ -0,0 +1,5 @@
    +pr: 114002
    +summary: Add a `mustache.max_output_size_bytes` setting to limit the length of results from mustache scripts
    +area: Infra/Scripting
    +type: enhancement
    +issues: []
    
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MustachePlugin.java+1 1 modified
    @@ -34,7 +34,7 @@ public class MustachePlugin extends Plugin implements ScriptPlugin, ActionPlugin
     
         @Override
         public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
    -        return new MustacheScriptEngine();
    +        return new MustacheScriptEngine(settings);
         }
     
         @Override
    
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngine.java+26 1 modified
    @@ -15,7 +15,14 @@
     import org.apache.logging.log4j.Logger;
     import org.apache.logging.log4j.message.ParameterizedMessage;
     import org.apache.logging.log4j.util.Supplier;
    +import org.elasticsearch.ElasticsearchParseException;
    +import org.elasticsearch.ExceptionsHelper;
     import org.elasticsearch.SpecialPermission;
    +import org.elasticsearch.common.settings.Setting;
    +import org.elasticsearch.common.settings.Settings;
    +import org.elasticsearch.common.text.SizeLimitingStringWriter;
    +import org.elasticsearch.common.unit.ByteSizeValue;
    +import org.elasticsearch.common.unit.MemorySizeValue;
     import org.elasticsearch.script.GeneralScriptException;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.ScriptContext;
    @@ -45,6 +52,19 @@ public final class MustacheScriptEngine implements ScriptEngine {
     
         public static final String NAME = "mustache";
     
    +    public static final Setting<ByteSizeValue> MUSTACHE_RESULT_SIZE_LIMIT = new Setting<>(
    +        "mustache.max_output_size_bytes",
    +        s -> "1mb",
    +        s -> MemorySizeValue.parseBytesSizeValueOrHeapRatio(s, "mustache.max_output_size_bytes"),
    +        Setting.Property.NodeScope
    +    );
    +
    +    private final int sizeLimit;
    +
    +    public MustacheScriptEngine(Settings settings) {
    +        sizeLimit = (int) MUSTACHE_RESULT_SIZE_LIMIT.get(settings).getBytes();
    +    }
    +
         /**
          * Compile a template string to (in this case) a Mustache object than can
          * later be re-used for execution to fill in missing parameter values.
    @@ -106,7 +126,7 @@ private class MustacheExecutableScript extends TemplateScript {
     
             @Override
             public String execute() {
    -            final StringWriter writer = new StringWriter();
    +            final StringWriter writer = new SizeLimitingStringWriter(sizeLimit);
                 try {
                     // crazy reflection here
                     SpecialPermission.check();
    @@ -115,6 +135,11 @@ public String execute() {
                         return null;
                     });
                 } catch (Exception e) {
    +                // size limit exception can appear at several places in the causal list depending on script & context
    +                if (ExceptionsHelper.unwrap(e, SizeLimitingStringWriter.SizeLimitExceededException.class) != null) {
    +                    // don't log, client problem
    +                    throw new ElasticsearchParseException("Mustache script result size limit exceeded", e);
    +                }
                     logger.error((Supplier<?>) () -> new ParameterizedMessage("Error running {}", template), e);
                     throw new GeneralScriptException("Error running " + template, e);
                 }
    
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/CustomMustacheFactoryTests.java+4 3 modified
    @@ -8,6 +8,7 @@
     
     package org.elasticsearch.script.mustache;
     
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.ScriptEngine;
     import org.elasticsearch.script.TemplateScript;
    @@ -54,7 +55,7 @@ public void testCreateEncoder() {
         }
     
         public void testJsonEscapeEncoder() {
    -        final ScriptEngine engine = new MustacheScriptEngine();
    +        final ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             final Map<String, String> params = randomBoolean() ? singletonMap(Script.CONTENT_TYPE_OPTION, JSON_MIME_TYPE) : emptyMap();
     
             TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);
    @@ -64,7 +65,7 @@ public void testJsonEscapeEncoder() {
         }
     
         public void testDefaultEncoder() {
    -        final ScriptEngine engine = new MustacheScriptEngine();
    +        final ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             final Map<String, String> params = singletonMap(Script.CONTENT_TYPE_OPTION, PLAIN_TEXT_MIME_TYPE);
     
             TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);
    @@ -74,7 +75,7 @@ public void testDefaultEncoder() {
         }
     
         public void testUrlEncoder() {
    -        final ScriptEngine engine = new MustacheScriptEngine();
    +        final ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             final Map<String, String> params = singletonMap(Script.CONTENT_TYPE_OPTION, X_WWW_FORM_URLENCODED_MIME_TYPE);
     
             TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);
    
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MustacheScriptEngineTests.java+2 1 modified
    @@ -9,6 +9,7 @@
     
     import com.github.mustachejava.MustacheFactory;
     
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.TemplateScript;
     import org.elasticsearch.test.ESTestCase;
    @@ -33,7 +34,7 @@ public class MustacheScriptEngineTests extends ESTestCase {
     
         @Before
         public void setup() {
    -        qe = new MustacheScriptEngine();
    +        qe = new MustacheScriptEngine(Settings.EMPTY);
             factory = new CustomMustacheFactory();
         }
     
    
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MustacheTests.java+2 1 modified
    @@ -8,6 +8,7 @@
     package org.elasticsearch.script.mustache;
     
     import org.elasticsearch.common.bytes.BytesReference;
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.common.xcontent.XContentHelper;
     import org.elasticsearch.script.ScriptEngine;
     import org.elasticsearch.script.ScriptException;
    @@ -38,7 +39,7 @@
     
     public class MustacheTests extends ESTestCase {
     
    -    private ScriptEngine engine = new MustacheScriptEngine();
    +    private ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
     
         public void testBasics() {
             String template = "GET _search {\"query\": "
    
  • qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/java/org/elasticsearch/ingest/AbstractScriptTestCase.java+1 1 modified
    @@ -30,7 +30,7 @@ public abstract class AbstractScriptTestCase extends ESTestCase {
     
         @Before
         public void init() throws Exception {
    -        MustacheScriptEngine engine = new MustacheScriptEngine();
    +        MustacheScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             Map<String, ScriptEngine> engines = Collections.singletonMap(engine.getType(), engine);
             scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
         }
    
  • server/src/main/java/org/elasticsearch/common/text/SizeLimitingStringWriter.java+67 0 added
    @@ -0,0 +1,67 @@
    +/*
    + * 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.common.text;
    +
    +import java.io.StringWriter;
    +import java.util.Locale;
    +
    +/**
    + * A {@link StringWriter} that throws an exception if the string exceeds a specified size.
    + */
    +public class SizeLimitingStringWriter extends StringWriter {
    +
    +    public static class SizeLimitExceededException extends IllegalStateException {
    +        public SizeLimitExceededException(String message) {
    +            super(message);
    +        }
    +    }
    +
    +    private final int sizeLimit;
    +
    +    public SizeLimitingStringWriter(int sizeLimit) {
    +        this.sizeLimit = sizeLimit;
    +    }
    +
    +    private void checkSizeLimit(int additionalChars) {
    +        int bufLen = getBuffer().length();
    +        if (bufLen + additionalChars > sizeLimit) {
    +            String substring = getBuffer().substring(0, Math.min(bufLen, 20));
    +            throw new SizeLimitExceededException(
    +                String.format(Locale.ROOT, "String [%s...] has exceeded the size limit [%s]", substring, sizeLimit)
    +            );
    +        }
    +    }
    +
    +    @Override
    +    public void write(int c) {
    +        checkSizeLimit(1);
    +        super.write(c);
    +    }
    +
    +    // write(char[]) delegates to write(char[], int, int)
    +
    +    @Override
    +    public void write(char[] cbuf, int off, int len) {
    +        checkSizeLimit(len);
    +        super.write(cbuf, off, len);
    +    }
    +
    +    @Override
    +    public void write(String str) {
    +        checkSizeLimit(str.length());
    +        super.write(str);
    +    }
    +
    +    @Override
    +    public void write(String str, int off, int len) {
    +        checkSizeLimit(len);
    +        super.write(str, off, len);
    +    }
    +
    +    // append(...) delegates to write(...) methods
    +}
    
  • server/src/test/java/org/elasticsearch/common/text/SizeLimitingStringWriterTests.java+27 0 added
    @@ -0,0 +1,27 @@
    +/*
    + * 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.common.text;
    +
    +import org.elasticsearch.test.ESTestCase;
    +
    +public class SizeLimitingStringWriterTests extends ESTestCase {
    +    public void testSizeIsLimited() {
    +        SizeLimitingStringWriter writer = new SizeLimitingStringWriter(10);
    +
    +        writer.write("aaaaaaaaaa");
    +
    +        // test all the methods
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write('a'));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write("a"));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write(new char[1]));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write(new char[1], 0, 1));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.append('a'));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.append("a"));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.append("a", 0, 1));
    +    }
    +}
    
  • x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java+7 7 modified
    @@ -93,7 +93,7 @@ public void testEqualsAndHashCode() throws Exception {
         public void testEvaluateRoles() throws Exception {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             final ExpressionModel model = new ExpressionModel();
    @@ -149,7 +149,7 @@ public void tryEquals(TemplateRoleName original) {
         public void testValidate() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
     
    @@ -175,7 +175,7 @@ public void testValidate() {
         public void testValidateWillPassWithEmptyContext() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
     
    @@ -205,7 +205,7 @@ public void testValidateWillPassWithEmptyContext() {
         public void testValidateWillFailForSyntaxError() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
     
    @@ -267,7 +267,7 @@ public void testValidationWillFailWhenInlineScriptIsNotEnabled() {
             final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build();
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             final BytesReference inlineScript = new BytesArray("{ \"source\":\"\" }");
    @@ -282,7 +282,7 @@ public void testValidateWillFailWhenStoredScriptIsNotEnabled() {
             final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build();
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             final ClusterChangedEvent clusterChangedEvent = mock(ClusterChangedEvent.class);
    @@ -309,7 +309,7 @@ public void testValidateWillFailWhenStoredScriptIsNotEnabled() {
         public void testValidateWillFailWhenStoredScriptIsNotFound() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             final ClusterChangedEvent clusterChangedEvent = mock(ClusterChangedEvent.class);
    
  • x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/support/SecurityQueryTemplateEvaluatorTests.java+2 1 modified
    @@ -8,6 +8,7 @@
     package org.elasticsearch.xpack.core.security.authz.support;
     
     import org.elasticsearch.common.Strings;
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.index.query.TermQueryBuilder;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.ScriptService;
    @@ -101,7 +102,7 @@ public void testDocLevelSecurityTemplateWithOpenIdConnectStyleMetadata() throws
                 true
             );
     
    -        final MustacheScriptEngine mustache = new MustacheScriptEngine();
    +        final MustacheScriptEngine mustache = new MustacheScriptEngine(Settings.EMPTY);
     
             when(scriptService.compile(any(Script.class), eq(TemplateScript.CONTEXT))).thenAnswer(inv -> {
                 assertThat(inv.getArguments(), arrayWithSize(2));
    
  • x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/sp/WildcardServiceProviderResolverTests.java+1 1 modified
    @@ -94,7 +94,7 @@ public void setUpResolver() {
             final Settings settings = Settings.EMPTY;
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             final ServiceProviderDefaults samlDefaults = new ServiceProviderDefaults(
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java+1 1 modified
    @@ -401,7 +401,7 @@ public void testRealmWithTemplatedRoleMapping() throws Exception {
     
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             NativeRoleMappingStore roleMapper = new NativeRoleMappingStore(settings, mockClient, mockSecurityIndex, scriptService) {
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java+1 1 modified
    @@ -462,7 +462,7 @@ public void testLdapRealmWithTemplatedRoleMapping() throws Exception {
     
             final ScriptService scriptService = new ScriptService(
                 defaultGlobalSettings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             NativeRoleMappingStore roleMapper = new NativeRoleMappingStore(
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java+1 1 modified
    @@ -119,7 +119,7 @@ public void testResolveRoles() throws Exception {
             SecurityIndexManager securityIndex = mock(SecurityIndexManager.class);
             ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS
             );
             when(securityIndex.isAvailable()).thenReturn(true);
    
  • x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/support/WatcherTemplateTests.java+1 1 modified
    @@ -38,7 +38,7 @@ public class WatcherTemplateTests extends ESTestCase {
     
         @Before
         public void init() throws Exception {
    -        MustacheScriptEngine engine = new MustacheScriptEngine();
    +        MustacheScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             Map<String, ScriptEngine> engines = Collections.singletonMap(engine.getType(), engine);
             Map<String, ScriptContext<?>> contexts = Collections.singletonMap(
                 Watcher.SCRIPT_TEMPLATE_CONTEXT.name,
    
cbde7f456d7c

Add a size limit to outputs from mustache (#114002)

https://github.com/elastic/elasticsearchSimon CooperOct 8, 2024via ghsa
19 files changed · +181 24
  • docs/changelog/114002.yaml+5 0 added
    @@ -0,0 +1,5 @@
    +pr: 114002
    +summary: Add a `mustache.max_output_size_bytes` setting to limit the length of results from mustache scripts
    +area: Infra/Scripting
    +type: enhancement
    +issues: []
    
  • libs/logstash-bridge/src/main/java/org/elasticsearch/logstashbridge/script/ScriptServiceBridge.java+1 1 modified
    @@ -53,7 +53,7 @@ private static ScriptService getScriptService(final Settings settings, final Lon
                 PainlessScriptEngine.NAME,
                 new PainlessScriptEngine(settings, scriptContexts),
                 MustacheScriptEngine.NAME,
    -            new MustacheScriptEngine()
    +            new MustacheScriptEngine(settings)
             );
             return new ScriptService(settings, scriptEngines, ScriptModule.CORE_CONTEXTS, timeProvider);
         }
    
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MustachePlugin.java+1 1 modified
    @@ -44,7 +44,7 @@ public class MustachePlugin extends Plugin implements ScriptPlugin, ActionPlugin
     
         @Override
         public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
    -        return new MustacheScriptEngine();
    +        return new MustacheScriptEngine(settings);
         }
     
         @Override
    
  • modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngine.java+26 1 modified
    @@ -14,6 +14,13 @@
     
     import org.apache.logging.log4j.LogManager;
     import org.apache.logging.log4j.Logger;
    +import org.elasticsearch.ElasticsearchParseException;
    +import org.elasticsearch.ExceptionsHelper;
    +import org.elasticsearch.common.settings.Setting;
    +import org.elasticsearch.common.settings.Settings;
    +import org.elasticsearch.common.text.SizeLimitingStringWriter;
    +import org.elasticsearch.common.unit.ByteSizeValue;
    +import org.elasticsearch.common.unit.MemorySizeValue;
     import org.elasticsearch.script.GeneralScriptException;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.ScriptContext;
    @@ -47,6 +54,19 @@ public final class MustacheScriptEngine implements ScriptEngine {
     
         public static final String NAME = "mustache";
     
    +    public static final Setting<ByteSizeValue> MUSTACHE_RESULT_SIZE_LIMIT = new Setting<>(
    +        "mustache.max_output_size_bytes",
    +        s -> "1mb",
    +        s -> MemorySizeValue.parseBytesSizeValueOrHeapRatio(s, "mustache.max_output_size_bytes"),
    +        Setting.Property.NodeScope
    +    );
    +
    +    private final int sizeLimit;
    +
    +    public MustacheScriptEngine(Settings settings) {
    +        sizeLimit = (int) MUSTACHE_RESULT_SIZE_LIMIT.get(settings).getBytes();
    +    }
    +
         /**
          * Compile a template string to (in this case) a Mustache object than can
          * later be re-used for execution to fill in missing parameter values.
    @@ -118,10 +138,15 @@ private class MustacheExecutableScript extends TemplateScript {
     
             @Override
             public String execute() {
    -            final StringWriter writer = new StringWriter();
    +            StringWriter writer = new SizeLimitingStringWriter(sizeLimit);
                 try {
                     template.execute(writer, params);
                 } catch (Exception e) {
    +                // size limit exception can appear at several places in the causal list depending on script & context
    +                if (ExceptionsHelper.unwrap(e, SizeLimitingStringWriter.SizeLimitExceededException.class) != null) {
    +                    // don't log, client problem
    +                    throw new ElasticsearchParseException("Mustache script result size limit exceeded", e);
    +                }
                     if (shouldLogException(e)) {
                         logger.error(() -> format("Error running %s", template), e);
                     }
    
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/CustomMustacheFactoryTests.java+4 3 modified
    @@ -9,6 +9,7 @@
     
     package org.elasticsearch.script.mustache;
     
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.ScriptEngine;
     import org.elasticsearch.script.TemplateScript;
    @@ -65,7 +66,7 @@ public void testCreateEncoder() {
         }
     
         public void testJsonEscapeEncoder() {
    -        final ScriptEngine engine = new MustacheScriptEngine();
    +        final ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             final Map<String, String> params = randomBoolean() ? Map.of(Script.CONTENT_TYPE_OPTION, JSON_MEDIA_TYPE) : Map.of();
     
             TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);
    @@ -75,7 +76,7 @@ public void testJsonEscapeEncoder() {
         }
     
         public void testDefaultEncoder() {
    -        final ScriptEngine engine = new MustacheScriptEngine();
    +        final ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             final Map<String, String> params = Map.of(Script.CONTENT_TYPE_OPTION, PLAIN_TEXT_MEDIA_TYPE);
     
             TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);
    @@ -85,7 +86,7 @@ public void testDefaultEncoder() {
         }
     
         public void testUrlEncoder() {
    -        final ScriptEngine engine = new MustacheScriptEngine();
    +        final ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             final Map<String, String> params = Map.of(Script.CONTENT_TYPE_OPTION, X_WWW_FORM_URLENCODED_MEDIA_TYPE);
     
             TemplateScript.Factory compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", TemplateScript.CONTEXT, params);
    
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MustacheScriptEngineTests.java+27 1 modified
    @@ -8,8 +8,13 @@
      */
     package org.elasticsearch.script.mustache;
     
    +import com.github.mustachejava.MustacheException;
     import com.github.mustachejava.MustacheFactory;
     
    +import org.elasticsearch.ElasticsearchParseException;
    +import org.elasticsearch.common.Strings;
    +import org.elasticsearch.common.settings.Settings;
    +import org.elasticsearch.common.text.SizeLimitingStringWriter;
     import org.elasticsearch.script.GeneralScriptException;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.TemplateScript;
    @@ -24,6 +29,9 @@
     import java.util.List;
     import java.util.Map;
     
    +import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
    +import static org.hamcrest.Matchers.allOf;
    +import static org.hamcrest.Matchers.endsWith;
     import static org.hamcrest.Matchers.equalTo;
     import static org.hamcrest.Matchers.instanceOf;
     import static org.hamcrest.Matchers.startsWith;
    @@ -37,7 +45,7 @@ public class MustacheScriptEngineTests extends ESTestCase {
     
         @Before
         public void setup() {
    -        qe = new MustacheScriptEngine();
    +        qe = new MustacheScriptEngine(Settings.builder().put(MustacheScriptEngine.MUSTACHE_RESULT_SIZE_LIMIT.getKey(), "1kb").build());
             factory = CustomMustacheFactory.builder().build();
         }
     
    @@ -402,6 +410,24 @@ public void testEscapeJson() throws IOException {
             }
         }
     
    +    public void testResultSizeLimit() throws IOException {
    +        String vals = "\"" + "{{val}}".repeat(200) + "\"";
    +        String params = "\"val\":\"aaaaaaaaaa\"";
    +        XContentParser parser = createParser(JsonXContent.jsonXContent, Strings.format("{\"source\":%s,\"params\":{%s}}", vals, params));
    +        Script script = Script.parse(parser);
    +        var compiled = qe.compile(null, script.getIdOrCode(), TemplateScript.CONTEXT, Map.of());
    +        TemplateScript templateScript = compiled.newInstance(script.getParams());
    +        var ex = expectThrows(ElasticsearchParseException.class, templateScript::execute);
    +        assertThat(ex.getCause(), instanceOf(MustacheException.class));
    +        assertThat(
    +            ex.getCause().getCause(),
    +            allOf(
    +                instanceOf(SizeLimitingStringWriter.SizeLimitExceededException.class),
    +                transformedMatch(Throwable::getMessage, endsWith("has exceeded the size limit [1024]"))
    +            )
    +        );
    +    }
    +
         private String getChars() {
             String string = randomRealisticUnicodeOfCodepointLengthBetween(0, 10);
             for (int i = 0; i < string.length(); i++) {
    
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MustacheTests.java+2 1 modified
    @@ -9,6 +9,7 @@
     package org.elasticsearch.script.mustache;
     
     import org.elasticsearch.common.bytes.BytesReference;
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.common.xcontent.XContentHelper;
     import org.elasticsearch.core.Strings;
     import org.elasticsearch.script.ScriptEngine;
    @@ -39,7 +40,7 @@
     
     public class MustacheTests extends ESTestCase {
     
    -    private ScriptEngine engine = new MustacheScriptEngine();
    +    private ScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
     
         public void testBasics() {
             String template = """
    
  • qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/java/org/elasticsearch/ingest/AbstractScriptTestCase.java+1 1 modified
    @@ -31,7 +31,7 @@ public abstract class AbstractScriptTestCase extends ESTestCase {
     
         @Before
         public void init() throws Exception {
    -        MustacheScriptEngine engine = new MustacheScriptEngine();
    +        MustacheScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             Map<String, ScriptEngine> engines = Collections.singletonMap(engine.getType(), engine);
             scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS, () -> 1L);
         }
    
  • server/src/main/java/org/elasticsearch/common/text/SizeLimitingStringWriter.java+69 0 added
    @@ -0,0 +1,69 @@
    +/*
    + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
    + * License v3.0 only", or the "Server Side Public License, v 1".
    + */
    +
    +package org.elasticsearch.common.text;
    +
    +import org.elasticsearch.common.Strings;
    +
    +import java.io.StringWriter;
    +
    +/**
    + * A {@link StringWriter} that throws an exception if the string exceeds a specified size.
    + */
    +public class SizeLimitingStringWriter extends StringWriter {
    +
    +    public static class SizeLimitExceededException extends IllegalStateException {
    +        public SizeLimitExceededException(String message) {
    +            super(message);
    +        }
    +    }
    +
    +    private final int sizeLimit;
    +
    +    public SizeLimitingStringWriter(int sizeLimit) {
    +        this.sizeLimit = sizeLimit;
    +    }
    +
    +    private void checkSizeLimit(int additionalChars) {
    +        int bufLen = getBuffer().length();
    +        if (bufLen + additionalChars > sizeLimit) {
    +            throw new SizeLimitExceededException(
    +                Strings.format("String [%s...] has exceeded the size limit [%s]", getBuffer().substring(0, Math.min(bufLen, 20)), sizeLimit)
    +            );
    +        }
    +    }
    +
    +    @Override
    +    public void write(int c) {
    +        checkSizeLimit(1);
    +        super.write(c);
    +    }
    +
    +    // write(char[]) delegates to write(char[], int, int)
    +
    +    @Override
    +    public void write(char[] cbuf, int off, int len) {
    +        checkSizeLimit(len);
    +        super.write(cbuf, off, len);
    +    }
    +
    +    @Override
    +    public void write(String str) {
    +        checkSizeLimit(str.length());
    +        super.write(str);
    +    }
    +
    +    @Override
    +    public void write(String str, int off, int len) {
    +        checkSizeLimit(len);
    +        super.write(str, off, len);
    +    }
    +
    +    // append(...) delegates to write(...) methods
    +}
    
  • server/src/test/java/org/elasticsearch/common/text/SizeLimitingStringWriterTests.java+29 0 added
    @@ -0,0 +1,29 @@
    +/*
    + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
    + * License v3.0 only", or the "Server Side Public License, v 1".
    + */
    +
    +package org.elasticsearch.common.text;
    +
    +import org.elasticsearch.test.ESTestCase;
    +
    +public class SizeLimitingStringWriterTests extends ESTestCase {
    +    public void testSizeIsLimited() {
    +        SizeLimitingStringWriter writer = new SizeLimitingStringWriter(10);
    +
    +        writer.write("a".repeat(10));
    +
    +        // test all the methods
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write('a'));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write("a"));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write(new char[1]));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.write(new char[1], 0, 1));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.append('a'));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.append("a"));
    +        expectThrows(SizeLimitingStringWriter.SizeLimitExceededException.class, () -> writer.append("a", 0, 1));
    +    }
    +}
    
  • x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java+7 7 modified
    @@ -89,7 +89,7 @@ public void testEqualsAndHashCode() throws Exception {
         public void testEvaluateRoles() throws Exception {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    @@ -145,7 +145,7 @@ public void tryEquals(TemplateRoleName original) {
         public void testValidate() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    @@ -173,7 +173,7 @@ public void testValidate() {
         public void testValidateWillPassWithEmptyContext() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    @@ -204,7 +204,7 @@ public void testValidateWillPassWithEmptyContext() {
         public void testValidateWillFailForSyntaxError() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    @@ -268,7 +268,7 @@ public void testValidationWillFailWhenInlineScriptIsNotEnabled() {
             final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build();
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    @@ -285,7 +285,7 @@ public void testValidateWillFailWhenStoredScriptIsNotEnabled() {
             final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build();
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    @@ -314,7 +314,7 @@ public void testValidateWillFailWhenStoredScriptIsNotEnabled() {
         public void testValidateWillFailWhenStoredScriptIsNotFound() {
             final ScriptService scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    
  • x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/support/SecurityQueryTemplateEvaluatorTests.java+2 1 modified
    @@ -8,6 +8,7 @@
     package org.elasticsearch.xpack.core.security.authz.support;
     
     import org.elasticsearch.common.Strings;
    +import org.elasticsearch.common.settings.Settings;
     import org.elasticsearch.index.query.TermQueryBuilder;
     import org.elasticsearch.script.Script;
     import org.elasticsearch.script.ScriptService;
    @@ -94,7 +95,7 @@ public void testDocLevelSecurityTemplateWithOpenIdConnectStyleMetadata() throws
                 true
             );
     
    -        final MustacheScriptEngine mustache = new MustacheScriptEngine();
    +        final MustacheScriptEngine mustache = new MustacheScriptEngine(Settings.EMPTY);
     
             when(scriptService.compile(any(Script.class), eq(TemplateScript.CONTEXT))).thenAnswer(inv -> {
                 assertThat(inv.getArguments(), arrayWithSize(2));
    
  • x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/sp/WildcardServiceProviderResolverTests.java+1 1 modified
    @@ -95,7 +95,7 @@ public void setUpResolver() {
             final Settings settings = Settings.EMPTY;
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/ltr/LearningToRankServiceTests.java+1 1 modified
    @@ -241,7 +241,7 @@ private LearningToRankService getTestLearningToRankService(TrainedModelProvider
         }
     
         private ScriptService getTestScriptService() {
    -        ScriptEngine scriptEngine = new MustacheScriptEngine();
    +        ScriptEngine scriptEngine = new MustacheScriptEngine(Settings.EMPTY);
             return new ScriptService(Settings.EMPTY, Map.of(DEFAULT_TEMPLATE_LANG, scriptEngine), ScriptModule.CORE_CONTEXTS, () -> 1L);
         }
     }
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java+1 1 modified
    @@ -434,7 +434,7 @@ public void testRealmWithTemplatedRoleMapping() throws Exception {
     
             final ScriptService scriptService = new ScriptService(
                 settings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java+1 1 modified
    @@ -533,7 +533,7 @@ public void testLdapRealmWithTemplatedRoleMapping() throws Exception {
     
             final ScriptService scriptService = new ScriptService(
                 defaultGlobalSettings,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ClusterStateRoleMapperTests.java+1 1 modified
    @@ -51,7 +51,7 @@ public class ClusterStateRoleMapperTests extends ESTestCase {
         public void setup() {
             scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java+1 1 modified
    @@ -87,7 +87,7 @@ public class NativeRoleMappingStoreTests extends ESTestCase {
         public void setup() {
             scriptService = new ScriptService(
                 Settings.EMPTY,
    -            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()),
    +            Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine(Settings.EMPTY)),
                 ScriptModule.CORE_CONTEXTS,
                 () -> 1L
             );
    
  • x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/support/WatcherTemplateTests.java+1 1 modified
    @@ -37,7 +37,7 @@ public class WatcherTemplateTests extends ESTestCase {
     
         @Before
         public void init() throws Exception {
    -        MustacheScriptEngine engine = new MustacheScriptEngine();
    +        MustacheScriptEngine engine = new MustacheScriptEngine(Settings.EMPTY);
             Map<String, ScriptEngine> engines = Collections.singletonMap(engine.getType(), engine);
             Map<String, ScriptContext<?>> contexts = Collections.singletonMap(
                 Watcher.SCRIPT_TEMPLATE_CONTEXT.name,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.