diff --git a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java index 4d552d5948..10fdf65ced 100644 --- a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java +++ b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java @@ -310,6 +310,15 @@ protected boolean checkExclusionList(Object target, Member member) { } /** + * Blocks access to classes in the default (unnamed) package. + *
+ * The emptiness of {@link #toPackageName(Class)} is the same test as the
+ * {@code getPackage() == null || getPackage().getName().isEmpty()} form this replaced, for every
+ * class shape: {@code getPackage()} is null for arrays, primitives and {@code void}, and names the
+ * unnamed package with the empty string, all of which {@code toPackageName} reports as empty. It
+ * avoids the {@code getPackage()} lookup through the defining classloader's package map, which
+ * ran twice per class here. See WW-5677.
+ *
* @return {@code true} if member access is allowed
*/
protected boolean checkDefaultPackageAccess(Object target, Member member) {
@@ -317,7 +326,7 @@ protected boolean checkDefaultPackageAccess(Object target, Member member) {
return true;
}
Class> memberClass = member.getDeclaringClass();
- if (memberClass.getPackage() == null || memberClass.getPackage().getName().isEmpty()) {
+ if (toPackageName(memberClass).isEmpty()) {
LOG.warn("Class [{}] from the default package is excluded!", memberClass);
return false;
}
@@ -325,7 +334,7 @@ protected boolean checkDefaultPackageAccess(Object target, Member member) {
return true;
}
Class> targetClass = target.getClass();
- if (targetClass.getPackage() == null || targetClass.getPackage().getName().isEmpty()) {
+ if (toPackageName(targetClass).isEmpty()) {
LOG.warn("Class [{}] from the default package is excluded!", targetClass);
return false;
}
@@ -407,7 +416,9 @@ public static String toPackageName(Class> clazz) {
}
protected boolean isExcludedPackageNamePatterns(Class> clazz) {
- return excludedPackageNamePatterns.stream().anyMatch(pattern -> pattern.matcher(toPackageName(clazz)).matches());
+ // Resolved once rather than inside the lambda, which re-resolved it per configured pattern.
+ String packageName = toPackageName(clazz);
+ return excludedPackageNamePatterns.stream().anyMatch(pattern -> pattern.matcher(packageName).matches());
}
protected boolean isExcludedPackageNames(Class> clazz) {
diff --git a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
index 3dfa540dd4..d333b20af6 100644
--- a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
+++ b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
@@ -53,6 +53,15 @@ private static boolean legacyPrefixMatch(String packageName, Set