/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2026 3A Systems, LLC. */ package org.opends.server.controls; import static org.opends.server.protocols.internal.InternalClientConnection.*; import static org.opends.server.protocols.internal.Requests.*; import static org.testng.Assert.*; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.SearchScope; import org.opends.server.TestCaseUtils; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.protocols.internal.SearchRequest; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** * Regression test for GHSA-q4wx-wj4j-4657 / OPENDJ-003 — unbounded array * allocation via VLV beforeCount/afterCount/offset (CWE-789 / CWE-770 / CWE-190). *
* A VLV-by-offset request carries attacker-controlled before/after counts. The * server computed {@code count = 1 + beforeCount + afterCount} and allocated * {@code new long[count]} without clamping it to the actual list size. With * {@code afterCount = Integer.MAX_VALUE} the sum overflowed to a negative int, * yielding a {@code NegativeArraySizeException} (or, with large non-overflowing * values, a multi-gigabyte allocation / {@code OutOfMemoryError}) from a single * search request. The fix computes the count with long arithmetic and clamps it * to the number of entries actually available, so the oversized window is now * bounded instead of faulting the search handler. *
* Note: this exercises the default in-memory sort path * ({@code EntryContainer.sortByOffset}), which requires no VLV index — the same * unclamped allocation also existed in {@code VLVIndex.readRange}. */ @SuppressWarnings("javadoc") public class VLVOffsetAllocationDoSTest extends ControlsTestCase { @BeforeClass public void startServer() throws Exception { TestCaseUtils.startServer(); } private void populateDB() throws Exception { TestCaseUtils.clearBackend("userRoot", "dc=example,dc=com"); TestCaseUtils.addEntries( "dn: uid=albert.zimmerman,dc=example,dc=com", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: albert.zimmerman", "givenName: Albert", "sn: Zimmerman", "cn: Albert Zimmerman", "", "dn: uid=aaron.zimmerman,dc=example,dc=com", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: aaron.zimmerman", "givenName: Aaron", "sn: Zimmerman", "cn: Aaron Zimmerman", "", "dn: uid=mary.jones,dc=example,dc=com", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: mary.jones", "givenName: Mary", "sn: Jones", "cn: Mary Jones"); } /** * A single VLV-by-offset search with {@code afterCount = Integer.MAX_VALUE} * (which previously overflowed {@code 1 + beforeCount + afterCount} into a * negative array size) must now be clamped to the list size and return the * same bounded page as a normal request — not fault the search handler with a * {@code NegativeArraySizeException} / {@code OTHER}. */ @Test public void offsetAllocationOverflowIsClamped() throws Exception { populateDB(); // Baseline: a normal, bounded window. SearchRequest ok = newSearchRequest("dc=example,dc=com", SearchScope.WHOLE_SUBTREE, "(objectClass=person)") .addControl(new ServerSideSortRequestControl("givenName")) .addControl(new VLVRequestControl(0, 3, 1, 0)); InternalSearchOperation okOp = getRootConnection().processSearch(ok); assertEquals(okOp.getResultCode(), ResultCode.SUCCESS); // Oversized window: afterCount = Integer.MAX_VALUE. Before the fix this // overflowed to new long[-2147483648]; a larger non-overflowing count would // instead force a multi-gigabyte allocation (OutOfMemoryError). SearchRequest bad = newSearchRequest("dc=example,dc=com", SearchScope.WHOLE_SUBTREE, "(objectClass=person)") .addControl(new ServerSideSortRequestControl("givenName")) .addControl(new VLVRequestControl(0, Integer.MAX_VALUE, 1, 0)); InternalSearchOperation badOp = getRootConnection().processSearch(bad); assertEquals(badOp.getResultCode(), ResultCode.SUCCESS, "oversized VLV afterCount must be clamped to the list size, not fault the search handler " + "(GHSA-q4wx-wj4j-4657): " + badOp.getErrorMessage()); // The page is bounded by the number of entries that actually exist (offset 1 // onwards), i.e. the same result as the bounded baseline request. assertEquals(badOp.getSearchEntries().size(), okOp.getSearchEntries().size()); } }