This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git
The following commit(s) were added to refs/heads/master by this push:
new e60d2aff Disassembler and Class2HTML re-implement the unchecked
switch-table allocation (f009).
e60d2aff is described below
commit e60d2affd5f934b2c5c7410b7699be288adcea85
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 16:10:21 2026 -0400
Disassembler and Class2HTML re-implement the unchecked switch-table
allocation (f009).
---
src/changes/changes.xml | 1 +
.../java/org/apache/bcel/classfile/Utility.java | 13 ++++++++++++-
src/main/java/org/apache/bcel/util/CodeHTML.java | 14 +++++++++++++-
.../org/apache/bcel/classfile/UtilityTest.java | 22 ++++++++++++++++++++++
4 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d12360b7..7e1ad79e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -96,6 +96,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Pass 3b dataflow stores frames per instruction and clones ever-growing
execution chains (f006).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Cubic handlers × range × subroutines scan in Subroutines constructor
hangs JustIce Pass 3b (f007).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Subroutines.noRecursiveCalls enumerates exponentially many JSR call
paths (f008).</action>
+ <action type="fix" dev="ggregory" due-to="Gary
Gregory">Disassembler and Class2HTML re-implement the unchecked switch-table
allocation (f009).</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="nbauma109,
Gary Gregory">Add support for permitted subclasses #493.</action>
<action type="add" dev="ggregory" due-to="nbauma109,
Gary Gregory">Add RecordComponentInfo.getAttribute(byte tag)#494.</action>
diff --git a/src/main/java/org/apache/bcel/classfile/Utility.java
b/src/main/java/org/apache/bcel/classfile/Utility.java
index 7dddc70a..ddee58cf 100644
--- a/src/main/java/org/apache/bcel/classfile/Utility.java
+++ b/src/main/java/org/apache/bcel/classfile/Utility.java
@@ -343,8 +343,15 @@ public abstract class Utility {
high = bytes.readInt();
offset = bytes.getIndex() - 12 - noPadBytes - 1;
defaultOffset += offset;
+ // Each jump table entry is a 4 byte offset, so a well-formed
table cannot declare more entries than fit
+ // into the remaining byte code; checking before allocating keeps
a crafted low/high pair from forcing a
+ // huge allocation.
+ final long jumpTableLength = (long) high - low + 1;
+ if (jumpTableLength < 0 || jumpTableLength * 4 >
bytes.available()) {
+ throw new ClassFormatException("Invalid TABLESWITCH: low = " +
low + ", high = " + high + " but only " + bytes.available() + " bytes remain");
+ }
buf.append("\tdefault = ").append(defaultOffset).append(", low =
").append(low).append(", high = ").append(high).append("(");
- jumpTable = new int[high - low + 1];
+ jumpTable = new int[(int) jumpTableLength];
for (int i = 0; i < jumpTable.length; i++) {
jumpTable[i] = offset + bytes.readInt();
buf.append(jumpTable[i]);
@@ -360,6 +367,10 @@ public abstract class Utility {
case Const.LOOKUPSWITCH: {
npairs = bytes.readInt();
offset = bytes.getIndex() - 8 - noPadBytes - 1;
+ // Each match-offset pair is 8 bytes, see the TABLESWITCH check
above.
+ if (npairs < 0 || (long) npairs * 8 > bytes.available()) {
+ throw new ClassFormatException("Invalid LOOKUPSWITCH: npairs =
" + npairs + " but only " + bytes.available() + " bytes remain");
+ }
match = new int[npairs];
jumpTable = new int[npairs];
defaultOffset += offset;
diff --git a/src/main/java/org/apache/bcel/util/CodeHTML.java
b/src/main/java/org/apache/bcel/util/CodeHTML.java
index bb7b09b7..3b9d1b90 100644
--- a/src/main/java/org/apache/bcel/util/CodeHTML.java
+++ b/src/main/java/org/apache/bcel/util/CodeHTML.java
@@ -25,6 +25,7 @@ import java.util.BitSet;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
+import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.ConstantFieldref;
@@ -109,9 +110,16 @@ final class CodeHTML {
high = bytes.readInt();
offset = bytes.getIndex() - 12 - noPadBytes - 1;
defaultOffset += offset;
+ // Each jump table entry is a 4 byte offset, so a well-formed
table cannot declare more entries than fit
+ // into the remaining byte code; checking before allocating keeps
a crafted low/high pair from forcing a
+ // huge allocation.
+ final long jumpTableLength = (long) high - low + 1;
+ if (jumpTableLength < 0 || jumpTableLength * 4 >
bytes.available()) {
+ throw new ClassFormatException("Invalid TABLESWITCH: low = " +
low + ", high = " + high + " but only " + bytes.available() + " bytes remain");
+ }
buf.append("<TABLE BORDER=1><TR>");
// Print switch indices in first row (and default)
- jumpTable = new int[high - low + 1];
+ jumpTable = new int[(int) jumpTableLength];
for (int i = 0; i < jumpTable.length; i++) {
jumpTable[i] = offset + bytes.readInt();
buf.append("<TH>").append(low + i).append("</TH>");
@@ -130,6 +138,10 @@ final class CodeHTML {
case Const.LOOKUPSWITCH:
final int npairs = bytes.readInt();
offset = bytes.getIndex() - 8 - noPadBytes - 1;
+ // Each match-offset pair is 8 bytes, see the TABLESWITCH check
above.
+ if (npairs < 0 || (long) npairs * 8 > bytes.available()) {
+ throw new ClassFormatException("Invalid LOOKUPSWITCH: npairs =
" + npairs + " but only " + bytes.available() + " bytes remain");
+ }
jumpTable = new int[npairs];
defaultOffset += offset;
buf.append("<TABLE BORDER=1><TR>");
diff --git a/src/test/java/org/apache/bcel/classfile/UtilityTest.java
b/src/test/java/org/apache/bcel/classfile/UtilityTest.java
index 12ebb059..3d276583 100644
--- a/src/test/java/org/apache/bcel/classfile/UtilityTest.java
+++ b/src/test/java/org/apache/bcel/classfile/UtilityTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -90,6 +91,27 @@ class UtilityTest {
}
}
+ @Test
+ void testCodeToStringOversizedLookupSwitch() {
+ // A LOOKUPSWITCH claiming Integer.MAX_VALUE pairs without any table
bytes behind it
+ // must be rejected instead of allocating two huge arrays.
+ final byte[] code = { (byte) Const.LOOKUPSWITCH, 0, 0, 0, // opcode
plus 3 padding bytes
+ 0, 0, 0, 0, // default offset
+ 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }; // npairs =
Integer.MAX_VALUE
+ assertThrows(ClassFormatException.class, () ->
Utility.codeToString(new ByteSequence(code), new ConstantPool()));
+ }
+
+ @Test
+ void testCodeToStringOversizedTableSwitch() {
+ // A TABLESWITCH claiming 2^32 entries without any table bytes behind
it must be
+ // rejected instead of allocating a huge jump table.
+ final byte[] code = { (byte) Const.TABLESWITCH, 0, 0, 0, // opcode
plus 3 padding bytes
+ 0, 0, 0, 0, // default offset
+ (byte) 0x80, 0, 0, 0, // low = Integer.MIN_VALUE
+ 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }; // high =
Integer.MAX_VALUE
+ assertThrows(ClassFormatException.class, () ->
Utility.codeToString(new ByteSequence(code), new ConstantPool()));
+ }
+
@Test
void testCodeToStringWideIsThreadLocal() throws Exception {
// A WIDE opcode disassembled on one thread must not change how the
next