Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public class StringUtils {

static final int WILD_COMPARE_NO_MATCH = -1;

/**
* Maximum absolute {@link BigDecimal#scale()} accepted by {@link
* #consistentToString(BigDecimal)} before it falls back to scientific notation.
*
* <p>{@link BigDecimal#toPlainString()} expands the value to a non-scientific decimal string
* whose length grows with {@code |scale|}. A value such as {@code 1e1000000000} would therefore
* materialize a ~1GB {@link String}, causing {@link OutOfMemoryError} / denial of service on the
* client. Any scale beyond this bound is rejected for expansion and the caller gets the scientific
* form instead, which is a few dozen characters at most.
*/
static final int MAX_PLAIN_STRING_SCALE = 10_000;

static {
for (int i = -128; i <= 127; i++) {
allBytes[i - -128] = (byte) i;
Expand All @@ -63,9 +75,16 @@ public static String consistentToString(BigDecimal decimal) {
if (decimal == null) {
return null;
}
// Guard against CVE-style DoS: BigDecimal values constructed from extreme scientific
// notation (e.g. "1e1000000000") have a huge |scale| and would otherwise cause
// toPlainString() to allocate a multi-GB String and OOM the JVM. Fall back to the
// scientific form, which is always short.
if (decimal.scale() > MAX_PLAIN_STRING_SCALE || decimal.scale() < -MAX_PLAIN_STRING_SCALE) {
return decimal.toString();
}
if (toPlainStringMethod != null) {
try {
return (String) toPlainStringMethod.invoke(decimal, null);
return (String) toPlainStringMethod.invoke(decimal, (Object[]) null);
} catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.warn("consistent to String Error:", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.iotdb.jdbc;

import org.junit.Test;

import java.math.BigDecimal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class StringUtilsTest {

@Test
public void consistentToStringHandlesNull() {
assertNull(StringUtils.consistentToString(null));
}

@Test
public void consistentToStringExpandsNormalDecimal() {
assertEquals("123.45", StringUtils.consistentToString(new BigDecimal("123.45")));
// Plain-string form, not scientific notation.
assertEquals("100", StringUtils.consistentToString(new BigDecimal("1E+2")));
}

@Test
public void consistentToStringExpandsUpToLimit() {
// scale right at the boundary must still be expanded to plain form.
BigDecimal atLimit = new BigDecimal("1E+" + StringUtils.MAX_PLAIN_STRING_SCALE);
String out = StringUtils.consistentToString(atLimit);
assertEquals(StringUtils.MAX_PLAIN_STRING_SCALE + 1, out.length());
assertTrue(out.startsWith("1"));
}

/**
* Regression test for the BigDecimal DoS: {@code new BigDecimal("1e1000000000")} used to cause
* {@link BigDecimal#toPlainString()} to allocate a ~1GB string and OOM the JVM. The guard must
* refuse to expand such values and return the compact scientific form instead.
*/
@Test
public void consistentToStringRefusesToExpandHugeScale() {
BigDecimal malicious = new BigDecimal("1e1000000000");
String out = StringUtils.consistentToString(malicious);
// Must be short (scientific notation), never the ~1e9-char plain form.
assertTrue(
"expected scientific form, got length " + out.length(),
out.length() < 64);
assertTrue(out.contains("E") || out.contains("e"));
}

@Test
public void consistentToStringRefusesToExpandHugePositiveScale() {
// Very large positive scale -> also produces a long plain string; must be refused.
BigDecimal malicious = new BigDecimal("1e-1000000000");
String out = StringUtils.consistentToString(malicious);
assertTrue(
"expected scientific form, got length " + out.length(),
out.length() < 64);
}
}
Loading