Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
3 changes: 1 addition & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: 1.24.0
check-latest: true
- name: Test Cross-Language
env:
TEST_DB_NAME: ${{ env.MYSQL_DATABASE }}
Expand All @@ -62,7 +61,7 @@ jobs:
ASHERAH_KMS_MODE: static
run: scripts/integration-test.sh
- name: Publish (dry-run)
run: npm publish --dry-run
run: npm publish --dry-run --tag dev
test-multi-arch:
runs-on: ubuntu-latest
strategy:
Expand Down
167 changes: 167 additions & 0 deletions REMAINING_ISSUES_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Remaining Issues Report - Asherah Node

After implementing fixes across multiple branches:
- `add-lto-optimization` - Link-Time Optimization
- `add-secure-memory-wiping` - Secure memory wiping for sensitive data
- `fix-exception-handling` - Exception handling in async workers
- `add-jsdocs` - Comprehensive JSDoc documentation
- `fix-empty-partition-validation` - Empty partition ID validation
- `optimize-partition-validation` - String length optimization
- `add-move-optimizations` - Eliminate buffer copies with move semantics

## Critical Issues (3 total)

### 1. Integer Overflow in Size Calculations
**Severity**: High
**Location**: `src/asherah.cc:437`
```cpp
auto new_size = (size_t)item_size.Int32Value();
```
**Issue**: Negative int32 values will wrap to huge size_t values
**Fix**: Add bounds checking before cast

### 2. Potential Integer Overflow in EstimateAsherahOutputSize
**Severity**: High
**Location**: `src/asherah.cc:753-756`
```cpp
size_t est_data_byte_len =
size_t(double(data_byte_len + est_encryption_overhead) *
base64_overhead) + 1;
```
**Issue**: Large inputs could overflow when multiplied by 1.34
**Fix**: Add SIZE_MAX/2 check before calculation

### 3. Buffer Underflow in AllocationSizeToMaxDataSize
**Severity**: High
**Location**: `src/cobhan_buffer.h:96-104`
```cpp
size_t data_len_bytes = allocation_len_bytes - cobhan_header_size_bytes -
canary_size_bytes - safety_padding_bytes;
```
**Issue**: No underflow check if allocation_len_bytes is too small
**Fix**: Check allocation_len_bytes >= total_overhead before subtraction

## Medium Priority Issues

### 5. Missing Error Context in Cobhan Buffer Errors
**Severity**: Low
**Location**: Multiple locations in `cobhan_buffer.h`
**Issue**: Generic error messages don't indicate which operation failed
**Fix**: Add context to error messages (e.g., "CobhanBuffer constructor: allocation size too small")

### 6. Inconsistent Error Handling Pattern
**Severity**: Low
**Location**: `cobhan_buffer_napi.h:105-106`
```cpp
napi_throw_error(env, nullptr,
"Failed to create Napi::String from CobhanBuffer");
```
**Issue**: Uses `napi_throw_error` directly instead of `NapiUtils::ThrowException`
**Fix**: Use consistent error throwing mechanism

### 7. Missing Validation for Config JSON Properties
**Severity**: Low
**Location**: `src/asherah.cc:BeginSetupAsherah`
**Issue**: No validation that required config properties exist
**Fix**: Add checks for required properties before use

### 8. Uninitialized partition_id_length in Some Code Paths
**Severity**: Low
**Location**: `src/asherah.cc` (DecryptSync methods without stack allocation)
**Issue**: Variable used before initialization in some paths
**Fix**: Ensure consistent initialization

## Performance Optimization Opportunities

### 9. Redundant Buffer Allocations
**Severity**: Performance
**Location**: Throughout async operations
**Issue**: Buffers are allocated and copied multiple times
**Opportunity**: Consider buffer pooling or reuse strategy

### 10. ~~Missing Move Semantics in Some Cases~~ ✅ FIXED
**Severity**: Performance
**Location**: `CobhanBufferNapi` usage patterns
**Issue**: Some operations copy when they could move
**Fix**: Implemented in `add-move-optimizations` branch - worker constructors now use rvalue references and std::move

### 11. Inefficient String Conversions
**Severity**: Performance
**Location**: JSON parsing/stringification
**Issue**: Multiple round trips between C++ and JavaScript for JSON
**Opportunity**: Consider native JSON parsing library

## Testing Gaps

### 12. No Tests for Maximum Size Limits
**Severity**: Testing
**Issue**: No tests verify behavior at size boundaries
**Fix**: Add tests for max buffer sizes, partition ID lengths

### 13. No Stress Tests
**Severity**: Testing
**Issue**: No tests for concurrent operations or memory pressure
**Fix**: Add stress tests for async operations

### 14. Missing Negative Test Cases
**Severity**: Testing
**Issue**: Limited testing of error paths
**Fix**: Add tests for all error conditions

## API Design Issues

### 15. Inconsistent Parameter Validation
**Severity**: API Design
**Location**: Various public methods
**Issue**: Some methods validate parameters, others don't
**Fix**: Add consistent parameter validation at API boundaries

### 16. No Way to Configure Buffer Allocation Strategy
**Severity**: API Design
**Issue**: Stack allocation size is global, not per-operation
**Fix**: Consider per-operation allocation hints

## Documentation Issues

### 17. Missing Architecture Documentation
**Severity**: Documentation
**Issue**: No high-level architecture docs explaining Cobhan protocol
**Fix**: Add architecture.md explaining the design

### 18. No Performance Tuning Guide
**Severity**: Documentation
**Issue**: No guidance on optimal buffer sizes or allocation strategies
**Fix**: Add performance tuning documentation

## Build System Issues

### 19. No Debug Symbol Generation in Release Builds
**Severity**: Build
**Location**: `binding.gyp`
**Issue**: Hard to debug production issues
**Fix**: Add symbol generation with separate symbol files

### 20. Missing Compiler Security Flags
**Severity**: Build/Security
**Issue**: Not using -fstack-protector-strong, -D_FORTIFY_SOURCE=2
**Fix**: Add security hardening flags

## Summary

After implementing fixes across 7 branches, I've identified 18 remaining issues (was 19):

### Critical Issues (3)
1. Integer overflow in SetMaxStackAllocItemSize
2. Integer overflow in EstimateAsherahOutputSize
3. Buffer underflow in AllocationSizeToMaxDataSize

### Fixed Issues
- ✅ Link-Time Optimization (LTO)
- ✅ Secure memory wiping for sensitive data
- ✅ Exception handling in async workers
- ✅ Comprehensive JSDoc documentation
- ✅ Empty partition ID validation
- ✅ String length optimization to reduce N-API crossings
- ✅ Move semantics for async operations (eliminated buffer copies)

The codebase is well-structured with good memory safety practices. Most remaining issues are straightforward to fix without architectural changes.
1 change: 1 addition & 0 deletions scripts/integration-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ GO_DIR="${ORIG_DIR}/integration/go"
mkdir -p "${GO_DIR}/bin"
export GOPATH=${GO_DIR}
export GOBIN=${GO_DIR}/bin
export GOTOOLCHAIN=local
PATH=$GOPATH/bin:$GOROOT/bin:$PATH

### Encrypt with Go
Expand Down
67 changes: 45 additions & 22 deletions src/asherah.cc
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,14 @@ class Asherah : public Napi::Addon<Asherah> {
try {
Napi::String partition_id_string;
Napi::Value input_value;
size_t partition_id_length;

BeginEncryptToJson(env, __func__, info, partition_id_string, input_value);
BeginEncryptToJson(env, __func__, info, partition_id_string, input_value, partition_id_length);

#ifdef USE_SCOPED_ALLOCATE_BUFFER
char *partition_id_cbuffer;
size_t partition_id_cbuffer_size =
CobhanBufferNapi::StringToAllocationSize(env, partition_id_string);
CobhanBufferNapi::StringToAllocationSize(env, partition_id_string, partition_id_length);
SCOPED_ALLOCATE_BUFFER(partition_id_cbuffer, partition_id_cbuffer_size,
maximum_stack_alloc_size, __func__);

Expand All @@ -173,11 +174,12 @@ class Asherah : public Napi::Addon<Asherah> {

CobhanBufferNapi partition_id(env, partition_id_string,
partition_id_cbuffer,
partition_id_cbuffer_size);
partition_id_cbuffer_size,
partition_id_length);
CobhanBufferNapi input(env, input_value, input_cbuffer,
input_cbuffer_size);
#else
CobhanBufferNapi partition_id(env, partition_id_string);
CobhanBufferNapi partition_id(env, partition_id_string, partition_id_length);
CobhanBufferNapi input(env, input_value);
#endif

Expand Down Expand Up @@ -218,9 +220,10 @@ class Asherah : public Napi::Addon<Asherah> {
try {
Napi::String partition_id_string;
Napi::Value input_value;
BeginEncryptToJson(env, __func__, info, partition_id_string, input_value);
size_t partition_id_length;
BeginEncryptToJson(env, __func__, info, partition_id_string, input_value, partition_id_length);

CobhanBufferNapi partition_id(env, partition_id_string);
CobhanBufferNapi partition_id(env, partition_id_string, partition_id_length);
CobhanBufferNapi input(env, input_value);

size_t partition_id_data_len_bytes = partition_id.get_data_len_bytes();
Expand Down Expand Up @@ -250,14 +253,15 @@ class Asherah : public Napi::Addon<Asherah> {
try {
Napi::String partition_id_string;
Napi::Value input_value;
size_t partition_id_length;

BeginDecryptFromJson(env, __func__, info, partition_id_string,
input_value);
input_value, partition_id_length);

#ifdef USE_SCOPED_ALLOCATE_BUFFER
char *partition_id_cbuffer;
size_t partition_id_cbuffer_size =
CobhanBufferNapi::StringToAllocationSize(env, partition_id_string);
CobhanBufferNapi::StringToAllocationSize(env, partition_id_string, partition_id_length);
SCOPED_ALLOCATE_BUFFER(partition_id_cbuffer, partition_id_cbuffer_size,
maximum_stack_alloc_size, __func__);

Expand All @@ -269,7 +273,8 @@ class Asherah : public Napi::Addon<Asherah> {

CobhanBufferNapi partition_id(env, partition_id_string,
partition_id_cbuffer,
partition_id_cbuffer_size);
partition_id_cbuffer_size,
partition_id_length);
CobhanBufferNapi input(env, input_value, input_cbuffer,
input_cbuffer_size);

Expand All @@ -280,7 +285,7 @@ class Asherah : public Napi::Addon<Asherah> {
maximum_stack_alloc_size, __func__);
CobhanBufferNapi output(env, output_cobhan_buffer, output_size_bytes);
#else
CobhanBufferNapi partition_id(env, partition_id_string);
CobhanBufferNapi partition_id(env, partition_id_string, partition_id_length);
CobhanBufferNapi input(env, input_value);
CobhanBufferNapi output(env, input.get_data_len_bytes());
#endif
Expand Down Expand Up @@ -309,10 +314,11 @@ class Asherah : public Napi::Addon<Asherah> {
try {
Napi::String partition_id_string;
Napi::Value input_value;
size_t partition_id_length;
BeginDecryptFromJson(env, __func__, info, partition_id_string,
input_value);
input_value, partition_id_length);

CobhanBufferNapi partition_id(env, partition_id_string);
CobhanBufferNapi partition_id(env, partition_id_string, partition_id_length);
CobhanBufferNapi input(env, input_value);

CobhanBufferNapi output(env, input.get_data_len_bytes());
Expand All @@ -338,13 +344,14 @@ class Asherah : public Napi::Addon<Asherah> {

Napi::String partition_id_string;
Napi::Value input_value;
size_t partition_id_length;
BeginDecryptFromJson(env, __func__, info, partition_id_string,
input_value);
input_value, partition_id_length);

#ifdef USE_SCOPED_ALLOCATE_BUFFER
char *partition_id_cbuffer;
size_t partition_id_cbuffer_size =
CobhanBufferNapi::StringToAllocationSize(env, partition_id_string);
CobhanBufferNapi::StringToAllocationSize(env, partition_id_string, partition_id_length);
SCOPED_ALLOCATE_BUFFER(partition_id_cbuffer, partition_id_cbuffer_size,
maximum_stack_alloc_size, __func__);

Expand All @@ -356,13 +363,14 @@ class Asherah : public Napi::Addon<Asherah> {

CobhanBufferNapi partition_id(env, partition_id_string,
partition_id_cbuffer,
partition_id_cbuffer_size);
partition_id_cbuffer_size,
partition_id_length);
CobhanBufferNapi input(env, input_value, input_cbuffer,
input_cbuffer_size);

CobhanBufferNapi output(env, input.get_data_len_bytes());
#else
CobhanBufferNapi partition_id(env, partition_id_string);
CobhanBufferNapi partition_id(env, partition_id_string, partition_id_length);
CobhanBufferNapi input(env, input_value);
CobhanBufferNapi output(env, input.get_data_len_bytes());
#endif
Expand All @@ -388,10 +396,11 @@ class Asherah : public Napi::Addon<Asherah> {

Napi::String partition_id_string;
Napi::Value input_value;
size_t partition_id_length;
BeginDecryptFromJson(env, __func__, info, partition_id_string,
input_value);
input_value, partition_id_length);

CobhanBufferNapi partition_id(env, partition_id_string);
CobhanBufferNapi partition_id(env, partition_id_string, partition_id_length);
CobhanBufferNapi input(env, input_value);

CobhanBufferNapi output(env, input.get_data_len_bytes());
Expand Down Expand Up @@ -541,13 +550,20 @@ class Asherah : public Napi::Addon<Asherah> {

void BeginEncryptToJson(const Napi::Env &env, const char *func_name,
const Napi::CallbackInfo &info,
Napi::String &partition_id, Napi::Value &input) {
Napi::String &partition_id, Napi::Value &input,
size_t &partition_id_length) {
RequireAsherahSetup(env, func_name);

NapiUtils::RequireParameterCount(info, 2);

partition_id = NapiUtils::RequireParameterString(env, func_name, info[0]);
partition_id = NapiUtils::RequireParameterStringWithLength(env, func_name, info[0], partition_id_length);
input = NapiUtils::RequireParameterStringOrBuffer(env, func_name, info[1]);

// Validate partition ID is not empty
if (partition_id_length == 0) {
NapiUtils::ThrowException(env, std::string(func_name) +
": Partition ID cannot be empty");
}
}

void EndEncryptToJson(Napi::Env env, CobhanBufferNapi &output, GoInt32 result,
Expand All @@ -567,13 +583,20 @@ class Asherah : public Napi::Addon<Asherah> {

void BeginDecryptFromJson(const Napi::Env &env, const char *func_name,
const Napi::CallbackInfo &info,
Napi::String &partition_id, Napi::Value &input) {
Napi::String &partition_id, Napi::Value &input,
size_t &partition_id_length) {
RequireAsherahSetup(env, func_name);

NapiUtils::RequireParameterCount(info, 2);

partition_id = NapiUtils::RequireParameterString(env, func_name, info[0]);
partition_id = NapiUtils::RequireParameterStringWithLength(env, func_name, info[0], partition_id_length);
input = NapiUtils::RequireParameterStringOrBuffer(env, func_name, info[1]);

// Validate partition ID is not empty
if (partition_id_length == 0) {
NapiUtils::ThrowException(env, std::string(func_name) +
": Partition ID cannot be empty");
}
}

void EndDecryptFromJson(Napi::Env &env, CobhanBufferNapi &output,
Expand Down
Loading
Loading