Add checker for PID/UID/user ID arguments.

Many system internals pass around PID, UID and user ID arguments as a
single weakly-typed "int" value, which developers can accidentally
cross in method argument lists, resulting in obscure bugs.

Bug: 155703208
Test: atest error_prone_android_framework_test
Change-Id: I5e4d9b5a533071f94d82dff17faff5d52ae54564
This commit is contained in:
Jeff Sharkey
2020-06-24 12:58:02 -06:00
parent acc7080d7e
commit 439b861677
4 changed files with 263 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.errorprone.BugPattern.SeverityLevel.WARNING;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import java.util.List;
import java.util.regex.Pattern;
/**
* Many system internals pass around PID, UID and user ID arguments as a single
* weakly-typed {@code int} value, which developers can accidentally cross in
* method argument lists, resulting in obscure bugs.
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "AndroidFrameworkUid",
summary = "Verifies that PID, UID and user ID arguments aren't crossed",
severity = WARNING)
public final class UidChecker extends BugChecker implements MethodInvocationTreeMatcher {
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
final List<VarSymbol> vars = ASTHelpers.getSymbol(tree).params();
final List<? extends ExpressionTree> args = tree.getArguments();
for (int i = 0; i < Math.min(vars.size(), args.size()); i++) {
final Flavor varFlavor = getFlavor(vars.get(i));
final Flavor argFlavor = getFlavor(args.get(i));
if (varFlavor == Flavor.UNKNOWN || argFlavor == Flavor.UNKNOWN) {
continue;
}
if (varFlavor != argFlavor) {
return buildDescription(tree).setMessage("Argument #" + (i + 1) + " expected "
+ varFlavor + " but passed " + argFlavor).build();
}
}
return Description.NO_MATCH;
}
private static enum Flavor {
UNKNOWN(null),
PID(Pattern.compile("(^pid$|Pid$)")),
UID(Pattern.compile("(^uid$|Uid$)")),
USER_ID(Pattern.compile("(^userId$|UserId$|^userHandle$|UserHandle$)"));
private Pattern pattern;
private Flavor(Pattern pattern) {
this.pattern = pattern;
}
public boolean matches(CharSequence input) {
return (pattern != null) && pattern.matcher(input).find();
}
}
private static Flavor getFlavor(String name) {
for (Flavor f : Flavor.values()) {
if (f.matches(name)) {
return f;
}
}
return Flavor.UNKNOWN;
}
private static Flavor getFlavor(VarSymbol symbol) {
final String type = symbol.type.toString();
if ("int".equals(type)) {
return getFlavor(symbol.name.toString());
}
return Flavor.UNKNOWN;
}
private static Flavor getFlavor(ExpressionTree tree) {
if (tree instanceof IdentifierTree) {
return getFlavor(((IdentifierTree) tree).getName().toString());
} else if (tree instanceof MemberSelectTree) {
return getFlavor(((MemberSelectTree) tree).getIdentifier().toString());
} else if (tree instanceof MethodInvocationTree) {
return getFlavor(((MethodInvocationTree) tree).getMethodSelect());
}
return Flavor.UNKNOWN;
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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 com.google.errorprone.CompilationTestHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class UidCheckerTest {
private CompilationTestHelper compilationHelper;
@Before
public void setUp() {
compilationHelper = CompilationTestHelper.newInstance(
UidChecker.class, getClass());
}
@Test
public void testTypical() {
compilationHelper
.addSourceLines("Example.java",
"public abstract class Example {",
" abstract void bar(int pid, int uid, int userId);",
" abstract int getUserId();",
" void foo(int pid, int uid, int userId, int unrelated) {",
" bar(0, 0, 0);",
" bar(pid, uid, userId);",
" bar(pid, uid, getUserId());",
" bar(unrelated, unrelated, unrelated);",
" // BUG: Diagnostic contains:",
" bar(uid, pid, userId);",
" // BUG: Diagnostic contains:",
" bar(pid, userId, uid);",
" // BUG: Diagnostic contains:",
" bar(getUserId(), 0, 0);",
" }",
"}")
.doTest();
}
@Test
public void testCallingUid() {
compilationHelper
.addSourceFile("/android/os/Binder.java")
.addSourceFile("/android/os/UserHandle.java")
.addSourceLines("Example.java",
"import android.os.Binder;",
"import android.os.UserHandle;",
"public abstract class Example {",
" int callingUserId;",
" int callingUid;",
" abstract void setCallingUserId(int callingUserId);",
" abstract void setCallingUid(int callingUid);",
" void doUserId(int callingUserId) {",
" setCallingUserId(UserHandle.getUserId(Binder.getCallingUid()));",
" setCallingUserId(this.callingUserId);",
" setCallingUserId(callingUserId);",
" // BUG: Diagnostic contains:",
" setCallingUserId(Binder.getCallingUid());",
" // BUG: Diagnostic contains:",
" setCallingUserId(this.callingUid);",
" // BUG: Diagnostic contains:",
" setCallingUserId(callingUid);",
" }",
" void doUid(int callingUserId) {",
" setCallingUid(Binder.getCallingUid());",
" setCallingUid(this.callingUid);",
" setCallingUid(callingUid);",
" // BUG: Diagnostic contains:",
" setCallingUid(UserHandle.getUserId(Binder.getCallingUid()));",
" // BUG: Diagnostic contains:",
" setCallingUid(this.callingUserId);",
" // BUG: Diagnostic contains:",
" setCallingUid(callingUserId);",
" }",
" void doInner() {",
" // BUG: Diagnostic contains:",
" setCallingUserId(UserHandle.getUserId(callingUserId));",
" }",
"}")
.doTest();
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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 android.os;
public class Binder {
public static int getCallingUid() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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 android.os;
public class UserHandle {
public static int getUserId(int uid) {
throw new UnsupportedOperationException();
}
public static int getAppId(int uid) {
throw new UnsupportedOperationException();
}
public static int getUid(int userId, int appId) {
throw new UnsupportedOperationException();
}
}