Skip to content

Commit

Permalink
Add new rule REFL_REFLECTION_INCREASES_ACCESSIBILITY_OF_CLASS (#1541)
Browse files Browse the repository at this point in the history
* Add new rule REFL_REFLECTION_INCREASES_ACCESSIBILITY_OF_CLASS

SEI CERT rule SEC05-J forbids the use of reflection to increase
accessibility of classes, methods or fields. If a class in a package
provides a public method which takes an instance of java.lang.Class as
its parameter and calls its newInstance() method then it increases
accessibility of classes in the same package without public constructors.
An attacker code may call this method and pass such class to create an
instance of it. This should be avoided by either making the method
non-public or by invoking SecurityManager.checkPackageAccess on the
package. A third possiblity is to use the java.beans.Beans.instantiate()
method instead of java.lang.Class.newInstance() which checks whether the
Class object being received has any public constructors.

This patch adds a new detector to check this rule.

* CHANGELOG.md updated

* Updated according to a comment from @KengoTODA. Also added additional condition to check whether the class is public as well.

* CHANGELOG fixed

* Exclude fields from the objects considered as class-type arguments

Because of field summaries some fields may also be initial parameters if they are initialized from constructor parameters. Let us exclude them!

* Error message changed to REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_CLASS

* Add the other part of the SEI CERT Rule: Fields

The SEI CERT rule also mentions two uncompliant code pieces where accessibility of fields are increased.
This patch extends the detector to also report these cases: REFL_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD.

* Bug codes separated and annotation added to the detector
  • Loading branch information
Balogh, Ádám committed Nov 7, 2021
1 parent 3088491 commit 085339f
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Currently the versioning policy of this project follows [Semantic Versioning v2.
### Added
* Rule `DCN_NULLPOINTER_EXCEPTION` covers catching NullPointerExceptions in accordance with SEI Cert rule [ERR08-J](https://wiki.sei.cmu.edu/confluence/display/java/ERR08-J.+Do+not+catch+NullPointerException+or+any+of+its+ancestors) ([#1740](https://github.com/spotbugs/spotbugs/pull/1740))
* Multiple types of report can be generated in batch. Set multiple commandline options for report configuration like `-html=report/spotbugs.html -xml:withMessages=report/spotbugs.xml`.
* New rule `REFL_REFLECTION_INCREASES_ACCESSIBILITY_OF_CLASS` to detect public methods instantiating a class they get in their parameter. This rule based on the SEI CERT rule *SEC05-J. Do not use reflection to increase accessibility of classes, methods, or fields*. ([#SEC05-J](https://wiki.sei.cmu.edu/confluence/display/java/SEC05-J.+Do+not+use+reflection+to+increase+accessibility+of+classes%2C+methods%2C+or+fields))

### Fixed
* False negative about the rule ES_COMPARING_STRINGS_WITH_EQ ([#1764](https://github.com/spotbugs/spotbugs/issues/1764))
Expand Down
4 changes: 4 additions & 0 deletions spotbugs/etc/findbugs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,8 @@
reports="JUA_DONT_ASSERT_INSTANCEOF_IN_TESTS"/>
<Detector class="edu.umd.cs.findbugs.detect.FindBadEndOfStreamCheck" speed="fast"
reports="EOS_BAD_END_OF_STREAM_CHECK"/>
<Detector class="edu.umd.cs.findbugs.detect.ReflectionIncreaseAccessibility"
reports="REFLC_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_CLASS,REFLF_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD"/>
<!-- Bug Categories -->
<BugCategory category="NOISE" hidden="true"/>

Expand Down Expand Up @@ -1258,4 +1260,6 @@
<BugPattern abbrev="NP" type="NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION" category="STYLE" />
<BugPattern abbrev="NP" type="NP_METHOD_PARAMETER_RELAXING_ANNOTATION" category="STYLE" deprecated="true" />
<BugPattern abbrev="EOS" type="EOS_BAD_END_OF_STREAM_CHECK" category="CORRECTNESS" />
<BugPattern abbrev="REFLC" type="REFLC_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_CLASS" category="MALICIOUS_CODE" />
<BugPattern abbrev="REFLF" type="REFLF_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD" category="MALICIOUS_CODE" />
</FindbugsPlugin>
43 changes: 43 additions & 0 deletions spotbugs/etc/messages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,16 @@ factory pattern to create these objects.</p>
]]>
</Details>
</Detector>
<Detector class="edu.umd.cs.findbugs.detect.ReflectionIncreaseAccessibility">
<Details>
<![CDATA[
<p>Detector for public methods instantiating a class they get in their parameter.</p>
<p>
An attacker may invoke this method with a class that has no public constructor.
</p>
]]>
</Details>
</Detector>
<!--
**********************************************************************
BugPatterns
Expand Down Expand Up @@ -8530,6 +8540,37 @@ after the call to initLogging, the logger configuration is lost
</p>]]>
</Details>
</BugPattern>
<BugPattern type="REFLC_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_CLASS">
<ShortDescription>Public method uses reflection to create a class it gets in its parameter which could increase the accessibility of any class</ShortDescription>
<LongDescription>Public method {1} uses reflection to create a class it gets in its parameter which could increase the accessibility of any class</LongDescription>
<Details>
<![CDATA[
<p>
<a href="https://wiki.sei.cmu.edu/confluence/display/java/SEC05-J.+Do+not+use+reflection+to+increase+accessibility+of+classes%2C+methods%2C+or+fields">SEI CERT SEC05-J</a> rule forbids the use of reflection to increase accessibility of classes, methods or fields. If
a class in a package provides a public method which takes an instance of java.lang.Class as its parameter and
calls its newInstance() method then it increases accessibility of classes in the same package without public
constructors. An attacker code may call this method and pass such class to create an instance of it. This should
be avoided by either making the method non-public or by checking for package access permission on the package.
A third possibility is to use the java.beans.Beans.instantiate() method instead of java.lang.Class.newInstance()
which checks whether the Class object being received has any public constructors.
</p>]]>
</Details>
</BugPattern>
<BugPattern type="REFLF_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD">
<ShortDescription>Public method uses reflection to modify a field it gets in its parameter which could increase the accessibility of any class</ShortDescription>
<LongDescription>Public method {1} uses reflection to modify a field it gets in its parameter which could increase the accessibility of any class.</LongDescription>
<Details>
<![CDATA[
<p>
<a href="https://wiki.sei.cmu.edu/confluence/display/java/SEC05-J.+Do+not+use+reflection+to+increase+accessibility+of+classes%2C+methods%2C+or+fields">SEI CERT SEC05-J</a> rule forbids the use of reflection to increase accessibility of classes, methods or fields. If
a class in a package provides a public method which takes an instance of java.lang.reflect.Field as its
parameter and calls a setter (or setAccessible()) method then it increases accessibility of fields in the same
package which are private, protected or package private. An attacker code may call this method and pass such
field to change it. This should be avoided by either making the method non-public or by checking for package
access permission on the package.
</p>]]>
</Details>
</BugPattern>
<!--
**********************************************************************
BugCodes
Expand Down Expand Up @@ -8676,4 +8717,6 @@ after the call to initLogging, the logger configuration is lost
<BugCode abbrev="DL">Unintended contention or possible deadlock due to locking on shared objects</BugCode>
<BugCode abbrev="JUA">Problems in JUnit Assertions</BugCode>
<BugCode abbrev="EOS">Bad End of Stream check</BugCode>
<BugCode abbrev="REFLC">Reflection increasing accessibility of classes</BugCode>
<BugCode abbrev="REFLF">Reflection increasing accessibility of fields</BugCode>
</MessageCollection>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* SpotBugs - Find bugs in Java programs
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

package edu.umd.cs.findbugs.detect;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;


import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;

@OpcodeStack.CustomUserValue
public class ReflectionIncreaseAccessibility extends OpcodeStackDetector {
private boolean securityCheck = false;
private boolean fieldNameUnderGet = false;

private final BugAccumulator bugAccumulator;

public ReflectionIncreaseAccessibility(BugReporter bugReporter) {
this.bugAccumulator = new BugAccumulator(bugReporter);
}

@Override
public void visit(Method met) {
securityCheck = false;
super.visit(met);
}

@Override
public void visitAfter(JavaClass obj) {
bugAccumulator.reportAccumulatedBugs();
}

@Override
public void sawOpcode(int seen) {
fieldNameUnderGet = false;

if (seen != Const.INVOKEVIRTUAL || !getThisClass().isPublic() || !getMethod().isPublic()) {
return;
}

OpcodeStack.Item obj = stack.getItemMethodInvokedOn(this);
try {
JavaClass cls = obj.getJavaClass();
if (cls == null) {
return;
}
XMethod met = getXMethodOperand();
if (!securityCheck && obj.isInitialParameter() && obj.getXField() == null &&
"java.lang.Class".equals(cls.getClassName()) &&
"newInstance".equals(met.getName()) &&
"()Ljava/lang/Object;".equals(met.getSignature())) {
bugAccumulator.accumulateBug(new BugInstance(this,
"REFLC_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_CLASS", NORMAL_PRIORITY)
.addClassAndMethod(this), this);
} else if ("java.lang.Class".equals(cls.getClassName()) &&
"getDeclaredField".equals(met.getName()) &&
"(Ljava/lang/String;)Ljava/lang/reflect/Field;".equals(met.getSignature())) {
OpcodeStack.Item param = stack.getStackItem(0);
fieldNameUnderGet = param.isInitialParameter() && param.getXField() == null;
} else if (!securityCheck && "java.lang.reflect.Field".equals(cls.getClassName()) &&
met.getName().startsWith("set")) {
Boolean fieldIsFromParam = (Boolean) obj.getUserValue();
if (fieldIsFromParam != null && fieldIsFromParam.booleanValue()) {
bugAccumulator.accumulateBug(new BugInstance(this,
"REFLF_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD", NORMAL_PRIORITY)
.addClassAndMethod(this), this);
}
} else if ("java.lang.SecurityManager".equals(cls.getClassName()) &&
"checkPackageAccess".equals(met.getName()) &&
"(Ljava/lang/String;)V".equals(met.getSignature()) &&
getPackageName().equals((String) stack.getStackItem(0).getConstant())) {
securityCheck = true;
}
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
}
}

@Override
public void afterOpcode(int seen) {
super.afterOpcode(seen);

if (fieldNameUnderGet) {
stack.getStackItem(0).setUserValue(true);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import edu.umd.cs.findbugs.annotations.NoWarning;

public class ReflectionIncreaseAccessibilityNegativeTest {
private Class cls;

ReflectionIncreaseAccessibilityNegativeTest(Class cls) {
this.cls = cls;
}

@NoWarning("REFLC")
public static <T> T create(Class<T> c)
throws InstantiationException, IllegalAccessException {
if (c.getConstructors().length == 0) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess("");
}
}
return c.newInstance();
}

private static class C {
Class<?> cls;
public C(Class<?> cls) {
this.cls = cls;
}
}

@NoWarning("REFLC")
public void strangeFP()
throws InstantiationException, IllegalAccessException {
cls.newInstance();
}

private C getC() {
return new C(Integer.class);
}

@NoWarning("REFLC")
public void strangeFP2()
throws InstantiationException, IllegalAccessException {
C c = getC();
c.cls.newInstance();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import java.lang.reflect.Field;

import edu.umd.cs.findbugs.annotations.ExpectWarning;

public class ReflectionIncreaseAccessibilityTest {
ReflectionIncreaseAccessibilityTest() {}

@ExpectWarning("REFLC")
public static <T> T create(Class<T> c)
throws InstantiationException, IllegalAccessException {
return c.newInstance();
}

private int field;

@ExpectWarning("REFLF")
public void setField(String fieldName, int value) {
try {
Field f = this.getClass().getDeclaredField(fieldName);
f.setInt(this, value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

@ExpectWarning("REFLF")
public Field getAccessibleField(String fieldName) {
try {
Field f = this.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
return f;
} catch (NoSuchFieldException e) {
e.printStackTrace();
return null;
} catch (SecurityException e) {
e.printStackTrace();
return null;
}
}
}

0 comments on commit 085339f

Please sign in to comment.