Description
During a comprehensive code review of the JNI native layer, multiple critical code quality and safety issues were identified in LibrustzcashJNIImpl.cpp and LibsodiumJNIImpl.cpp. These issues could lead to:
- Memory leaks - Improper JNI array release on error paths
- Undefined behavior - Null pointer dereference due to incorrect check ordering
- Build failures - Unused headers causing compilation errors on macOS
- Poor maintainability - C-style casts and inconsistent code patterns
Environment
Network: N/A (native library code)
Software Versions
OS: macOS 24.6.0, Linux
JVM: JDK 8+
Git Commit: 834f0da
Version: 1.0.0
Code: zksnark-java-sdk
Configuration
- CMake 3.10.2+
- Rust (cargo) 1.50.0+
- Native library: libzksnarkjni.jnilib / libzksnarkjni.so
Expected Behavior
- JNI native code should properly release array elements on all error paths
- Null pointer checks should execute BEFORE function calls that dereference pointers
- C++ code should use modern C++ style (nullptr, reinterpret_cast, etc.)
- Build should succeed on all supported platforms (macOS x86_64/aarch64, Linux)
Actual Behavior
- Memory leaks on error paths - When
GetByteArrayElements returns null for only some arrays, previously acquired arrays are not released
- Null check after use - In
librustzcashToScalar, the null check executes after the function call, so it never protects against null dereference
- Build failures on macOS - Unused
#include <iostream> causes 'iostream' file not found error
- Compilation errors with modern JDKs - Missing
const_cast when releasing const jbyte* arrays
Frequency
Steps to Reproduce
Issue 1: Null Check After Function Call (librustzcashToScalar)
// Line 837-845 (original code)
const unsigned char * i = (const unsigned char *) env->GetByteArrayElements(input, nullptr);
unsigned char * r = (unsigned char *) env->GetByteArrayElements(result, nullptr);
librustzcash_to_scalar(i, r); // Called FIRST
if (r == NULL || i == NULL) { // Check AFTER - never executes!
return;
}
Reproduction: Trigger GetByteArrayElements to return null (e.g., invalid array), the function will crash.
Issue 2: Memory Leak on Error Path
// Multiple functions (original code)
const unsigned char * s = reinterpret_cast<const unsigned char *>(env->GetByteArrayElements(seed, nullptr));
unsigned char * x = reinterpret_cast<unsigned char *>(env->GetByteArrayElements(xsk_master, nullptr));
if (s == NULL || x == NULL) {
return; // Leaks s if x is null, leaks x if s is null!
}
Reproduction: Pass one invalid array and one valid array - the valid array's native reference leaks.
Issue 3: Build Failure on macOS
cd cpp && mkdir build && cd build
cmake ..
make -j4
# Error: 'iostream' file not found
Additional Context
Possible Solution
The fix involves:
- Replace unused headers:
<iostream> → <cstring> and <new>
- Fix null check ordering: Move null checks BEFORE function calls
- Add proper error cleanup: Release acquired arrays on all error paths with
JNI_ABORT
- Use modern C++ casts: Replace C-style casts with
reinterpret_cast, static_cast, const_cast
- Use
nullptr instead of NULL
- Add
const_cast for JNI array release: Required when releasing const jbyte* with JNI_ABORT
- Use
new (std::nothrow): Prevent unhandled C++ exceptions in JNI code
Description
During a comprehensive code review of the JNI native layer, multiple critical code quality and safety issues were identified in
LibrustzcashJNIImpl.cppandLibsodiumJNIImpl.cpp. These issues could lead to:Environment
Network: N/A (native library code)
Software Versions
Configuration
Expected Behavior
Actual Behavior
GetByteArrayElementsreturns null for only some arrays, previously acquired arrays are not releasedlibrustzcashToScalar, the null check executes after the function call, so it never protects against null dereference#include <iostream>causes'iostream' file not founderrorconst_castwhen releasingconst jbyte*arraysFrequency
Steps to Reproduce
Issue 1: Null Check After Function Call (librustzcashToScalar)
Reproduction: Trigger
GetByteArrayElementsto return null (e.g., invalid array), the function will crash.Issue 2: Memory Leak on Error Path
Reproduction: Pass one invalid array and one valid array - the valid array's native reference leaks.
Issue 3: Build Failure on macOS
Additional Context
Possible Solution
The fix involves:
<iostream>→<cstring>and<new>JNI_ABORTreinterpret_cast,static_cast,const_castnullptrinstead ofNULLconst_castfor JNI array release: Required when releasingconst jbyte*withJNI_ABORTnew (std::nothrow): Prevent unhandled C++ exceptions in JNI code