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.
This commit is contained in:
Adrien Grand 2016-03-30 11:40:13 +02:00
parent 068c788ec8
commit fc47007e17
4 changed files with 64 additions and 4 deletions

View File

@ -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,

View File

@ -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)) {

View File

@ -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"));
}
}

View File

@ -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
[[mapping-limit-settings]]
==== Settings to prevent mappings explosion
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`.
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