diff --git a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java index 37a071a7cf32a..a7edab721ef72 100644 --- a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java +++ b/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, diff --git a/core/src/main/java/org/elasticsearch/index/mapper/MapperService.java b/core/src/main/java/org/elasticsearch/index/mapper/MapperService.java index f46586ccb1aaa..72fbc8ef6485d 100755 --- a/core/src/main/java/org/elasticsearch/index/mapper/MapperService.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/MapperService.java @@ -86,6 +86,8 @@ public enum MergeReason { Setting.longSetting("index.mapping.nested_fields.limit", 50L, 0, Property.Dynamic, Property.IndexScope); public static final Setting INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING = Setting.longSetting("index.mapping.total_fields.limit", 1000L, 0, Property.Dynamic, Property.IndexScope); + public static final Setting 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 INDEX_MAPPER_DYNAMIC_SETTING = Setting.boolSetting("index.mapper.dynamic", INDEX_MAPPER_DYNAMIC_DEFAULT, Property.IndexScope); @@ -292,6 +294,7 @@ private synchronized DocumentMapper merge(DocumentMapper mapper, MergeReason rea // this check will be skipped. checkNestedFieldsLimit(fullPathObjectMappers); checkTotalFieldsLimit(objectMappers.size() + fieldMappers.size()); + checkDepthLimit(fullPathObjectMappers.keySet()); } Set parentTypes = this.parentTypes; @@ -418,6 +421,27 @@ private void checkTotalFieldsLimit(long totalMappers) { } } + private void checkDepthLimit(Collection 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)) { diff --git a/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java b/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java index bf7f8e0b5ccfe..a0fc90fe08685 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java @@ -161,4 +161,31 @@ public void testTotalFieldsExceedsLimit() throws Throwable { 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")); + } } diff --git a/docs/reference/mapping/dynamic/field-mapping.asciidoc b/docs/reference/mapping/dynamic/field-mapping.asciidoc index 238052a9739d8..625bb401018eb 100644 --- a/docs/reference/mapping/dynamic/field-mapping.asciidoc +++ b/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 <>. -[[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