#49003 [SC-Low] Array Underflow Vulnerability in UInt64SetLib leads to contract failure
Submitted on Jul 10th 2025 at 12:44:28 UTC by @HandsomeEarthworm6 for Audit Comp | Folks Smart Contract Library
Report ID: #49003
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/Folks-Finance/algorand-smart-contract-library/blob/main/contracts/library/UInt64SetLib.py
Impacts:
Temporary denial of service (smart contract is made unable to operate for one block, functionality is restored in the next block)
Description
Brief/Intro
The UInt64SetLib.py contract contains an integer underflow vulnerability in the remove_item function that causes temporary contract failure when attempting to remove items from empty arrays.
Vulnerability Details
The vulnerability exists in the remove_item_for_header function of contracts/library/UInt64SetLib.py:
last_idx = items.length - 1 # Critical underflow when items.length = 0
When items.length equals 0, the subtraction operation 0 - 1 causes an integer underflow in the underlying TEAL assembly code. Since Algorand uses unsigned 64-bit integers (UInt64), this underflow wraps the value to UInt64::MAX. The contract then attempts to access items[18446744073709551615], which immediately fails with an array bounds error as shown below. intc_0 // 0 <- Push 0 (array length) extract_uint16 <- Extract as 16-bit uint dup <- Duplicate the length value intc_1 // 1 <- Push 1
<--- Error <- Subtraction causes underflow and crash
The vulnerability affects multiple code paths:
Direct calls to removeItem() with empty arrays
Cascading failures when arrays become empty through normal operations
Batch operations that process items sequentially
Test Results Confirmation: Six separate test cases failed with identical underflow errors, confirming the vulnerability affects:
1.remove_item on empty array should not crash
2.remove_item on empty array with different values
3.single item removal leading to empty array
4.operations on empty arrays
5.consecutive operations on empty arrays
Impact Details
Contract Failure: Any call to remove_item with an empty array immediately crashes the contract
##Recommendation check if the array is empty before trying to calculate last_idx
Proof of Concept
Proof of Concept
add this test to tests/library/UInt64SetLib.test.ts and then run :;
npx jest tests/library/UInt64SetLib.test.ts --testTimeout=30000
/ Add these test cases to your existing describe("UInt64SetLib") block
describe("Security Vulnerability Tests", () => { describe("Critical Issue: Array Underflow on Empty Array", () => { test("remove_item on empty array should not crash", async () => { // This test will likely FAIL and expose the critical bug const result = await client.removeItem({ args: [1n, []] }); expect(result).toEqual([false, []]); });
test("remove_item on empty array with different values", async () => {
expect(await client.removeItem({ args: [0n, []] })).toEqual([false, []]);
expect(await client.removeItem({ args: [999999n, []] })).toEqual([false, []]);
});
});
describe("Edge Cases", () => {
test("single item removal leading to empty array", async () => {
// Remove the only item to create empty array
const removeResult = await client.removeItem({ args: [42n, [42n]] });
expect(removeResult).toEqual([true, []]);
// Try to remove from the now-empty result
const removeFromEmpty = await client.removeItem({ args: [42n, removeResult[1]] });
expect(removeFromEmpty).toEqual([false, []]);
});
test("operations on empty arrays", async () => {
// Test has_item on empty array
expect(await client.hasItem({ args: [1n, []] })).toBeFalsy();
expect(await client.hasItem({ args: [0n, []] })).toBeFalsy();
// Test add_item on empty array
expect(await client.addItem({ args: [1n, []] })).toEqual([true, [1n]]);
expect(await client.addItem({ args: [0n, []] })).toEqual([true, [0n]]);
// Test remove_item on empty array
expect(await client.removeItem({ args: [1n, []] })).toEqual([false, []]);
expect(await client.removeItem({ args: [0n, []] })).toEqual([false, []]);
});
test("consecutive operations on empty arrays", async () => {
// Multiple remove operations on empty arrays
for (let i = 0; i < 3; i++) {
const result = await client.removeItem({ args: [BigInt(i), []] });
expect(result).toEqual([false, []]);
}
});
});
describe("Resource Tests", () => {
test("operations on large arrays", async () => {
// Create a moderately large array
const largeArray = Array.from({ length: 50 }, (_, i) => BigInt(i));
// These should complete without issues
const hasResult = await client.hasItem({ args: [25n, largeArray] });
expect(typeof hasResult).toBe("boolean");
const addResult = await client.addItem({ args: [999n, largeArray] });
expect(Array.isArray(addResult)).toBe(true);
const removeResult = await client.removeItem({ args: [25n, largeArray] });
expect(Array.isArray(removeResult)).toBe(true);
});
});
});
Was this helpful?