Browse Source

Add a soft limit on the mapping depth. #17400

This commit adds the new `index.mapping.depth.limit` setting which controls the
maximum mapping depth that is allowed. It has a default value of 20.
Adrien Grand 9 years ago
parent
commit
fc47007e17

+ 1 - 0
core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java

@@ -129,6 +129,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
         MapperService.INDEX_MAPPER_DYNAMIC_SETTING,
         MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING,
         MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING,
+        MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING,
         BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING,
         IndexModule.INDEX_STORE_TYPE_SETTING,
         IndexModule.INDEX_QUERY_CACHE_TYPE_SETTING,

+ 24 - 0
core/src/main/java/org/elasticsearch/index/mapper/MapperService.java

@@ -86,6 +86,8 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
         Setting.longSetting("index.mapping.nested_fields.limit", 50L, 0, Property.Dynamic, Property.IndexScope);
     public static final Setting<Long> INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING =
         Setting.longSetting("index.mapping.total_fields.limit", 1000L, 0, Property.Dynamic, Property.IndexScope);
+    public static final Setting<Long> INDEX_MAPPING_DEPTH_LIMIT_SETTING =
+            Setting.longSetting("index.mapping.depth.limit", 20L, 1, Property.Dynamic, Property.IndexScope);
     public static final boolean INDEX_MAPPER_DYNAMIC_DEFAULT = true;
     public static final Setting<Boolean> INDEX_MAPPER_DYNAMIC_SETTING =
         Setting.boolSetting("index.mapper.dynamic", INDEX_MAPPER_DYNAMIC_DEFAULT, Property.IndexScope);
@@ -292,6 +294,7 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
             // this check will be skipped.
             checkNestedFieldsLimit(fullPathObjectMappers);
             checkTotalFieldsLimit(objectMappers.size() + fieldMappers.size());
+            checkDepthLimit(fullPathObjectMappers.keySet());
         }
 
         Set<String> parentTypes = this.parentTypes;
@@ -418,6 +421,27 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
         }
     }
 
+    private void checkDepthLimit(Collection<String> objectPaths) {
+        final long maxDepth = indexSettings.getValue(INDEX_MAPPING_DEPTH_LIMIT_SETTING);
+        for (String objectPath : objectPaths) {
+            checkDepthLimit(objectPath, maxDepth);
+        }
+    }
+
+    private void checkDepthLimit(String objectPath, long maxDepth) {
+        int numDots = 0;
+        for (int i = 0; i < objectPath.length(); ++i) {
+            if (objectPath.charAt(i) == '.') {
+                numDots += 1;
+            }
+        }
+        final int depth = numDots + 2;
+        if (depth > maxDepth) {
+            throw new IllegalArgumentException("Limit of mapping depth [" + maxDepth + "] in index [" + index().getName()
+                    + "] has been exceeded due to object field [" + objectPath + "]");
+        }
+    }
+
     public DocumentMapper parse(String mappingType, CompressedXContent mappingSource, boolean applyDefault) throws MapperParsingException {
         String defaultMappingSource;
         if (PercolatorFieldMapper.TYPE_NAME.equals(mappingType)) {

+ 27 - 0
core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java

@@ -161,4 +161,31 @@ public class MapperServiceTests extends ESSingleNodeTestCase {
             assertThat(e.getMessage(), containsString("Limit of total fields [1] in index [test2] has been exceeded"));
         }
     }
+
+    public void testMappingDepthExceedsLimit() throws Throwable {
+        CompressedXContent simpleMapping = new CompressedXContent(XContentFactory.jsonBuilder().startObject()
+                .startObject("properties")
+                    .startObject("field")
+                        .field("type", "text")
+                    .endObject()
+                .endObject().endObject().bytes());
+        IndexService indexService1 = createIndex("test1", Settings.builder().put(MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING.getKey(), 1).build());
+        // no exception
+        indexService1.mapperService().merge("type", simpleMapping, MergeReason.MAPPING_UPDATE, false);
+
+        CompressedXContent objectMapping = new CompressedXContent(XContentFactory.jsonBuilder().startObject()
+                .startObject("properties")
+                    .startObject("object1")
+                        .field("type", "object")
+                    .endObject()
+                .endObject().endObject().bytes());
+
+        IndexService indexService2 = createIndex("test2");
+        // no exception
+        indexService2.mapperService().merge("type", objectMapping, MergeReason.MAPPING_UPDATE, false);
+
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
+                () -> indexService1.mapperService().merge("type2", objectMapping, MergeReason.MAPPING_UPDATE, false));
+        assertThat(e.getMessage(), containsString("Limit of mapping depth [1] in index [test1] has been exceeded"));
+    }
 }

+ 13 - 5
docs/reference/mapping/dynamic/field-mapping.asciidoc

@@ -30,11 +30,19 @@ detected.  All other datatypes must be mapped explicitly.
 Besides the options listed below, dynamic field mapping rules can be further
 customised with <<dynamic-templates,`dynamic_templates`>>.
 
-[[total-fields-limit]]
-==== Total fields limit
-
-To avoid mapping explosion, Index has a default limit of 1000 total number of fields.
-The default setting can be updated with `index.mapping.total_fields.limit`.
+[[mapping-limit-settings]]
+==== Settings to prevent mappings explosion
+
+Two settings allow to control mapping explosion, in order to prevent adversary
+documents to create huge mappings through dynamic mappings for instance:
+
+`index.mapping.total_fields.limit`::
+    The maximum number of fields in an index. The default value is `1000`.
+`index.mapping.depth.limit`::
+    The maximum depth for a field, which is measured as the number of nested
+    objects. For instance, if all fields are defined at the root object level,
+    then the depth is `1`. If there is one object mapping, then the depth is
+    `2`, etc. The default is `20`.
 
 [[date-detection]]
 ==== Date detection