Skip to content

Commit 6bd4faa

Browse files
committed
fix(API): add whitelist rate limiter adapter
1 parent 4b5663c commit 6bd4faa

2 files changed

Lines changed: 151 additions & 3 deletions

File tree

framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import io.prometheus.client.Histogram;
55
import java.io.IOException;
66
import java.lang.reflect.Constructor;
7+
import java.util.Collections;
8+
import java.util.HashMap;
9+
import java.util.Map;
710
import javax.annotation.PostConstruct;
811
import javax.servlet.ServletException;
912
import javax.servlet.http.HttpServlet;
@@ -31,7 +34,19 @@
3134
@Slf4j
3235
public abstract class RateLimiterServlet extends HttpServlet {
3336
private static final String KEY_PREFIX_HTTP = "http_";
34-
private static final String ADAPTER_PREFIX = "org.tron.core.services.ratelimiter.adapter.";
37+
38+
// Whitelist of allowed rate limiter adapter class names.
39+
// Class.forName() is intentionally NOT used; only these pre-approved classes may be loaded.
40+
private static final Map<String, Class<? extends IRateLimiter>> ALLOWED_ADAPTERS;
41+
42+
static {
43+
Map<String, Class<? extends IRateLimiter>> m = new HashMap<>();
44+
m.put("GlobalPreemptibleAdapter", GlobalPreemptibleAdapter.class);
45+
m.put("QpsRateLimiterAdapter", QpsRateLimiterAdapter.class);
46+
m.put("IPQPSRateLimiterAdapter", IPQPSRateLimiterAdapter.class);
47+
m.put("DefaultBaseQqsAdapter", DefaultBaseQqsAdapter.class);
48+
ALLOWED_ADAPTERS = Collections.unmodifiableMap(m);
49+
}
3550

3651
@Autowired
3752
private RateLimiterContainer container;
@@ -49,8 +64,13 @@ private void addRateContainer() {
4964
try {
5065
cName = item.getStrategy();
5166
params = item.getParams();
52-
// add the specific rate limiter strategy of servlet.
53-
Class<?> c = Class.forName(ADAPTER_PREFIX + cName);
67+
// Whitelist check: only allow known adapter class names to prevent
68+
// arbitrary class loading (RCE) via a tampered config file.
69+
Class<? extends IRateLimiter> c = ALLOWED_ADAPTERS.get(cName);
70+
if (c == null) {
71+
throw new IllegalArgumentException(
72+
"Unknown rate limiter adapter (not in whitelist): " + cName);
73+
}
5474
Constructor constructor;
5575
if (c == GlobalPreemptibleAdapter.class || c == QpsRateLimiterAdapter.class
5676
|| c == IPQPSRateLimiterAdapter.class) {
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package org.tron.core.services.http;
2+
3+
import static org.junit.Assert.assertNotNull;
4+
import static org.junit.Assert.assertNull;
5+
import static org.junit.Assert.assertTrue;
6+
import static org.junit.Assert.assertFalse;
7+
8+
import java.lang.reflect.Field;
9+
import java.util.Map;
10+
import org.junit.BeforeClass;
11+
import org.junit.Test;
12+
import org.tron.core.services.ratelimiter.adapter.DefaultBaseQqsAdapter;
13+
import org.tron.core.services.ratelimiter.adapter.GlobalPreemptibleAdapter;
14+
import org.tron.core.services.ratelimiter.adapter.IPQPSRateLimiterAdapter;
15+
import org.tron.core.services.ratelimiter.adapter.IRateLimiter;
16+
import org.tron.core.services.ratelimiter.adapter.QpsRateLimiterAdapter;
17+
18+
/**
19+
* Security test: verifies that RateLimiterServlet uses a strict whitelist
20+
* instead of Class.forName(), preventing arbitrary class loading (RCE)
21+
* via a tampered config file.
22+
*/
23+
public class RateLimiterServletWhitelistTest {
24+
25+
private static Map<String, Class<? extends IRateLimiter>> allowedAdapters;
26+
27+
@SuppressWarnings("unchecked")
28+
@BeforeClass
29+
public static void loadWhitelist() throws Exception {
30+
Field f = RateLimiterServlet.class.getDeclaredField("ALLOWED_ADAPTERS");
31+
f.setAccessible(true);
32+
allowedAdapters = (Map<String, Class<? extends IRateLimiter>>) f.get(null);
33+
}
34+
35+
// --- whitelist completeness ---
36+
37+
@Test
38+
public void testWhitelistContainsAllLegitimateAdapters() {
39+
assertNotNull("GlobalPreemptibleAdapter must be whitelisted",
40+
allowedAdapters.get("GlobalPreemptibleAdapter"));
41+
assertNotNull("QpsRateLimiterAdapter must be whitelisted",
42+
allowedAdapters.get("QpsRateLimiterAdapter"));
43+
assertNotNull("IPQPSRateLimiterAdapter must be whitelisted",
44+
allowedAdapters.get("IPQPSRateLimiterAdapter"));
45+
assertNotNull("DefaultBaseQqsAdapter must be whitelisted",
46+
allowedAdapters.get("DefaultBaseQqsAdapter"));
47+
}
48+
49+
@Test
50+
public void testWhitelistMapsToCorrectClasses() {
51+
assertTrue(allowedAdapters.get("GlobalPreemptibleAdapter")
52+
.isAssignableFrom(GlobalPreemptibleAdapter.class));
53+
assertTrue(allowedAdapters.get("QpsRateLimiterAdapter")
54+
.isAssignableFrom(QpsRateLimiterAdapter.class));
55+
assertTrue(allowedAdapters.get("IPQPSRateLimiterAdapter")
56+
.isAssignableFrom(IPQPSRateLimiterAdapter.class));
57+
assertTrue(allowedAdapters.get("DefaultBaseQqsAdapter")
58+
.isAssignableFrom(DefaultBaseQqsAdapter.class));
59+
}
60+
61+
// --- security: malicious class names must be rejected ---
62+
63+
@Test
64+
public void testArbitraryClassNameIsRejected() {
65+
// Simulates a tampered config: attacker supplies a fully-qualified class name
66+
assertNull("Arbitrary class name must not be in whitelist",
67+
allowedAdapters.get("com.evil.MaliciousAdapter"));
68+
}
69+
70+
@Test
71+
public void testPathTraversalStyleNameIsRejected() {
72+
assertNull("Path-traversal style name must not be in whitelist",
73+
allowedAdapters.get("../../../../evil.Payload"));
74+
}
75+
76+
@Test
77+
public void testEmptyStrategyIsRejected() {
78+
assertNull("Empty strategy must not be in whitelist",
79+
allowedAdapters.get(""));
80+
}
81+
82+
@Test
83+
public void testNullStrategyIsRejected() {
84+
assertNull("Null strategy must not be in whitelist",
85+
allowedAdapters.get(null));
86+
}
87+
88+
@Test
89+
public void testRuntimeClassIsRejected() {
90+
// Attacker tries to load java.lang.Runtime to exec arbitrary commands
91+
assertNull("java.lang.Runtime must not be in whitelist",
92+
allowedAdapters.get("java.lang.Runtime"));
93+
}
94+
95+
@Test
96+
public void testProcessBuilderIsRejected() {
97+
assertNull("java.lang.ProcessBuilder must not be in whitelist",
98+
allowedAdapters.get("java.lang.ProcessBuilder"));
99+
}
100+
101+
// --- whitelist is immutable (cannot be tampered at runtime) ---
102+
103+
@Test(expected = UnsupportedOperationException.class)
104+
public void testWhitelistIsImmutable() {
105+
// The map must be unmodifiable to prevent runtime injection
106+
allowedAdapters.put("Injected", DefaultBaseQqsAdapter.class);
107+
}
108+
109+
// --- all whitelisted entries implement IRateLimiter ---
110+
111+
@Test
112+
public void testAllWhitelistedClassesImplementIRateLimiter() {
113+
for (Map.Entry<String, Class<? extends IRateLimiter>> entry : allowedAdapters.entrySet()) {
114+
assertTrue(
115+
entry.getKey() + " must implement IRateLimiter",
116+
IRateLimiter.class.isAssignableFrom(entry.getValue())
117+
);
118+
}
119+
}
120+
121+
// --- whitelist size is exactly what we expect (no accidental additions) ---
122+
123+
@Test
124+
public void testWhitelistSizeIsExact() {
125+
assertFalse("Whitelist must not be empty", allowedAdapters.isEmpty());
126+
assertTrue("Whitelist must contain exactly 4 adapters", allowedAdapters.size() == 4);
127+
}
128+
}

0 commit comments

Comments
 (0)