[ 
https://issues.apache.org/jira/browse/LANG-1814?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Ivan Ponomarev updated LANG-1814:
---------------------------------
    Description: 
{{ArrayUtils.subarray(.., int startIndexInclusive, int endIndexExclusive)}} 
(the full family of overloads for different array types) is documented to 
return an empty array when {{{}endIndexExclusive < startIndexInclusive{}}}:
{quote}“Undervalue (< startIndex) produces empty array.”
{quote}
However, for certain extreme combinations of indices the method violates this 
contract due to subtraction overflow in index arithmetic. After clamping:
 * {{startIndexInclusive}} is promoted to {{>= 0}}

 * {{endIndexExclusive}} is only clamped to {{{}<= array.length{}}}, but may 
remain negative

The method then computes:
 
{{final int newSize = endIndexExclusive - startIndexInclusive;}}
This subtraction is performed in {{int}} and may overflow, turning a logically 
negative size into a positive value. As a result, the method attempts to 
allocate and/or copy an array instead of returning an empty result.

*Observed failure modes*
Depending on the overflowed value and available heap size, this leads to:
 * {{ArrayIndexOutOfBoundsException}} (small positive overflow, allocation 
succeeds, copy fails)

 * {{OutOfMemoryError}} (large positive overflow, allocation attempt fails)

{{*Reproducer*}}

   @ParameterizedTest
    @CsvSource({
            // Normal operation
            "2, 1",
            // Overflowed to newSize == 1 (AIOOBE)
            "2147483647, -2147483648",
            // Overflowed to a large positive newSize (OOME)
            "2000000000, -2000000000"
    })
    void testNegativeEndAndHugeStartDoesNotOverflow(int startIndexInclusive, 
int endIndexExclusive) {
        int[] array = \{1, 2, 3};
        int[] result = ArrayUtils.subarray(array, startIndexInclusive, 
endIndexExclusive);
        assertEquals(0, result.length);
    }
{{ }}
{{(For all inputs where {{{}endIndexExclusive < startIndexInclusive{}}}, the 
method should return an empty array as documented, however, the method may 
throw {{ArrayIndexOutOfBoundsException}} or {{OutOfMemoryError}} instead of 
returning an empty array.)}}

*Proposed Fix*
For all {{ArrayUtils.subarray(..)}} overloads, ensure {{endIndexExclusive}} is 
clamped to the same lower bound as {{startIndexInclusive}} before subtraction:
 
{{endIndexExclusive = {*}max0{*}(Math.min(endIndexExclusive, array.length));}}
{{int newSize = endIndexExclusive - startIndexInclusive; }}
This guarantees:
 * {{0 <= endIndexExclusive}} < {{array.length}}

 * {{newSize}} cannot overflow

 * {{newSize <= 0}} correctly triggers an empty array return

 

 

  was:
{{ArrayUtils.subarray(.., int startIndexInclusive, int endIndexExclusive)}} 
(the full family of overloads for different array types) is documented to 
return an empty array when {{{}endIndexExclusive < startIndexInclusive{}}}:
{quote}“Undervalue (< startIndex) produces empty array.”
{quote}
However, for certain extreme combinations of indices the method violates this 
contract due to subtraction overflow in index arithmetic. After clamping:
 * {{startIndexInclusive}} is promoted to {{>= 0}}

 * {{endIndexExclusive}} is only clamped to {{{}<= array.length{}}}, but may 
remain negative

The method then computes:
 
{{final int newSize = endIndexExclusive - startIndexInclusive;}}
This subtraction is performed in {{int}} and may overflow, turning a logically 
negative size into a positive value. As a result, the method attempts to 
allocate and/or copy an array instead of returning an empty result.

*Observed failure modes*
Depending on the overflowed value and available heap size, this leads to:
 * {{ArrayIndexOutOfBoundsException}} (small positive overflow, allocation 
succeeds, copy fails)

 * {{OutOfMemoryError}} (large positive overflow, allocation attempt fails)

*Reproducer*
{color:#9e880d}@ParameterizedTest
{color}{color:#9e880d}@CsvSource{color}({
{color:#8c8c8c}// Normal operation
{color}{color:#8c8c8c} {color}{color:#067d17}"2, 1"{color},
{color:#8c8c8c}// Overflowed to newSize == 1 (AIOOBE)
{color}{color:#8c8c8c} {color}{color:#067d17}"2147483647, -2147483648"{color},
{color:#8c8c8c}// Overflowed to a large positive newSize (OOME)
{color}{color:#8c8c8c} {color}{color:#067d17}"2000000000, -2000000000"
{color}})
{color:#0033b3}void 
{color}{color:#00627a}testNegativeEndAndHugeStartDoesNotOverflow{color}({color:#0033b3}int
 {color}{color:#000000}startIndexInclusive{color}, {color:#0033b3}int 
{color}{color:#000000}endIndexExclusive{color}) {
{color:#0033b3}int{color}[] {color:#000000}array {color}= 
{{color:#1750eb}1{color}, {color:#1750eb}2{color}, {color:#1750eb}3{color}};
{color:#0033b3}int{color}[] {color:#000000}result {color}= 
{color:#000000}ArrayUtils{color}.subarray({color:#000000}array{color}, 
{color:#000000}startIndexInclusive{color}, 
{color:#000000}endIndexExclusive{color});
assertEquals({color:#1750eb}0{color}, 
{color:#000000}result{color}.{color:#871094}length{color});
}
 
(For all inputs where {{{}endIndexExclusive < startIndexInclusive{}}}, the 
method should return an empty array as documented, however, the method may 
throw {{ArrayIndexOutOfBoundsException}} or {{OutOfMemoryError}} instead of 
returning an empty array.)


*Proposed Fix*
For all {{ArrayUtils.subarray(..)}} overloads, ensure {{endIndexExclusive}} is 
clamped to the same lower bound as {{startIndexInclusive}} before subtraction:
 
{{endIndexExclusive = {*}max0{*}(Math.min(endIndexExclusive, array.length));}}
{{int newSize = endIndexExclusive - startIndexInclusive; }}
This guarantees:
 * {{0 <= endIndexExclusive}} < {{array.length}}

 * {{newSize}} cannot overflow

 * {{newSize <= 0}} correctly triggers an empty array return

 

 


> ArrayUtils.subarray(..) may overflow index arithmetic and violate contract 
> for extreme index values
> ---------------------------------------------------------------------------------------------------
>
>                 Key: LANG-1814
>                 URL: https://issues.apache.org/jira/browse/LANG-1814
>             Project: Commons Lang
>          Issue Type: Bug
>          Components: lang.*
>            Reporter: Ivan Ponomarev
>            Priority: Trivial
>
> {{ArrayUtils.subarray(.., int startIndexInclusive, int endIndexExclusive)}} 
> (the full family of overloads for different array types) is documented to 
> return an empty array when {{{}endIndexExclusive < startIndexInclusive{}}}:
> {quote}“Undervalue (< startIndex) produces empty array.”
> {quote}
> However, for certain extreme combinations of indices the method violates this 
> contract due to subtraction overflow in index arithmetic. After clamping:
>  * {{startIndexInclusive}} is promoted to {{>= 0}}
>  * {{endIndexExclusive}} is only clamped to {{{}<= array.length{}}}, but may 
> remain negative
> The method then computes:
>  
> {{final int newSize = endIndexExclusive - startIndexInclusive;}}
> This subtraction is performed in {{int}} and may overflow, turning a 
> logically negative size into a positive value. As a result, the method 
> attempts to allocate and/or copy an array instead of returning an empty 
> result.
> *Observed failure modes*
> Depending on the overflowed value and available heap size, this leads to:
>  * {{ArrayIndexOutOfBoundsException}} (small positive overflow, allocation 
> succeeds, copy fails)
>  * {{OutOfMemoryError}} (large positive overflow, allocation attempt fails)
> {{*Reproducer*}}
>    @ParameterizedTest
>     @CsvSource({
>             // Normal operation
>             "2, 1",
>             // Overflowed to newSize == 1 (AIOOBE)
>             "2147483647, -2147483648",
>             // Overflowed to a large positive newSize (OOME)
>             "2000000000, -2000000000"
>     })
>     void testNegativeEndAndHugeStartDoesNotOverflow(int startIndexInclusive, 
> int endIndexExclusive) {
>         int[] array = \{1, 2, 3};
>         int[] result = ArrayUtils.subarray(array, startIndexInclusive, 
> endIndexExclusive);
>         assertEquals(0, result.length);
>     }
> {{ }}
> {{(For all inputs where {{{}endIndexExclusive < startIndexInclusive{}}}, the 
> method should return an empty array as documented, however, the method may 
> throw {{ArrayIndexOutOfBoundsException}} or {{OutOfMemoryError}} instead of 
> returning an empty array.)}}
> *Proposed Fix*
> For all {{ArrayUtils.subarray(..)}} overloads, ensure {{endIndexExclusive}} 
> is clamped to the same lower bound as {{startIndexInclusive}} before 
> subtraction:
>  
> {{endIndexExclusive = {*}max0{*}(Math.min(endIndexExclusive, array.length));}}
> {{int newSize = endIndexExclusive - startIndexInclusive; }}
> This guarantees:
>  * {{0 <= endIndexExclusive}} < {{array.length}}
>  * {{newSize}} cannot overflow
>  * {{newSize <= 0}} correctly triggers an empty array return
>  
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to