@@ -756,10 +756,10 @@ void OS::protectSignalHandlers(SigAction segvHandler, SigAction busHandler) {
756756 // Save the current (JVM's) signal handlers BEFORE we install ours.
757757 // These will be returned as oldact when we intercept other libraries' sigaction calls,
758758 // so they chain to JVM instead of back to us (which would cause infinite loops).
759- if (!_orig_handlers_saved && _real_sigaction != nullptr ) {
759+ if (!__atomic_load_n (& _orig_handlers_saved, __ATOMIC_ACQUIRE) && _real_sigaction != nullptr ) {
760760 _real_sigaction (SIGSEGV, nullptr , &_orig_segv_sigaction);
761761 _real_sigaction (SIGBUS, nullptr , &_orig_bus_sigaction);
762- _orig_handlers_saved = true ;
762+ __atomic_store_n (& _orig_handlers_saved, true , __ATOMIC_RELEASE) ;
763763 }
764764 _protected_segv_handler = segvHandler;
765765 _protected_bus_handler = busHandler;
@@ -773,7 +773,67 @@ SigAction OS::getBusChainTarget() {
773773 return __atomic_load_n (&_bus_chain_target, __ATOMIC_ACQUIRE);
774774}
775775
776- // sigaction hook - called via GOT patching to intercept sigaction calls
776+ // sigaction_hook - intercepts sigaction(2) calls from any library via GOT patching.
777+ //
778+ // PROBLEM SOLVED
779+ // ==============
780+ // Without interception, a library (e.g. wasmtime) can overwrite our SIGSEGV handler:
781+ //
782+ // Before: kernel --> our_handler --> JVM_handler
783+ // After lib calls sigaction(SIGSEGV, lib_handler, &oldact):
784+ // kernel --> lib_handler
785+ // lib_handler stores oldact = our_handler as its chain target
786+ // => when lib chains on unhandled fault: lib_handler --> our_handler --> lib_handler --> ...
787+ // INFINITE LOOP
788+ //
789+ // HANDLER CHAIN AFTER SETUP
790+ // ==========================
791+ //
792+ // protectSignalHandlers() replaceSigsegvHandler() LibraryPatcher::patch_sigaction()
793+ // | | |
794+ // v v v
795+ // save JVM handler install our_handler GOT-patch sigaction
796+ // into _orig_segv_sigaction as real OS handler => all future sigaction()
797+ // calls go through us
798+ //
799+ // Signal delivery chain:
800+ //
801+ // kernel
802+ // |
803+ // v
804+ // our_handler (installed via replaceSigsegvHandler, never displaced)
805+ // |
806+ // +-- handled by us? --> done
807+ // |
808+ // v (not handled)
809+ // _segv_chain_target (lib_handler, set when we intercepted lib's sigaction call)
810+ // |
811+ // +-- handled by lib? --> done
812+ // |
813+ // v (lib chains to its saved oldact)
814+ // _orig_segv_sigaction (JVM's original handler, what we returned as oldact to lib)
815+ // |
816+ // v
817+ // JVM handles or terminates
818+ //
819+ // INTERCEPTION LOGIC (this function)
820+ // ===================================
821+ // Case 1 - Install call [act != nullptr, SA_SIGINFO]:
822+ // - Save lib's handler as _segv_chain_target (we'll call it if we can't handle)
823+ // - Return _orig_segv_sigaction as oldact (NOT our handler, to break the loop)
824+ // - Do NOT actually install lib's handler (keep ours on top)
825+ //
826+ // Case 2 - Query-only call [act == nullptr, oldact != nullptr]:
827+ // - Return _orig_segv_sigaction as oldact (same reason: lib must not see our handler)
828+ // - A lib that queries, stores the result, then uses it as a chain target would
829+ // loop if we returned our handler here.
830+ //
831+ // Case 3 - 1-arg handler [act != nullptr, no SA_SIGINFO]:
832+ // - Pass through: we cannot safely chain 1-arg handlers (different calling convention)
833+ //
834+ // Case 4 - Any other signal, or protection not yet active:
835+ // - Pass through to real sigaction unchanged.
836+ //
777837static int sigaction_hook (int signum, const struct sigaction * act, struct sigaction * oldact) {
778838 // _real_sigaction must be resolved before any GOT patching happens
779839 if (_real_sigaction == nullptr ) {
@@ -782,20 +842,23 @@ static int sigaction_hook(int signum, const struct sigaction* act, struct sigact
782842 }
783843
784844 // If this is SIGSEGV or SIGBUS and we have protected handlers installed,
785- // intercept the call to keep our handler on top
786- if (act != nullptr ) {
787- if (signum == SIGSEGV && _protected_segv_handler != nullptr ) {
788- // Only intercept SA_SIGINFO handlers (3-arg form) for safe chaining
845+ // intercept the call to keep our handler on top.
846+ // We intercept both install calls (act != nullptr) and query-only calls (act == nullptr)
847+ // to ensure callers always see the JVM's original handler, never ours.
848+ // A caller that gets our handler as oldact and later chains to it would cause an
849+ // infinite loop: us -> them -> us -> ...
850+ if (signum == SIGSEGV && _protected_segv_handler != nullptr ) {
851+ if (act != nullptr ) {
852+ // Install call: only intercept SA_SIGINFO handlers (3-arg form) for safe chaining
789853 if (act->sa_flags & SA_SIGINFO) {
790854 SigAction new_handler = act->sa_sigaction ;
791855 // Don't intercept if it's our own handler being installed
792856 if (new_handler != _protected_segv_handler) {
793857 // Save their handler as our chain target
794858 __atomic_exchange_n (&_segv_chain_target, new_handler, __ATOMIC_ACQ_REL);
795859 if (oldact != nullptr ) {
796- // Return the original (JVM's) handler, not our handler.
797- // This way, when the intercepted library chains to "previous",
798- // it goes to JVM, not back to us (which would cause infinite loops).
860+ // Return the original (JVM's) handler, not ours, to prevent
861+ // the caller from chaining back to us.
799862 *oldact = _orig_segv_sigaction;
800863 }
801864 Counters::increment (SIGACTION_INTERCEPTED);
@@ -804,19 +867,28 @@ static int sigaction_hook(int signum, const struct sigaction* act, struct sigact
804867 }
805868 }
806869 // Let 1-arg handlers (without SA_SIGINFO) pass through - we can't safely chain them
807- } else if (signum == SIGBUS && _protected_bus_handler != nullptr ) {
870+ } else if (oldact != nullptr ) {
871+ // Query-only call: return the JVM's original handler, not ours.
872+ // Same reason: a caller that stores our handler and later chains to it causes loops.
873+ *oldact = _orig_segv_sigaction;
874+ return 0 ;
875+ }
876+ } else if (signum == SIGBUS && _protected_bus_handler != nullptr ) {
877+ if (act != nullptr ) {
808878 if (act->sa_flags & SA_SIGINFO) {
809879 SigAction new_handler = act->sa_sigaction ;
810880 if (new_handler != _protected_bus_handler) {
811881 __atomic_exchange_n (&_bus_chain_target, new_handler, __ATOMIC_ACQ_REL);
812882 if (oldact != nullptr ) {
813- // Return the original (JVM's) handler, not our handler.
814883 *oldact = _orig_bus_sigaction;
815884 }
816885 Counters::increment (SIGACTION_INTERCEPTED);
817886 return 0 ;
818887 }
819888 }
889+ } else if (oldact != nullptr ) {
890+ *oldact = _orig_bus_sigaction;
891+ return 0 ;
820892 }
821893 }
822894
@@ -828,4 +900,15 @@ void* OS::getSigactionHook() {
828900 return (void *)sigaction_hook;
829901}
830902
903+ void OS::resetSignalHandlersForTesting () {
904+ __atomic_store_n (&_orig_handlers_saved, false , __ATOMIC_RELEASE);
905+ memset (&_orig_segv_sigaction, 0 , sizeof (_orig_segv_sigaction));
906+ memset (&_orig_bus_sigaction, 0 , sizeof (_orig_bus_sigaction));
907+ _protected_segv_handler = nullptr ;
908+ _protected_bus_handler = nullptr ;
909+ __atomic_store_n (&_segv_chain_target, (SigAction)nullptr , __ATOMIC_RELEASE);
910+ __atomic_store_n (&_bus_chain_target, (SigAction)nullptr , __ATOMIC_RELEASE);
911+ // _real_sigaction is intentionally not reset: safe to reuse across tests
912+ }
913+
831914#endif // __linux__
0 commit comments