[ https://issues.apache.org/jira/browse/CLI-348?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Ruiqi Dong updated CLI-348: --------------------------- Affects Version/s: 1.10.0 > MissingOptionException lacks defensive copying, allowing external > modification of internal state > ------------------------------------------------------------------------------------------------ > > Key: CLI-348 > URL: https://issues.apache.org/jira/browse/CLI-348 > Project: Commons CLI > Issue Type: Bug > Affects Versions: 1.10.0 > Reporter: Ruiqi Dong > Priority: Major > > The {{MissingOptionException}} class has a defensive copying vulnerability > that allows external code to modify the exception's internal state after > construction. This violates the principle of encapsulation and can lead to > unpredictable behavior, especially in multi-threaded environments. As the > constructor {{MissingOptionException(List missingOptions)}} directly stores > the reference to the passed list without creating a copy, the getter method > {{getMissingOptions()}} returns a direct reference to the internal list, > allowing external modification. > > *Impact:* > * Violates the principle of immutability for exception objects > * Thread safety issues in concurrent environments > * Potential security vulnerability allowing unauthorized state modification > * Breaks the contract that exceptions should represent a fixed error state > *Test Case:* > {code:java} > @Test > void testDefensiveCopyAndImmutabilityOfMissingOptions() { > List<String> originalList = new ArrayList<>(Arrays.asList("optA", > "optB")); > MissingOptionException exception = new > MissingOptionException(originalList); originalList.add("optC"); > originalList.remove(0); assertEquals(Arrays.asList("optA", "optB"), > exception.getMissingOptions(), > "Missing options list should be isolated from original list > modifications"); List<String> retrievedList = > exception.getMissingOptions(); > assertThrows(UnsupportedOperationException.class, () -> > retrievedList.add("optD"), > "Missing options list should prevent modifications through defensive > immutability"); > } {code} > *Failure Scenario:* > {code:java} > [ERROR] > org.apache.commons.cli.MissingOptionExceptionTest.testDefensiveCopyAndImmutabilityOfMissingOptions > -- Time elapsed: 0.001 s <<< FAILURE! > org.opentest4j.AssertionFailedError: Missing options list should be isolated > from original list modifications ==> expected: <[optA, optB]> but was: > <[optB, optC]> > at > org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151) > at > org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132) > at > org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197) > at > org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182) > at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1156) > at > org.apache.commons.cli.MissingOptionExceptionTest.testDefensiveCopyAndImmutabilityOfMissingOptions(MissingOptionExceptionTest.java:164) > at java.lang.reflect.Method.invoke(Method.java:498) > at java.util.ArrayList.forEach(ArrayList.java:1259) > at java.util.ArrayList.forEach(ArrayList.java:1259) {code} > *Suggested Fix:* > {code:java} > public MissingOptionException(final List missingOptions) { > super(createMessage(missingOptions)); > this.missingOptions = missingOptions == null ? null : > Collections.unmodifiableList(new ArrayList<>(missingOptions)); > }{code} -- This message was sent by Atlassian Jira (v8.20.10#820010)