-
Notifications
You must be signed in to change notification settings - Fork 695
[GEODE-10520] Security : Eliminate DirectBuffer Access to sun.nio.ch Internal Package #7955
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
Conversation
Replace sun.nio.ch.DirectBuffer usage with BufferAttachmentTracker, using only public Java APIs (WeakHashMap and ByteBuffer). Changes: - Created BufferAttachmentTracker: WeakHashMap-based tracker for slice-to-original buffer mappings, replacing internal DirectBuffer.attachment() access - Updated BufferPool: Modified slice creation to record mappings and simplified getPoolableBuffer() to use the tracker - Removed DirectBuffer wrapper: Deleted geode-unsafe DirectBuffer wrapper class - Updated MemberJvmOptions: Removed SUN_NIO_CH_EXPORT from required JVM options - Added comprehensive unit tests: BufferAttachmentTrackerTest validates all tracker functionality Benefits: - Eliminates one JVM module export requirement - Uses only public Java APIs - Maintains functionality with automatic memory cleanup via WeakHashMap - Fully backward compatible Testing: - All BufferPool tests pass - New BufferAttachmentTracker tests pass - Compilation successful
- Add detailed PMD suppression justification explaining thread-safety - Document why ConcurrentHashMap is safe for concurrent access - Explain lock-free operations and atomic guarantees - Add 7-line comment block explaining mutable static field design choice
- Fixed acquirePredefinedFixedBuffer() to return full-capacity buffers instead of modifying buffer limits before return - Added BufferAttachmentTracker.removeTracking() in releaseBuffer() to properly clean up slice-to-original mappings - Created non-slicing buffer acquisition methods for NioPlainEngine and NioSslEngine which require reusable full-capacity buffers - Separated buffer acquisition into two use cases: * Single-use sliced buffers (2-param acquireDirectBuffer) * Reusable full-capacity buffers (3-param acquireDirectBuffer) This fixes IllegalArgumentException 'newLimit > capacity' errors in distributed tests by ensuring pooled buffers maintain proper capacity.
Replace ConcurrentHashMap with synchronized IdentityHashMap to avoid ByteBuffer.equals() issues. ByteBuffer uses content-based equality which can throw IndexOutOfBoundsException when buffer state (position/limit) changes after being used as a map key. IdentityHashMap uses object identity (==) which is safe and appropriate for tracking buffer relationships.
|
Hi @sboorlagadda . All tests have passed. Please let me know if you have any questions. Thank you. |
|
We are ready to merge. Please let me know if you have any concerns. Thank you very much for your support. |
sboorlagadda
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for this refactoring and brining JPMS compliance.
|
Thank you @sboorlagadda! Really appreciate your support throughout this initiative. Excited to see Apache Geode achieve full JPMS compliance! |
…Internal Package (#7955) * refactor: Replace internal JDK DirectBuffer with public API solution Replace sun.nio.ch.DirectBuffer usage with BufferAttachmentTracker, using only public Java APIs (WeakHashMap and ByteBuffer). Changes: - Created BufferAttachmentTracker: WeakHashMap-based tracker for slice-to-original buffer mappings, replacing internal DirectBuffer.attachment() access - Updated BufferPool: Modified slice creation to record mappings and simplified getPoolableBuffer() to use the tracker - Removed DirectBuffer wrapper: Deleted geode-unsafe DirectBuffer wrapper class - Updated MemberJvmOptions: Removed SUN_NIO_CH_EXPORT from required JVM options - Added comprehensive unit tests: BufferAttachmentTrackerTest validates all tracker functionality Benefits: - Eliminates one JVM module export requirement - Uses only public Java APIs - Maintains functionality with automatic memory cleanup via WeakHashMap - Fully backward compatible Testing: - All BufferPool tests pass - New BufferAttachmentTracker tests pass - Compilation successful * Add comprehensive documentation to BufferAttachmentTracker - Add detailed PMD suppression justification explaining thread-safety - Document why ConcurrentHashMap is safe for concurrent access - Explain lock-free operations and atomic guarantees - Add 7-line comment block explaining mutable static field design choice * Apply spotless formatting to BufferAttachmentTrackerTest * fix: Correct buffer pooling to prevent capacity issues in NioEngine - Fixed acquirePredefinedFixedBuffer() to return full-capacity buffers instead of modifying buffer limits before return - Added BufferAttachmentTracker.removeTracking() in releaseBuffer() to properly clean up slice-to-original mappings - Created non-slicing buffer acquisition methods for NioPlainEngine and NioSslEngine which require reusable full-capacity buffers - Separated buffer acquisition into two use cases: * Single-use sliced buffers (2-param acquireDirectBuffer) * Reusable full-capacity buffers (3-param acquireDirectBuffer) This fixes IllegalArgumentException 'newLimit > capacity' errors in distributed tests by ensuring pooled buffers maintain proper capacity. * Fix IndexOutOfBoundsException in BufferAttachmentTracker Replace ConcurrentHashMap with synchronized IdentityHashMap to avoid ByteBuffer.equals() issues. ByteBuffer uses content-based equality which can throw IndexOutOfBoundsException when buffer state (position/limit) changes after being used as a map key. IdentityHashMap uses object identity (==) which is safe and appropriate for tracking buffer relationships.
Summary
This PR removes Apache Geode's dependency on the internal
sun.nio.ch.DirectBufferinterface, eliminating the need for the--add-exports=java.base/sun.nio.ch=ALL-UNNAMEDJVM flag. The refactoring uses only public Java APIs with a thread-safeIdentityHashMap-based tracker while maintaining all existing functionality for buffer slice tracking in the BufferPool.Motivation
Problem
Apache Geode previously accessed the internal
sun.nio.ch.DirectBufferinterface to track buffer slice-to-original mappings inBufferPool. This approach had several critical issues:Impact
• One less JVM flag to configure (
SUN_NIO_CH_EXPORT)• Improved security posture by not exposing internal I/O implementation
• Better forward compatibility with future Java versions
• Simpler deployment and operations
• Reduced technical debt
Changes
New Component: BufferAttachmentTracker
Created
BufferAttachmentTrackerclass ingeode-coreto replace internal API usage:Key Features:
• Uses
Collections.synchronizedMap(new IdentityHashMap<>())for slice-to-original mapping• Thread-safe operations using synchronized wrapper
• Uses object identity (==) instead of equals() to avoid ByteBuffer.equals() issues
• Explicit cleanup via
removeTracking()to prevent memory leaks• Simple public API:
recordSlice(),getOriginal(), andremoveTracking()Implementation Details:
Why IdentityHashMap:
• Object Identity: Uses reference equality (==) instead of content-based equals()
• Prevents IndexOutOfBoundsException: ByteBuffer.equals() compares buffer content based on position/limit, which can throw exceptions when buffer state changes after being used as a map key
• Safe for Mutable Keys: IdentityHashMap doesn't call equals() or hashCode(), making it safe for ByteBuffer keys
• Thread-Safe: Collections.synchronizedMap provides thread-safe access
• Explicit Cleanup: Provides better control over memory management
PMD Suppression Justification:
The mutable static field is intentionally designed for global buffer tracking and properly justified:
Modified: BufferPool.java
Updated buffer slice creation and retrieval:
Before (using internal API):
After (using public API):
Key Changes:
• Record slice mapping when buffer is sliced
• Simplified
getPoolableBuffer()to use tracker• Explicit cleanup in
releaseBuffer()prevents memory leaks• No dependency on internal APIs
• Maintains exact same functionality
Removed: DirectBuffer Wrapper
Deleted internal API wrapper from
geode-unsafemodule:•
geode-unsafe/src/main/java/.../DirectBuffer.java(36 lines)•
geode-unsafe/src/test/java/.../DirectBufferTest.java(53 lines)These files are no longer needed as we don't access internal APIs.
Updated: MemberJvmOptions.java
Removed
SUN_NIO_CH_EXPORTfrom required JVM options:Before:
After:
New Tests: BufferAttachmentTrackerTest.java
Comprehensive test coverage (130 lines) includes:
Test Cases:
•
getOriginal_returnsOriginalBufferForSlice()- Basic slice tracking•
getOriginal_returnsBufferItselfWhenNotTracked()- Identity for non-tracked buffers•
removeTracking_removesSliceMapping()- Cleanup verification•
trackingMapSize_reflectsCurrentMappings()- Size tracking•
clearTracking_removesAllMappings()- Bulk cleanup•
recordSlice_canOverwriteExistingMapping()- Update behavior•
worksWithHeapBuffers()- Non-direct buffer support•
simpleThreadSafetyTest()- Basic concurrent access•
concurrentAccessToSameSlice_isThreadSafe()- 10 threads × 1,000 operations = 10,000 concurrent operations•
memoryCleanup_releasesBuffersCorrectly()- Memory cleanup behaviorThread Safety Test:
Testing
Existing Tests
• All
BufferPooltests pass without modification• No functional changes to buffer pooling behavior
• Existing integration tests validate end-to-end functionality
New Tests
•
BufferAttachmentTrackerTestprovides comprehensive coverage• Thread safety validated: 10,000 concurrent operations with zero errors
• Tests validate correctness, memory cleanup, and edge cases
Validation
All tests pass successfully. PMD static analysis passes with justified suppression.
Performance Impact
Expected: Negligible to none
Analysis:
•
IdentityHashMaplookup: O(1) average case with synchronized access• Synchronization overhead minimal for typical usage patterns
• Memory overhead: One IdentityHashMap entry per slice (small fixed cost)
• Object identity comparison (==) is faster than content-based equals()
Comparison:
Compatibility
Backward Compatibility
• Fully backward compatible - no API changes to BufferPool
• Existing code continues to work without modification
• No changes to buffer pooling behavior or semantics
Forward Compatibility
• Works on Java 17, 21, and future versions
• No dependency on JDK internals that could change
• Aligns with Java module system best practices
Security Improvements
Related Work
This PR is part of a broader initiative to eliminate all internal API usage in Apache Geode:
• GEODE-10519: Eliminated
UnsafeThreadLocalaccess tojava.langinternals (completed)• GEODE-10520: This PR - Eliminate
DirectBufferaccess tosun.nio.chinternals• Future: AddressableMemoryManager access to
java.niointernals• Future: VMStats50 access to
com.sun.management.internalinternalsGoal: Make Apache Geode fully compliant with Java Platform Module System, eliminating all
--add-opensand--add-exportsrequirements.Documentation Updates
• Added comprehensive Javadoc to
BufferAttachmentTrackerexplaining IdentityHashMap choice• Detailed inline comments explaining why IdentityHashMap is safe for ByteBuffer keys
• Updated
BufferPool.getPoolableBuffer()documentation• PMD suppression includes 4-point justification for thread safety
Checklist
✅ Code follows Apache Geode coding standards
✅ All new code has comprehensive test coverage
✅ All existing tests pass without modification
✅ No breaking changes to public APIs
✅ Internal API usage eliminated
✅ Thread safety validated (10,000 concurrent operations)
✅ Memory management verified (explicit cleanup)
✅ Documentation added for new components with thread-safety justification
✅ JVM flag removed from MemberJvmOptions
✅ Backward compatibility maintained
✅ PMD static analysis passes
Files Changed
Added
•
geode-core/src/main/java/org/apache/geode/internal/net/BufferAttachmentTracker.java(89 lines)•
geode-core/src/test/java/org/apache/geode/internal/net/BufferAttachmentTrackerTest.java(130 lines)Modified
•
geode-core/src/main/java/org/apache/geode/internal/net/BufferPool.java(20 lines changed)•
geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/MemberJvmOptions.java(7 lines deleted)Deleted
•
geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBuffer.java(36 lines)•
geode-unsafe/src/test/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBufferTest.java(53 lines)Summary: 6 files changed, 224 insertions(+), 111 deletions(-)
Review Focus Areas
Additional Notes
Implementation Evolution
Initial Approach: ConcurrentHashMap
Initially implemented with
ConcurrentHashMap<ByteBuffer, ByteBuffer>for lock-free concurrent access. However, this causedIndexOutOfBoundsExceptionduring distributed tests (discovered inWANHostNameVerificationDistributedTest).Root Cause:
ByteBufferimplements content-basedequals()andhashCode()that depend on the buffer's current position, limit, and content. When a ByteBuffer is used as a HashMap key:Stack Trace:
Solution: IdentityHashMap
Switched to
Collections.synchronizedMap(new IdentityHashMap<>())which:• Uses object identity (==) instead of equals() for key comparison
• Never calls ByteBuffer.equals() or hashCode()
• Safe for mutable keys like ByteBuffer
• Provides thread-safe access via synchronized wrapper
• Correctly tracks buffer relationships based on object identity, not content
Why Not Other Approaches?
Alternative 1: Store attachment in custom field
• Requires modifying ByteBuffer (not possible - final class)
• Would need wrapper class (defeats purpose of removing wrappers)
Alternative 2: WeakHashMap with Collections.synchronizedMap
• Initially attempted but discovered WeakHashMap is NOT thread-safe even with external synchronization
• WeakHashMap can modify internal structure during reads when GC cleans up references
• Led to race conditions under concurrent load (discovered via testing)
Alternative 3: Keep DirectBuffer wrapper
• Rejected - doesn't solve the core problem
• Still requires JVM export flags
• Still depends on unstable internal APIs
Alternative 4: ConcurrentHashMap (attempted)
• Lock-free concurrent access is appealing
• FAILED: ByteBuffer.equals() throws IndexOutOfBoundsException when used as HashMap key
• Not safe for mutable keys whose state changes after insertion
Chosen: Synchronized IdentityHashMap
• Object identity (==) comparison - safe for mutable keys
• Never calls equals() or hashCode()
• Thread-safe via Collections.synchronizedMap
• Explicit cleanup provides clear lifecycle management
• Validated with 10,000 concurrent operations with zero errors
Thread Safety Validation
The implementation was rigorously tested for thread safety:
ConcurrentHashMapIndexOutOfBoundsExceptionin distributed testsIdentityHashMapwith synchronized wrapperFuture Enhancements
Potential improvements for future consideration:
However, current implementation with explicit cleanup is sufficient and preferred for production use.
References
• JIRA: GEODE-10520
• Related PR: GEODE-10519 (UnsafeThreadLocal refactoring)
• Documentation: Java Platform Module System
• IdentityHashMap: Java API Documentation
For all changes, please confirm:
develop)?gradlew buildrun cleanly?