Skip to content

Commit 1458864

Browse files
authored
feat: Initial implementation of RNG detector (aws#5)
feat: Initial implementation of RNG detector
1 parent 2ff66d4 commit 1458864

25 files changed

+894
-141
lines changed

README.md

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,78 @@
1-
## My Project
1+
# AWS Lambda SnapStart Bug Scanner
22

3-
TODO: Fill this README out!
3+
SnapStart Bug Scanner is the [SpotBugs](https://spotbugs.github.io/) plugin for helping AWS Lambda customers inspect
4+
their functions against potential bugs unique to AWS Lambda SnapStart environment.
45

5-
Be sure to:
6+
## How to use
67

7-
* Change the title in this README
8-
* Edit your repository description on GitHub
8+
Following sections explain how to enable this plugin in your Gradle and Maven projects.
9+
10+
### Gradle Builds
11+
12+
After SpotBugs is [enabled in the Gradle project](https://spotbugs.readthedocs.io/en/latest/gradle.html) declaring a dependency on SnapStart bug scanner is sufficient.
13+
14+
Example:
15+
16+
```kotlin
17+
plugins {
18+
id("com.github.spotbugs") version "4.7.1"
19+
}
20+
21+
spotbugs {
22+
ignoreFailures.set(false)
23+
showStackTraces.set(true)
24+
}
25+
26+
dependencies {
27+
spotbugs("com.github.spotbugs:spotbugs:4.7.1")
28+
spotbugsPlugins("software.amazon.lambda.snapstart:aws-lambda-snapstart-java-rules:0.1")
29+
}
30+
```
31+
32+
### Maven Builds
33+
34+
After SpotBugs is [enabled in the Maven project](https://spotbugs.readthedocs.io/en/latest/maven.html) declaring a dependency on SnapStart bug scanner is sufficient.
35+
36+
Example:
37+
38+
```xml
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>com.github.spotbugs</groupId>
43+
<artifactId>spotbugs-maven-plugin</artifactId>
44+
<version>${spotbugs.version}</version>
45+
<configuration>
46+
<effort>Max</effort>
47+
<threshold>medium</threshold>
48+
<failOnError>true</failOnError>
49+
<plugins>
50+
<plugin>
51+
<groupId>software.amazon.lambda.snapstart</groupId>
52+
<artifactId>aws-lambda-snapstart-java-rules</artifactId>
53+
<version>0.1</version>
54+
</plugin>
55+
</plugins>
56+
</configuration>
57+
</plugin>
58+
</plugins>
59+
</build>
60+
```
61+
62+
## Bug Descriptions
63+
64+
### SNAP_START: Detected handler state that is potentially not resilient to VM snapshot and restore operations. (AWS_LAMBDA_SNAP_START_BUG)
65+
66+
Our analysis shows that AWS Lambda handler class initialization creates state that might have adverse effects
67+
on the output of the function when it uses SnapStart. Lambda functions that use SnapStart are
68+
snapshotted at their initialized state and all execution environments created afterwards share the same initial
69+
state. This means that if the Lambda function relies on state that is not resilient to snapshot and restore
70+
operations, it might manifest an unexpected behavior by using SnapStart.
71+
72+
Note that there are countless ways of initializing a Lambda function handler such that it’s not compatible
73+
with SnapStart. This tool helps you as much as possible but please use your own judgement to and refer
74+
to [the documentation](https://github.com/aws/aws-lambda-snapstart-java-rules/wiki) for
75+
understanding how to avoid making your Lambda function SnapStart incompatible.
976

1077
## Security
1178

pom.xml

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,27 @@
44

55
<groupId>software.amazon.lambda.snapstart</groupId>
66
<artifactId>aws-lambda-snapstart-java-rules</artifactId>
7-
<version>0.1-SNAPSHOT</version>
7+
<version>0.1</version>
8+
<name>${project.groupId}:${project.artifactId}</name>
89
<description>AWS Lambda SnapStart SpotBugs Rules</description>
10+
<url>https://github.com/aws/aws-lambda-snapstart-java-rules</url>
11+
<issueManagement>
12+
<system>GitHub Issues</system>
13+
<url>https://github.com/aws/aws-lambda-snapstart-java-rules/issues</url>
14+
</issueManagement>
15+
16+
<scm>
17+
<connection>scm:git:https://github.com/aws/aws-lambda-snapstart-java-rules.git</connection>
18+
<url>https://github.com/aws/aws-lambda-snapstart-java-rules.git</url>
19+
</scm>
20+
21+
<developers>
22+
<developer>
23+
<name>AWS Lambda Tooling team</name>
24+
<organization>Amazon Web Services</organization>
25+
<organizationUrl>https://aws.amazon.com/</organizationUrl>
26+
</developer>
27+
</developers>
928

1029
<properties>
1130
<maven.compiler.target>1.8</maven.compiler.target>
@@ -14,6 +33,13 @@
1433
<spotBugsVersion>4.7.2</spotBugsVersion>
1534
</properties>
1635

36+
<distributionManagement>
37+
<snapshotRepository>
38+
<id>ossrh</id>
39+
<url>https://aws.oss.sonatype.org/content/repositories/snapshots</url>
40+
</snapshotRepository>
41+
</distributionManagement>
42+
1743
<licenses>
1844
<license>
1945
<name>The Apache Software License, Version 2.0</name>
@@ -60,6 +86,12 @@
6086
<version>1.2.1</version>
6187
<scope>test</scope>
6288
</dependency>
89+
<dependency>
90+
<groupId>io.github.crac</groupId>
91+
<artifactId>org-crac</artifactId>
92+
<version>0.1.3</version>
93+
<scope>test</scope>
94+
</dependency>
6395
</dependencies>
6496

6597
<build>
@@ -112,4 +144,34 @@
112144
</plugin>
113145
</plugins>
114146
</build>
147+
148+
<profiles>
149+
<profile>
150+
<id>release-sign-artifacts</id>
151+
<activation>
152+
<property>
153+
<name>performRelease</name>
154+
<value>true</value>
155+
</property>
156+
</activation>
157+
<build>
158+
<plugins>
159+
<plugin>
160+
<groupId>org.apache.maven.plugins</groupId>
161+
<artifactId>maven-gpg-plugin</artifactId>
162+
<version>3.0.1</version>
163+
<executions>
164+
<execution>
165+
<id>sign-artifacts</id>
166+
<phase>verify</phase>
167+
<goals>
168+
<goal>sign</goal>
169+
</goals>
170+
</execution>
171+
</executions>
172+
</plugin>
173+
</plugins>
174+
</build>
175+
</profile>
176+
</profiles>
115177
</project>
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.amazon.lambda.snapstart;
5+
6+
import edu.umd.cs.findbugs.BugAccumulator;
7+
import edu.umd.cs.findbugs.BugReporter;
8+
import edu.umd.cs.findbugs.Detector;
9+
import edu.umd.cs.findbugs.ba.AnalysisContext;
10+
import edu.umd.cs.findbugs.ba.CFGBuilderException;
11+
import edu.umd.cs.findbugs.ba.ClassContext;
12+
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
13+
import edu.umd.cs.findbugs.ba.Location;
14+
import edu.umd.cs.findbugs.ba.MissingClassException;
15+
import edu.umd.cs.findbugs.ba.SignatureConverter;
16+
import edu.umd.cs.findbugs.ba.ca.Call;
17+
import edu.umd.cs.findbugs.ba.ca.CallList;
18+
import edu.umd.cs.findbugs.ba.ca.CallListDataflow;
19+
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
20+
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
21+
import edu.umd.cs.findbugs.classfile.Global;
22+
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
23+
import edu.umd.cs.findbugs.log.Profiler;
24+
import java.util.HashMap;
25+
import java.util.Iterator;
26+
import java.util.List;
27+
import java.util.Map;
28+
import org.apache.bcel.Const;
29+
import org.apache.bcel.classfile.Method;
30+
import org.apache.bcel.generic.ConstantPoolGen;
31+
import org.apache.bcel.generic.Instruction;
32+
import org.apache.bcel.generic.InvokeInstruction;
33+
import org.apache.bcel.generic.ReturnInstruction;
34+
35+
/**
36+
* This class is a detector implementation which runs in the first pass of analysis. With a very simplistic assumption
37+
* this identifies whether a method might return a pseudo-random value purely based on the fact that it calls a method
38+
* that's already known to return a pseudo-random value. A better approach is doing proper dataflow analysis to
39+
* understand whether the pseudo-random value makes it to the return instruction.
40+
*/
41+
public class BuildRandomReturningMethodsDatabase implements Detector {
42+
43+
private final BugReporter bugReporter;
44+
private final BugAccumulator bugAccumulator;
45+
private final ReturnValueRandomnessPropertyDatabase database;
46+
private final ByteCodeIntrospector introspector;
47+
48+
// Transient state
49+
private ClassContext classContext;
50+
private Method method;
51+
private MethodDescriptor methodDescriptor;
52+
private CallListDataflow callListDataflow;
53+
private Map<Call, MethodDescriptor> callMethodDescriptorMap;
54+
private CallGraph callGraph;
55+
56+
public BuildRandomReturningMethodsDatabase(BugReporter reporter) {
57+
this.bugReporter = reporter;
58+
this.bugAccumulator = new BugAccumulator(reporter);
59+
database = new ReturnValueRandomnessPropertyDatabase();
60+
Global.getAnalysisCache().eagerlyPutDatabase(ReturnValueRandomnessPropertyDatabase.class, database);
61+
callGraph = new CallGraph();
62+
callMethodDescriptorMap = new HashMap<>();
63+
introspector = new ByteCodeIntrospector();
64+
}
65+
66+
@Override
67+
public void visitClassContext(ClassContext classContext) {
68+
this.classContext = classContext;
69+
70+
String currentMethod = null;
71+
List<Method> methodsInCallOrder = classContext.getMethodsInCallOrder();
72+
for (Method method : methodsInCallOrder) {
73+
try {
74+
if (method.isAbstract() || method.isNative() || method.getCode() == null) {
75+
continue;
76+
}
77+
currentMethod = SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method);
78+
analyzeMethod(method);
79+
} catch (MissingClassException e) {
80+
bugReporter.reportMissingClass(e.getClassNotFoundException());
81+
} catch (DataflowAnalysisException | CFGBuilderException e) {
82+
bugReporter.logError("While analyzing " + currentMethod + ": BuildRandomReturningMethodsDatabase caught an exception", e);
83+
}
84+
bugAccumulator.reportAccumulatedBugs();
85+
}
86+
}
87+
88+
private void analyzeMethod(Method method) throws DataflowAnalysisException, CFGBuilderException {
89+
if ((method.getAccessFlags() & Const.ACC_BRIDGE) != 0) {
90+
return;
91+
}
92+
93+
this.method = method;
94+
this.methodDescriptor = DescriptorFactory.instance().getMethodDescriptor(classContext.getJavaClass(), method);
95+
callListDataflow = classContext.getCallListDataflow(method);
96+
97+
checkInvokeAndReturnInstructions();
98+
}
99+
100+
private void checkInvokeAndReturnInstructions() {
101+
Profiler profiler = Global.getAnalysisCache().getProfiler();
102+
profiler.start(BuildRandomReturningMethodsDatabase.class);
103+
try {
104+
for (Iterator<Location> i = classContext.getCFG(method).locationIterator(); i.hasNext();) {
105+
Location location = i.next();
106+
Instruction ins = location.getHandle().getInstruction();
107+
108+
if (ins instanceof ReturnInstruction) {
109+
examineReturnInstruction(location);
110+
} else if (ins instanceof InvokeInstruction) {
111+
examineInvokeInstruction((InvokeInstruction) ins);
112+
}
113+
}
114+
} catch (CheckedAnalysisException e) {
115+
AnalysisContext.logError("error:", e);
116+
} finally {
117+
profiler.end(BuildRandomReturningMethodsDatabase.class);
118+
}
119+
}
120+
121+
private void examineInvokeInstruction(InvokeInstruction inv) {
122+
ConstantPoolGen cpg = classContext.getConstantPoolGen();
123+
MethodDescriptor md = new MethodDescriptor(inv, classContext.getConstantPoolGen());
124+
Call call = new Call(inv.getClassName(cpg), inv.getName(cpg), inv.getSignature(cpg));
125+
callMethodDescriptorMap.put(call, md);
126+
}
127+
128+
private void examineReturnInstruction(Location location) throws DataflowAnalysisException {
129+
// We have a crude assumption here that is any method call effects the return value of the caller method.
130+
CallList callList = callListDataflow.getFactAtLocation(location);
131+
if (!callList.isValid()) {
132+
return;
133+
}
134+
135+
Iterator<Call> callIterator = callList.callIterator();
136+
while (callIterator.hasNext()) {
137+
Call call = callIterator.next();
138+
MethodDescriptor caller = methodDescriptor;
139+
MethodDescriptor called = callMethodDescriptorMap.get(call);
140+
if (called != null) {
141+
recordCalledMethod(caller, called);
142+
}
143+
}
144+
}
145+
146+
private void recordCalledMethod(MethodDescriptor caller, MethodDescriptor called) {
147+
Boolean returnsRandom = database.getProperty(called);
148+
if (returnsRandom != null && returnsRandom) {
149+
callGraph.flushCallersToDatabase(caller, database, true);
150+
} else {
151+
if (callGraph.isInCallGraph(caller) || isLambdaHandlerInitMethod()) {
152+
// Call chain from Lambda event handler checks out
153+
callGraph.record(caller, called);
154+
}
155+
}
156+
}
157+
158+
private boolean isLambdaHandlerInitMethod() {
159+
if (introspector.isLambdaHandler(classContext.getXClass())) {
160+
return Const.STATIC_INITIALIZER_NAME.equals(method.getName())
161+
|| Const.CONSTRUCTOR_NAME.equals(method.getName());
162+
}
163+
return false;
164+
}
165+
166+
@Override
167+
public void report() {
168+
// this is a non-reporting detector
169+
}
170+
}

0 commit comments

Comments
 (0)