Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically load ACCP with AWS-LC using RPath #224

Merged
merged 16 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions csrc/loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
// 0x1010107f == v1.1.1g release
#define LIBCRYPTO_MAJOR_MINOR_VERSION_MASK 0xFFF00000

#define LIBCRYPTO_EXACT_VERSION_MATCH 0
#define LIBCRYPTO_FUZZY_VERSION_MATCH 1
alexw91 marked this conversation as resolved.
Show resolved Hide resolved

static char accp_loader_exception_msg[256] = {0};
alexw91 marked this conversation as resolved.
Show resolved Hide resolved

using namespace AmazonCorrettoCryptoProvider;

namespace {
Expand Down Expand Up @@ -65,20 +70,28 @@ JNIEXPORT jstring JNICALL Java_com_amazon_corretto_crypto_provider_Loader_getNat

}

JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_Loader_validateLibcryptoExactVersionMatch(JNIEnv* pEnv, jclass)
{
char msg_buffer[256] = {0};
void accpValidateLibcryptoVersion(bool fuzzyMatch) {
unsigned long libcrypto_compiletime_version = OPENSSL_VERSION_NUMBER;
unsigned long libcrypto_runtime_version = OpenSSL_version_num();

try {
const unsigned long libcrypto_compiletime_version = OPENSSL_VERSION_NUMBER;
const unsigned long libcrypto_runtime_version = OpenSSL_version_num();
if (fuzzyMatch) {
libcrypto_compiletime_version &= LIBCRYPTO_MAJOR_MINOR_VERSION_MASK;
libcrypto_runtime_version &= LIBCRYPTO_MAJOR_MINOR_VERSION_MASK;
}

if (libcrypto_compiletime_version != libcrypto_runtime_version) {
snprintf(msg_buffer, sizeof(msg_buffer), "Runtime libcrypto version does not match compile-time version. "
"Expected: 0x%08lX , Actual: 0x%08lX", libcrypto_compiletime_version, libcrypto_runtime_version);
throw java_ex(EX_RUNTIME_CRYPTO, msg_buffer);
}
if (libcrypto_compiletime_version != libcrypto_runtime_version) {
memset(accp_loader_exception_msg, 0, sizeof(accp_loader_exception_msg));
snprintf(accp_loader_exception_msg, sizeof(accp_loader_exception_msg),
"Runtime libcrypto version does not match compile-time version. Expected: 0x%08lX , Actual: 0x%08lX",
libcrypto_compiletime_version, libcrypto_runtime_version);
throw java_ex(EX_RUNTIME_CRYPTO, accp_loader_exception_msg);
}
}

JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_Loader_validateLibcryptoExactVersionMatch(JNIEnv* pEnv, jclass)
alexw91 marked this conversation as resolved.
Show resolved Hide resolved
{
try {
accpValidateLibcryptoVersion(LIBCRYPTO_EXACT_VERSION_MATCH);
return JNI_TRUE;
} catch (java_ex &ex) {
ex.throw_to_java(pEnv);
Expand All @@ -89,18 +102,8 @@ JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_Loader_valid

JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_Loader_validateLibcryptoFuzzyVersionMatch(JNIEnv* pEnv, jclass)
alexw91 marked this conversation as resolved.
Show resolved Hide resolved
{
char msg_buffer[256] = {0};

try {
const unsigned long libcrypto_compiletime_version = (OPENSSL_VERSION_NUMBER & LIBCRYPTO_MAJOR_MINOR_VERSION_MASK);
const unsigned long libcrypto_runtime_version = (OpenSSL_version_num() & LIBCRYPTO_MAJOR_MINOR_VERSION_MASK);

if (libcrypto_compiletime_version != libcrypto_runtime_version) {
snprintf(msg_buffer, sizeof(msg_buffer), "Runtime libcrypto version does not match compile-time version. "
"Expected: 0x%08lX , Actual: 0x%08lX", libcrypto_compiletime_version, libcrypto_runtime_version);
throw java_ex(EX_RUNTIME_CRYPTO, msg_buffer);
}

accpValidateLibcryptoVersion(LIBCRYPTO_FUZZY_VERSION_MATCH);
return JNI_TRUE;
} catch (java_ex &ex) {
ex.throw_to_java(pEnv);
Expand Down
47 changes: 28 additions & 19 deletions src/com/amazon/corretto/crypto/provider/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ private static void tryLoadLibraryFromJar() throws IOException {
* Any shared libraries that are not present in Java's LOCAL object group (Group #1), will be loaded into a
* new child LOCAL object group, lower in the object group hierarchy. This can be done multiple times for
* multiple different JNI libraries. Note that since both Groups #2 and #3 have Group #1 as a parent, their
* library symbols will not conflict with each another since each group only see's symbols from their own
* library symbols will not conflict with each other since each group only see's symbols from their own
* parents in the hierarchy above them. This means it is possible for multiple different JNI libraries to be
* loaded at runtime that use different libcrypto implementations so long as those JNI libraries configure
* their RPath values correctly, and are compatible with any libraries that have already been loaded above
Expand Down Expand Up @@ -363,12 +363,15 @@ private static void maybeDeletePrivateTempDir(final Path tmpDirectory) {
private static Path writeJarResourceToTemporaryFile(Path tempDirectory, final String resourceFileName) throws IOException {
final Path tempResourceFilePath = tempDirectory.resolve(resourceFileName);

// Create a new temporary file. If one already exists this will throw FileAlreadyExistsException
// Create a new temporary file with ourselves as the owner. We need to ensure that we create the file and set
// permissions atomically so that an adversary cannot put a symlink or file here which would cause us to write
// (or read) to an arbitrary attacker controlled location. If this file already exists at this location, then
// a FileAlreadyExistsException will be thrown.
Files.createFile(tempResourceFilePath, SELF_OWNER_FILE_PERMISSIONS);

try (InputStream is = Loader.class.getResourceAsStream(resourceFileName);
OutputStream os = Files.newOutputStream(tempResourceFilePath, StandardOpenOption.CREATE,
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
OutputStream os = Files.newOutputStream(tempResourceFilePath, StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
final byte[] buffer = new byte[16 * 1024];
int read = is.read(buffer);
while (read >= 0) {
Expand All @@ -379,7 +382,9 @@ private static Path writeJarResourceToTemporaryFile(Path tempDirectory, final St
}

// Ensure we delete any temp files on exit
tempResourceFilePath.toFile().deleteOnExit();
if (!DebugFlag.PRESERVE_NATIVE_LIBRARIES.isEnabled()) {
tempResourceFilePath.toFile().deleteOnExit();
}
return tempResourceFilePath;
}

Expand Down Expand Up @@ -419,30 +424,34 @@ private static synchronized Path createPrivateTmpDir(final String prefix) throws
throw new AssertionError("java.io.tmpdir is not valid: " + systemTempDir);
}

final byte[] rndBytes = bootstrapRng(Long.BYTES); // Default java tmp files use this much entropy
final StringBuilder privateTempDir = new StringBuilder(prefix);

for (byte b : rndBytes) {
// We convert to an unsigned integer first to avoid sign-bit extension when converting to hex.
String hexByte = Integer.toHexString(Byte.toUnsignedInt(b));
if (hexByte.length() == 1) {
privateTempDir.append('0');
}
privateTempDir.append(hexByte);
}

final Path privateDirFullPath = systemTempDir.resolve(privateTempDir.toString());
final int RETRY_LIMIT = 10;
int attempt = 0;

while(attempt < RETRY_LIMIT) {
attempt++;
try {
final byte[] rndBytes = bootstrapRng(Long.BYTES); // Default java tmp files use this much entropy
final StringBuilder privateTempDir = new StringBuilder(prefix);

for (byte b : rndBytes) {
// We convert to an unsigned integer first to avoid sign-bit extension when converting to hex.
String hexByte = Integer.toHexString(Byte.toUnsignedInt(b));
if (hexByte.length() == 1) {
privateTempDir.append('0');
}
privateTempDir.append(hexByte);
}

final Path privateDirFullPath = systemTempDir.resolve(privateTempDir.toString());

final Path result = Files.createDirectory(privateDirFullPath, SELF_OWNER_FILE_PERMISSIONS);
if (DebugFlag.VERBOSELOGS.isEnabled()) {
LOG.log(Level.FINE, "Created temporary library directory");
}
result.toFile().deleteOnExit();

if (!DebugFlag.PRESERVE_NATIVE_LIBRARIES.isEnabled()) {
result.toFile().deleteOnExit();
}
return result;
} catch (final FileAlreadyExistsException ex) {
// We ignore and retry this exception
Expand Down