diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S9357.json b/its/ruling/src/test/resources/commons-beanutils/java-S9357.json index 8dec2c78df4..fbcbd085cce 100644 --- a/its/ruling/src/test/resources/commons-beanutils/java-S9357.json +++ b/its/ruling/src/test/resources/commons-beanutils/java-S9357.json @@ -1,4 +1,14 @@ { +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanMap.java": [ +74, +83, +92, +101, +110, +119, +128, +137 +], "commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/bugs/Jira509TestCase.java": [ 60 ] diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9357.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9357.json new file mode 100644 index 00000000000..d6f51dd9451 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9357.json @@ -0,0 +1,11 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java": [ +144 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java": [ +74 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +297 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9357.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9357.json index a234f34288b..a7e2d56f89e 100644 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S9357.json +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S9357.json @@ -1,4 +1,13 @@ { +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java": [ +144 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java": [ +74 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +297 +], "org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java": [ 150, 203, @@ -18,6 +27,9 @@ "org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java": [ 285 ], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java": [ +37 +], "org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java": [ 219, 293, diff --git a/its/ruling/src/test/resources/guava/java-S9357.json b/its/ruling/src/test/resources/guava/java-S9357.json new file mode 100644 index 00000000000..e7881e1d44d --- /dev/null +++ b/its/ruling/src/test/resources/guava/java-S9357.json @@ -0,0 +1,176 @@ +{ +"com.google.guava:guava:src/com/google/common/base/Converter.java": [ +180 +], +"com.google.guava:guava:src/com/google/common/base/Optional.java": [ +297 +], +"com.google.guava:guava:src/com/google/common/base/Splitter.java": [ +148, +179, +230, +294 +], +"com.google.guava:guava:src/com/google/common/cache/CacheBuilder.java": [ +183 +], +"com.google.guava:guava:src/com/google/common/cache/LocalCache.java": [ +2327, +3547 +], +"com.google.guava:guava:src/com/google/common/cache/LongAddables.java": [ +38, +45 +], +"com.google.guava:guava:src/com/google/common/cache/RemovalListeners.java": [ +45, +48 +], +"com.google.guava:guava:src/com/google/common/collect/Collections2.java": [ +302 +], +"com.google.guava:guava:src/com/google/common/collect/FilteredEntryMultimap.java": [ +398 +], +"com.google.guava:guava:src/com/google/common/collect/Interners.java": [ +46 +], +"com.google.guava:guava:src/com/google/common/collect/Iterables.java": [ +1014 +], +"com.google.guava:guava:src/com/google/common/collect/Iterators.java": [ +1296 +], +"com.google.guava:guava:src/com/google/common/collect/MapConstraints.java": [ +227, +485 +], +"com.google.guava:guava:src/com/google/common/collect/Maps.java": [ +1863, +1874, +1888, +1922, +3098 +], +"com.google.guava:guava:src/com/google/common/collect/Multimaps.java": [ +519, +1202, +1702 +], +"com.google.guava:guava:src/com/google/common/collect/Multisets.java": [ +305 +], +"com.google.guava:guava:src/com/google/common/collect/MutableClassToInstanceMap.java": [ +64 +], +"com.google.guava:guava:src/com/google/common/collect/Range.java": [ +117, +129 +], +"com.google.guava:guava:src/com/google/common/collect/RegularImmutableTable.java": [ +114 +], +"com.google.guava:guava:src/com/google/common/collect/StandardTable.java": [ +721, +794 +], +"com.google.guava:guava:src/com/google/common/collect/Tables.java": [ +240, +390, +419, +429, +592 +], +"com.google.guava:guava:src/com/google/common/collect/TreeBasedTable.java": [ +321 +], +"com.google.guava:guava:src/com/google/common/escape/Escaper.java": [ +90 +], +"com.google.guava:guava:src/com/google/common/eventbus/Subscriber.java": [ +76 +], +"com.google.guava:guava:src/com/google/common/io/CharSource.java": [ +442 +], +"com.google.guava:guava:src/com/google/common/net/MediaType.java": [ +406, +744 +], +"com.google.guava:guava:src/com/google/common/reflect/ClassPath.java": [ +64 +], +"com.google.guava:guava:src/com/google/common/reflect/MutableTypeToInstanceMap.java": [ +124 +], +"com.google.guava:guava:src/com/google/common/reflect/TypeToken.java": [ +701 +], +"com.google.guava:guava:src/com/google/common/reflect/Types.java": [ +61 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractExecutionThreadService.java": [ +45, +50, +145 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractIdleService.java": [ +40, +50, +64, +95 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractScheduledService.java": [ +205, +210, +232, +304 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AggregateFuture.java": [ +138 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AtomicLongMap.java": [ +294 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Callables.java": [ +44, +94 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Futures.java": [ +1168, +1494, +1569, +1662, +1749 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/FuturesGetChecked.java": [ +253 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/JdkFutureAdapters.java": [ +160 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/MoreExecutors.java": [ +204, +754, +856 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Runnables.java": [ +31 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ServiceManager.java": [ +598 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Striped.java": [ +199, +214, +236, +252, +283 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ThreadFactoryBuilder.java": [ +161 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/WrappingExecutorService.java": [ +67 +] +} diff --git a/its/ruling/src/test/resources/mall/java-S9357.json b/its/ruling/src/test/resources/mall/java-S9357.json new file mode 100644 index 00000000000..1245f4a90b1 --- /dev/null +++ b/its/ruling/src/test/resources/mall/java-S9357.json @@ -0,0 +1,6 @@ +{ +"com.macro.mall:mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/OmsPromotionServiceImpl.java": [ +177, +211 +] +} diff --git a/its/ruling/src/test/resources/sonar-server/java-S9357.json b/its/ruling/src/test/resources/sonar-server/java-S9357.json index ab90c0f4e03..859e9241a2d 100644 --- a/its/ruling/src/test/resources/sonar-server/java-S9357.json +++ b/its/ruling/src/test/resources/sonar-server/java-S9357.json @@ -1,4 +1,11 @@ { +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java": [ +55 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssuesFinderSort.java": [ +89, +142 +], "org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java": [ 64 ], diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html index affebc7a3fa..6ea4bfb91ce 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html @@ -17,8 +17,9 @@
For AES, the weakest mode is ECB (Electronic Codebook). Repeated blocks of data are encrypted to the same value, making them easy to identify and reducing the difficulty of recovering the original cleartext.
For RSA, the weakest algorithms are either using it without padding or using the PKCS1v1.5 padding scheme.
+For RSA signatures, the generally recommended good practice is to use RSASSA-PSS.
The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.
+The cleartext of an encrypted message might be recoverable, modifiable, or a signature might be forged.
Below are some real-world scenarios that illustrate possible impacts of an attacker exploiting the vulnerability.
The encrypted message might contain data that is considered sensitive and should not be known to third parties.
@@ -26,6 +27,9 @@By modifying the cleartext of the encrypted message it might be possible for an attacker to trigger other vulnerabilities in the code. Encrypted values are often considered trusted, since under normal circumstances it would not be possible for a third party to modify them.
+A weak signature padding scheme without a security proof might allow an attacker to forge a signature that a flawed verifier implementation accepts +as valid, undermining the trust and integrity guarantees signatures are meant to provide.
The Optimal Asymmetric Encryption Padding scheme (OAEP) adds randomness and a secure hash function that strengthens the regular inner workings of RSA.
+The Probabilistic Signature Scheme (RSASSA-PSS) uses a randomized encoding and comes with a security proof, whereas the older PKCS#1 v1.5 signature +scheme (RSASSA-PKCS1-v1_5) has none and has repeatedly enabled signature forgery against flawed verifier implementations.
+IETF RFC 8017 therefore requires RSASSA-PSS for new applications, in the interest of increased robustness.
This is an issue when an object’s initialization code can throw an exception and the class is not protected against attacks through cleanup hooks, +which can execute on partially-constructed objects. Protection mechanisms include preventing inheritance, providing empty cleanup implementations that +cannot be overridden, or using construction patterns that prevent direct instantiation.
+In Java, these cleanup hooks are called finalizers, and the protection mechanisms specifically refer to declaring the class as final,
+or using the factory pattern. Implementing a final empty finalize() method was also a known solution, but
+finalize() has been deprecated since Java 9 and is marked for removal.
When a constructor throws an exception, you might expect the object construction to fail completely and no reference to the object to exist. However, -finalization or cleanup mechanisms can be exploited to obtain a reference to a partially-constructed object.
-Here's how a Finalizer attack works:
+When a constructor throws an exception, you might expect the object construction to fail completely and no reference to the object to exist. +However, finalization or cleanup mechanisms can be exploited to obtain a reference to a partially-constructed object.
+Here’s how a Finalizer attack works:
This vulnerability is particularly dangerous for security-sensitive classes where the constructor performs authentication or authorization checks, -input validation, resource allocation with security constraints, or initialization of security-critical fields.
+This vulnerability is particularly dangerous for security-sensitive classes where the constructor performs:
+If an attacker can obtain a reference to a partially-constructed instance, they may be able to call methods on an object in an inconsistent state, +potentially bypassing security controls.
+Attacks exploiting cleanup mechanisms for incompletely initialized objects can lead to serious security vulnerabilities:
+The simplest solution is to declare the class as final. This prevents attackers from creating malicious subclasses that override the
finalize() method.
However, some frameworks such as Spring or JPA/Hibernate require non-final classes. In such cases, use a factory method with a private
constructor to ensure the object is fully validated before any reference is exposed. Since the constructor is private, no malicious subclass
can be created, achieving the same protection as final.
This rule targets classes whose constructors can throw exceptions and that are not protected against finalizer attacks. If the class is internal to +your application and never exposed to untrusted code, or if the constructor cannot actually throw, the risk is lower and the finding may be acceptable +as a won’t-fix. A genuine false positive would occur when the constructor is guaranteed not to throw, making the attack path impossible.
+
public class SecuritySensitiveClass {
private final String credentials;
@@ -35,7 +62,7 @@ Noncompliant code example
}
}
-
public final class SecuritySensitiveClass { // Compliant: class is final
private final String credentials;
@@ -52,16 +79,39 @@ Compliant solution
}
}
+When the class cannot be final because a framework requires subclassing or proxying, use a factory method with a private
+constructor:
+public class SecuritySensitiveClass { // Compliant: constructor is private, instances are created through factory method
+ private final String credentials;
+
+ private SecuritySensitiveClass(String credentials) {
+ this.credentials = credentials;
+ }
+
+ public static SecuritySensitiveClass create(String credentials) throws AuthenticationException {
+ if (!isValid(credentials)) {
+ throw new AuthenticationException("Invalid credentials");
+ }
+ return new SecuritySensitiveClass(credentials);
+ }
+
+ private static boolean isValid(String credentials) {
+ return credentials != null && credentials.length() > 10;
+ }
+}
+
Using 32-bit signed integer types for timestamps can lead to serious reliability issues such as incorrect time representation, -system failures, and the Year 2038 problem.
+This is an issue when a smaller integer type value is widened to a larger integer type and then used as an absolute timestamp, such as milliseconds +or seconds since the Unix epoch (January 1, 1970).
A 32-bit signed integer can hold values from -2,147,483,648 to 2,147,483,647. While this might seem like a large range, it's insufficient for +
A 32-bit signed integer can hold values from -2,147,483,648 to 2,147,483,647. While this might seem like a large range, it’s insufficient for representing timestamps:
When you cast a 32-bit integer to a 64-bit integer for use as a timestamp, you're not fixing the underlying problem — the value is already -corrupted or limited by the 32-bit constraint before the cast happens.
-When you cast a 32-bit integer to a 64-bit integer for use as a timestamp, you’re not fixing the underlying problem — the value is already +corrupted or limited by the 32-bit constraint before the cast happens. The cast simply preserves the incorrect or overflowed value in a larger +container.
+Timestamps should be stored as 64-bit integer values from the start to ensure they can represent dates far into the past and future without +overflow. A 64-bit integer type can represent timestamps for approximately 292 million years, which is more than sufficient for any practical +application.
+Using a 32-bit integer for timestamps is almost always a bug that will cause:
+In Java, 32-bit integers are represented by the int type and 64-bit integers by the long type.
Using 32-bit signed integer types for timestamps can lead to serious reliability issues:
+Change the variable type from int to long to properly represent the timestamp. This ensures the value can hold the full
+range of possible timestamp values without overflow.
int timestamp = 1234567890; -Date date = new Date(timestamp); // Noncompliant — int implicitly widened -Date date2 = new Date((long) timestamp); // Noncompliant — cast doesn't fix overflow +Date date = new Date(timestamp); // Noncompliant: int implicitly widened +Date date2 = new Date((long) timestamp); // Noncompliant: the cast does not fix the overflow-
long timestamp = 1234567890L; Date date = new Date(timestamp); -Date date2 = new Date(timestamp);
A single-method interface is a type that defines exactly one abstract behavior contract. Common examples include interfaces for tasks, -computations, comparisons, and various transformation operations.
-Before modern versions of the language introduced simplified syntax, the only way to implement a single-method interface was through verbose inline -type implementations. This approach requires significant boilerplate code that obscures the actual logic:
+A single-method interface is a type that defines exactly one abstract behavior contract. In Java, these are called functional
+interfaces. Common examples include Runnable, Callable, Comparator, and the interfaces in the
+java.util.function package.
Before Java 8 introduced lambda expressions, the only way to implement a functional interface inline was through verbose anonymous inner classes. +This approach requires significant boilerplate code that obscures the actual logic:
taskVariable = new InterfaceImplementation() {
@Override
@@ -14,23 +15,37 @@ Why is this an issue?
}
};
-Modern language versions introduced inline function syntax specifically to simplify this pattern. An inline function literal is a concise way to -represent a single-method interface using an expression. The same functionality can be written as:
+Java 8 introduced lambda expressions specifically to simplify this pattern. A lambda expression is a concise way to represent a functional +interface using an expression. The same functionality can be written as:
taskVariable = () -> processData();-
Using inline function syntax instead of verbose type implementations offers several benefits:
+Using lambda expressions instead of anonymous inner classes offers several benefits:
@Override annotations.The conversion from anonymous inner class to lambda expression is straightforward because the compiler can infer the interface type from the +context. This type inference is what makes lambda expressions so concise.
+Note that this conversion may require judgment in some cases. When the anonymous class references its own this keyword, the semantics
+change in a lambda (see "How to fix it" below). Similarly, when the target type is ambiguous due to method overloading, additional casts or type hints
+may be needed.
This rule does not apply when the anonymous class is intentionally kept for readability (e.g., complex multi-statement bodies where a lambda would +hurt clarity), when framework constraints require a named type, or when the anonymous class defines additional methods or fields beyond the single +abstract method.
+This rule does not raise an issue in the following cases:
+this or super, because this in a lambda refers to the enclosing class, not
+ the lambda itself.Serializable explicitly), because lambda serialization has
+ different semantics and caveats.The conversion from verbose inline implementation to concise function syntax is straightforward because the compiler can infer the interface type -from the context. This type inference is what makes inline functions so concise.
In Java, these single-method interfaces are called functional interfaces. Common examples include Runnable,
Callable, Comparator, and the many interfaces in the java.util.function package. Lambda expressions were
introduced in Java 8 as the standard syntax for implementing functional interfaces concisely.
Replace the anonymous inner class with a lambda expression. The lambda syntax uses parameters in parentheses, an arrow →, and the
+
Replace the anonymous inner class with a lambda expression. The lambda syntax uses parameters in parentheses, an arrow ->, and the
method body. For single-expression bodies, you can omit the braces and return keyword.
When the lambda body is a single method call, consider using a method reference instead (e.g., this::processData or
+System.out::println). Method references can be even more concise than lambdas and make the intent clearer.
Note that this inside an anonymous inner class refers to the anonymous class instance, while this inside a lambda refers
+to the enclosing class. Before converting, check whether the anonymous class uses this to reference its own instance. If it does, the
+conversion will change behavior and should not be applied mechanically.
@@ -65,8 +85,8 @@Compliant solution
Resources
Documentation