/* * Copyright 2017 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.errorprone.bugpatterns.android; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.errorprone.BugPattern.Category.ANDROID; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.matchers.Matchers.allOf; import static com.google.errorprone.matchers.Matchers.hasModifier; import static com.google.errorprone.matchers.Matchers.isSameType; import static com.google.errorprone.matchers.Matchers.methodHasParameters; import static com.google.errorprone.matchers.Matchers.methodIsNamed; import static com.google.errorprone.matchers.Matchers.not; import static com.google.errorprone.util.ASTHelpers.constValue; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.google.errorprone.util.ASTHelpers.getType; import static com.google.errorprone.util.ASTHelpers.isSubtype; import static com.google.errorprone.util.ASTHelpers.resolveExistingMethod; import com.google.common.collect.ImmutableList; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ReturnTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.util.FatalError; import javax.lang.model.element.Modifier; /** @author epmjohnston@google.com (Emily P.M. Johnston) */ @BugPattern( name = "FragmentInjection", summary = "Classes extending PreferenceActivity must implement isValidFragment such that it does not" + " unconditionally return true to prevent vulnerability to fragment injection attacks.", category = ANDROID, severity = WARNING ) public class FragmentInjection extends BugChecker implements ClassTreeMatcher { private static final Matcher<MethodTree> OVERRIDES_IS_VALID_FRAGMENT = allOf(methodIsNamed("isValidFragment"), methodHasParameters(isSameType("java.lang.String"))); @Override public Description matchClass(ClassTree tree, VisitorState state) { // Only examine classes that extend PreferenceActivity. Type preferenceActivityType = state.getTypeFromString("android.preference.PreferenceActivity"); if (!isSubtype(getType(tree), preferenceActivityType, state)) { return NO_MATCH; } // Examine each method in the class. Complain if isValidFragment not implemented. TypeSymbol preferenceActivityTypeSymbol = preferenceActivityType.tsym; boolean methodNotImplemented = true; try { MethodSymbol isValidFragmentMethodSymbol = resolveExistingMethod( state, getSymbol(tree), state.getName("isValidFragment"), ImmutableList.<Type>of(state.getTypeFromString("java.lang.String")), ImmutableList.<Type>of()); methodNotImplemented = isValidFragmentMethodSymbol.owner.equals(preferenceActivityTypeSymbol); } catch (FatalError e) { // If isValidFragment method symbol is not found, then we must be compiling against an old SDK // version (< 19) in which isValidFragment is not yet implemented, and neither this class nor // any of its super classes have implemented it. } // If neither this class nor any super class besides PreferenceActivity implements // isValidFragment, and this is not an abstract class, emit warning. if (methodNotImplemented && not(hasModifier(Modifier.ABSTRACT)).matches(tree, state)) { return buildDescription(tree) .setMessage("Class extending PreferenceActivity does not implement isValidFragment.") .build(); } // Check the implementation of isValidFragment. Complain if it always returns true. MethodTree isValidFragmentMethodTree = getMethod(OVERRIDES_IS_VALID_FRAGMENT, tree, state); if (isValidFragmentMethodTree != null) { if (isValidFragmentMethodTree.accept(ALWAYS_RETURNS_TRUE, null)) { return buildDescription(isValidFragmentMethodTree) .setMessage("isValidFragment unconditionally returns true.") .build(); } } return NO_MATCH; } /* * Return the first method tree on the given class tree that matches the given method matcher, * or null if one does not exist. */ private static MethodTree getMethod( Matcher<MethodTree> methodMatcher, ClassTree classTree, VisitorState state) { for (Tree member : classTree.getMembers()) { if (member instanceof MethodTree) { MethodTree memberTree = (MethodTree) member; if (methodMatcher.matches(memberTree, state)) { return memberTree; } } } return null; } /* * A tree scanner for isValidFragment, accepts methods that return true on all code paths. */ private static final TreeScanner<Boolean, Void> ALWAYS_RETURNS_TRUE = new TreeScanner<Boolean, Void>() { @Override public Boolean visitReturn(ReturnTree node, Void unused) { ExpressionTree returnExpression = node.getExpression(); Boolean returnValue = constValue(returnExpression, Boolean.class); return firstNonNull(returnValue, false); } @Override public Boolean reduce(Boolean r1, Boolean r2) { // And together the results of all visits. If any visit was false (return value was false // or not constant), the method implementation is okay. // null value means not a return statement, doesn't change anything, so treat as true. return (r1 == null || r1) && (r2 == null || r2); } }; }