Skip to content
Renato Valenzuela edited this page Dec 9, 2022 · 7 revisions

AWS Lambda SnapStart Bug Scanner

Welcome to the aws-lambda-snapstart-java-rules wiki!

Lambda SnapStart speeds up applications by re-using a single initialized snapshot to resume multiple execution environments. As a result, unique content included in the snapshot during initialization is reused across execution environments, and so may no longer remain unique. A class of applications where uniqueness of state is a key consideration is cryptographic software, which assumes that the random numbers are truly random (both random and unpredictable). If content such as a random seed is saved in the snapshot during initialization, it is re-used when multiple execution environments resume and may produce predictable random sequences.

To maintain uniqueness, you must verify before using SnapStart that any unique content previously generated during the initialization now gets generated after that initialization. This includes unique IDs, unique secrets, and entropy used to generate pseudo-randomness.

These are some examples that may lead to scenarios where your code does not maintain uniqueness with SnapStart.

Pseudo-random number generator

Lambda functions that require pseudo-random values can initialize a pseudo-random number generator as below. Lambda SnapStart functions will snapshot the java.util.Random instance which will cause exactly the same sequence of random numbers to be generated in each execution environment that runs this SnapStart function.

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.Random;

public class LambdaWithRandom implements RequestHandler<String, String> {

    private final Random random;

    public LambdaUsingRandom() {
        // This is snapshotted by SnapStart, so it's a bug
        random = new Random(); 
    }

    @Override
    public String handleRequest(String event, Context context) {
        // use random to generate a random number here.
        return "hello world";
    }
}

If this creates an issue for your Lambda application, you can make sure that each execution creates its own sequence of random numbers by using java.security.SecureRandom instead of java.util.Random as shown below.

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.security.SecureRandom;

public class LambdaWithRandom implements RequestHandler<String, String> {

    private final SecureRandom random;

    public LambdaUsingRandom() {
        random = new SecureRandom(); 
    }

    @Override
    public String handleRequest(String event, Context context) {
        // use random to generate a random number here.
        return "hello world";
    }
}

Unique CloudWatch log stream name

A common use case for Lambda functions is to create a unique identifier that’s used by multiple invocations in the same execution environment such as a CloudWatch log stream name. Since SnapStart execution environments can start from the same snapshot, a random value that initializes a Lambda handler class member gets copied to multiple execution environments.

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.UUID;

public class LambdaLogger implements RequestHandler<String, String> {

    private UUID uniqueLogId;

    public LambdaUsingRandom() {
        // This is snapshotted by SnapStart, so it's a bug
        uniqueLogId = UUID.randomUUID(); 
    }

    @Override
    public String handleRequest(String event, Context context) {
        // get or create a cloudwatch log stream with uniqueLogId
        return "hello world";
    }
}

In order to avoid these bugs we recommend writing your Lambda function’s code like below:

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.UUID;

public class LambdaLogger implements RequestHandler<String, String> {

    private UUID uniqueLogId;

    @Override
    public String handleRequest(String event, Context context) {
        if (uniqueLogId == null) {
           uniqueLogId = UUID.randomUUID();
        }
        // get or create a cloudwatch log stream with uniqueLogId
        return "hello world";
    }
}

Temporal values

Lambda functions may capture execution environment creation time for various purposes. In this case the time value that initializes a Lambda function’s class member will have the time when snapshot is created.

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class LambdaUsingTimestamp implements RequestHandler<String, String> {

    private long envCreationTime;

    public LambdaUsingUUID() {
       // This is snapshotted by SnapStart, so it's a bug
       envCreationTime = System.currentTimeMillis();
    }

    @Override
    public String handleRequest(String event, Context context) {
        // use envCreationTime to calculate the time difference
        return "hello world";
    }
}

You can eliminate this timing error by initializing the timestamp during snapshot resume as below.

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.crac.Resource;

public class LambdaUsingTimestamp implements RequestHandler<String, String>, Resource {

    private long envCreationTime;

    @Override
    public void afterRestore(org.crac.Context<? extends Resource> context) {
        envCreationTime = System.currentTimeMillis();
    }
    
    @Override
    public String handleRequest(String event, Context context) {
        // use envCreationTime to calculate the time difference
        return "hello world";
    }
}