A corrupt compressed schema token takes the backend down at open, or hangs it there
CompressedSchema.decodeId() folds the bytes of a token with no length cap and returns id - 1,
and the two load entry points hand that id straight to the decode map with neither a sign check
nor an upper bound:
// CompressedSchema.java, decodeId()
int id = 0;
for (final byte b : idBytes)
{
id <<= 8;
id |= b & 0xFF;
}
return id - 1; // Subtract 1 to compensate for old behavior.
// CompressedSchema.java, loadAttributeToMaps() - loadObjectClassesToMaps()/updateObjectClassesMaps()
// has the same shape
mappings.adEncodeMap.put(ad, id);
if (id < mappings.adDecodeMap.size())
{
mappings.adDecodeMap.set(id, ad);
}
else
{
// Grow the decode array.
while (id > mappings.adDecodeMap.size())
{
mappings.adDecodeMap.add(null);
}
mappings.adDecodeMap.add(ad);
}
Both are reached from loadAttribute()/loadObjectClasses(), the SPI an implementation calls once
per record it read out of its storage - PersistentCompressedSchema.load() while cursoring the
compressed_attributes/compressed_objectclasses trees, DefaultCompressedSchema.load() while
reading config/compressedschema.dat. Neither validates the key it read.
Two keys are enough:
-
An all-zero key gives id == -1. The guard id < size() passes, and set(-1, ad) throws
IndexOutOfBoundsException. Nothing on the load path catches it as anything but a fatal error:
it leaves DefaultCompressedSchema.load() as throw new RuntimeException(e), and
PersistentCompressedSchema's constructor runs inside RootContainer.open(), so it surfaces as a
StorageRuntimeException. The backend does not open.
-
A key of 0x7FFFFFFF gives id == 2147483646, and the else branch pads the decode map up to
it one element at a time. The map is a CopyOnWriteArrayList, so every add copies the whole
backing array - two billion appends, quadratic, under exclusiveLock, and for the pluggable
backend inside RootContainer.open()'s write transaction. The backend never finishes opening and
nothing in the log says why.
A four-byte key is what the encoder itself writes past id 16777214, so neither value needs a hostile
store - a truncated or partially written record is enough.
Suggested fix
Validate in loadAttribute()/loadObjectClasses(), before anything reaches the maps:
- reject
id < 0;
- reject an id beyond a sane ceiling for a compressed schema (the number of records the caller is
loading is an upper bound the SPI could take, or a fixed cap well below Integer.MAX_VALUE);
- report the rejected token as the key the storage holds -
CompressedSchema.tokenInMessage()
already formats one that way for the decode path - and skip the record rather than fail the open,
so one unreadable definition leaves a gap the decode path already handles instead of taking the
whole backend down.
Notes
Pre-existing, and untouched by #894 - raised there while reviewing the sibling read path, which
#894 hardened with decodeMapGet(). decodeMapGet() covers the decode side (an entry carrying a
token with no definition is now reported as an unknown token); this is the load side, which still
has no bound at all.
A corrupt compressed schema token takes the backend down at open, or hangs it there
CompressedSchema.decodeId()folds the bytes of a token with no length cap and returnsid - 1,and the two load entry points hand that id straight to the decode map with neither a sign check
nor an upper bound:
Both are reached from
loadAttribute()/loadObjectClasses(), the SPI an implementation calls onceper record it read out of its storage -
PersistentCompressedSchema.load()while cursoring thecompressed_attributes/compressed_objectclassestrees,DefaultCompressedSchema.load()whilereading
config/compressedschema.dat. Neither validates the key it read.Two keys are enough:
An all-zero key gives
id == -1. The guardid < size()passes, andset(-1, ad)throwsIndexOutOfBoundsException. Nothing on the load path catches it as anything but a fatal error:it leaves
DefaultCompressedSchema.load()asthrow new RuntimeException(e), andPersistentCompressedSchema's constructor runs insideRootContainer.open(), so it surfaces as aStorageRuntimeException. The backend does not open.A key of
0x7FFFFFFFgivesid == 2147483646, and the else branch pads the decode map up toit one element at a time. The map is a
CopyOnWriteArrayList, so everyaddcopies the wholebacking array - two billion appends, quadratic, under
exclusiveLock, and for the pluggablebackend inside
RootContainer.open()'s write transaction. The backend never finishes opening andnothing in the log says why.
A four-byte key is what the encoder itself writes past id 16777214, so neither value needs a hostile
store - a truncated or partially written record is enough.
Suggested fix
Validate in
loadAttribute()/loadObjectClasses(), before anything reaches the maps:id < 0;loading is an upper bound the SPI could take, or a fixed cap well below
Integer.MAX_VALUE);CompressedSchema.tokenInMessage()already formats one that way for the decode path - and skip the record rather than fail the open,
so one unreadable definition leaves a gap the decode path already handles instead of taking the
whole backend down.
Notes
Pre-existing, and untouched by #894 - raised there while reviewing the sibling read path, which
#894 hardened with
decodeMapGet().decodeMapGet()covers the decode side (an entry carrying atoken with no definition is now reported as an unknown token); this is the load side, which still
has no bound at all.