Skip to content

Commit

Permalink
Add a soft limit on the mapping depth. #17400
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jpountz committed Mar 30, 2016
1 parent 068c788 commit fc47007
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public enum MergeReason {
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);
Expand Down Expand Up @@ -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<String> parentTypes = this.parentTypes;
Expand Down Expand Up @@ -418,6 +421,27 @@ private void checkTotalFieldsLimit(long totalMappers) {
}
}

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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
18 changes: 13 additions & 5 deletions docs/reference/mapping/dynamic/field-mapping.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit fc47007

Please sign in to comment.