From e5d96999ae4388181ddb0c113313f26afbe997e8 Mon Sep 17 00:00:00 2001 From: Gabor Melis Date: Mon, 16 Feb 2009 22:23:08 +0000 Subject: [PATCH] 1.0.25.50: detect binding and alien stack exhaustion Alien stack exhaustion machinery only works on x86oids. --- src/code/error.lisp | 22 ++++++++++++++ src/code/exhaust.lisp | 5 ++-- src/code/interr.lisp | 14 +++++++++ src/compiler/generic/parms.lisp | 2 ++ src/pcl/methods.lisp | 4 +++ src/runtime/interrupt.c | 48 ++++++++++++++++++++++++++++--- src/runtime/thread.c | 10 ++++--- src/runtime/validate.c | 60 +++++++++++++++++++-------------------- src/runtime/validate.h | 36 +++++++++++++++++++---- src/runtime/x86-64-darwin-os.c | 8 +++--- src/runtime/x86-darwin-os.c | 8 +++--- tests/exhaust.impure.lisp | 27 ++++++++++++++++++ version.lisp-expr | 2 +- 13 files changed, 190 insertions(+), 56 deletions(-) diff --git a/src/code/error.lisp b/src/code/error.lisp index 085264c..db40332 100644 --- a/src/code/error.lisp +++ b/src/code/error.lisp @@ -164,6 +164,28 @@ calls, or a tail call that SBCL cannot or has not optimized away. PROCEED WITH CAUTION.")))) +(define-condition binding-stack-exhausted (storage-condition) + () + (:report + (lambda (condition stream) + (declare (ignore condition)) + (format stream + ;; no pretty-printing, because that would use a lot of stack. + "Binding stack exhausted. + +PROCEED WITH CAUTION.")))) + +(define-condition alien-stack-exhausted (storage-condition) + () + (:report + (lambda (condition stream) + (declare (ignore condition)) + (format stream + ;; no pretty-printing, because that would use a lot of stack. + "Alien stack exhausted. + +PROCEED WITH CAUTION.")))) + (define-condition heap-exhausted-error (storage-condition) () (:report diff --git a/src/code/exhaust.lisp b/src/code/exhaust.lisp index a96112b..98fa6bc 100644 --- a/src/code/exhaust.lisp +++ b/src/code/exhaust.lisp @@ -14,6 +14,7 @@ (define-alien-routine ("protect_control_stack_guard_page" %protect-control-stack-guard-page) sb!alien:void - (protect-p sb!alien:int)) + (protect-p sb!alien:int) + (thread sb!alien:int)) (defun protect-control-stack-guard-page (n) - (%protect-control-stack-guard-page (if n 1 0))) + (%protect-control-stack-guard-page (if n 1 0) 0)) diff --git a/src/code/interr.lisp b/src/code/interr.lisp index 8ac4f7a..9014b56 100644 --- a/src/code/interr.lisp +++ b/src/code/interr.lisp @@ -460,6 +460,20 @@ "Control stack guard page temporarily disabled: proceed with caution~%") (error 'control-stack-exhausted)))) +(defun binding-stack-exhausted-error () + (let ((sb!debug:*stack-top-hint* nil)) + (infinite-error-protect + (format *error-output* + "Binding stack guard page temporarily disabled: proceed with caution~%") + (error 'binding-stack-exhausted)))) + +(defun alien-stack-exhausted-error () + (let ((sb!debug:*stack-top-hint* nil)) + (infinite-error-protect + (format *error-output* + "Alien stack guard page temporarily disabled: proceed with caution~%") + (error 'alien-stack-exhausted)))) + ;;; KLUDGE: we keep a single HEAP-EXHAUSTED-ERROR object around, so ;;; that we don't need to allocate it when running out of ;;; memory. Similarly we pass the amounts in special variables as diff --git a/src/compiler/generic/parms.lisp b/src/compiler/generic/parms.lisp index 7954fc4..9b9a3a6 100644 --- a/src/compiler/generic/parms.lisp +++ b/src/compiler/generic/parms.lisp @@ -17,6 +17,8 @@ sb!kernel::post-gc sb!kernel::internal-error sb!kernel::control-stack-exhausted-error + sb!kernel::binding-stack-exhausted-error + sb!kernel::alien-stack-exhausted-error sb!kernel::heap-exhausted-error sb!kernel::undefined-alien-variable-error sb!kernel::undefined-alien-function-error diff --git a/src/pcl/methods.lisp b/src/pcl/methods.lisp index e6b08db..d7d40b0 100644 --- a/src/pcl/methods.lisp +++ b/src/pcl/methods.lisp @@ -1642,6 +1642,10 @@ (list (list (find-class 'sb-kernel::control-stack-exhausted)) (list (find-class + 'sb-kernel::binding-stack-exhausted)) + (list (find-class + 'sb-kernel::alien-stack-exhausted)) + (list (find-class 'sb-kernel::heap-exhausted-error)) (list (find-class 'restart)))) (setq po-cache cache) diff --git a/src/runtime/interrupt.c b/src/runtime/interrupt.c index 921f3b7..11e8f19 100644 --- a/src/runtime/interrupt.c +++ b/src/runtime/interrupt.c @@ -1382,8 +1382,8 @@ handle_guard_page_triggered(os_context_t *context,os_vm_address_t addr) * previous page so that we can catch returns from the guard page * and restore it. */ corruption_warning_and_maybe_lose("Control stack exhausted"); - protect_control_stack_guard_page(0); - protect_control_stack_return_guard_page(1); + protect_control_stack_guard_page(0, NULL); + protect_control_stack_return_guard_page(1, NULL); #ifdef LISP_FEATURE_C_STACK_IS_CONTROL_STACK /* For the unfortunate case, when the control stack is @@ -1401,8 +1401,48 @@ handle_guard_page_triggered(os_context_t *context,os_vm_address_t addr) * the return-guard-page, and hit it on our way to new * exhaustion instead. */ fprintf(stderr, "INFO: Control stack guard page reprotected\n"); - protect_control_stack_guard_page(1); - protect_control_stack_return_guard_page(0); + protect_control_stack_guard_page(1, NULL); + protect_control_stack_return_guard_page(0, NULL); + return 1; + } + else if(addr >= BINDING_STACK_GUARD_PAGE(th) && + addr < BINDING_STACK_GUARD_PAGE(th) + os_vm_page_size) { + corruption_warning_and_maybe_lose("Binding stack exhausted"); + protect_binding_stack_guard_page(0, NULL); + protect_binding_stack_return_guard_page(1, NULL); + + /* For the unfortunate case, when the binding stack is + * exhausted in a signal handler. */ + unblock_signals_in_context_and_maybe_warn(context); + arrange_return_to_lisp_function + (context, StaticSymbolFunction(BINDING_STACK_EXHAUSTED_ERROR)); + return 1; + } + else if(addr >= BINDING_STACK_RETURN_GUARD_PAGE(th) && + addr < BINDING_STACK_RETURN_GUARD_PAGE(th) + os_vm_page_size) { + fprintf(stderr, "INFO: Binding stack guard page reprotected\n"); + protect_binding_stack_guard_page(1, NULL); + protect_binding_stack_return_guard_page(0, NULL); + return 1; + } + else if(addr >= ALIEN_STACK_GUARD_PAGE(th) && + addr < ALIEN_STACK_GUARD_PAGE(th) + os_vm_page_size) { + corruption_warning_and_maybe_lose("Alien stack exhausted"); + protect_alien_stack_guard_page(0, NULL); + protect_alien_stack_return_guard_page(1, NULL); + + /* For the unfortunate case, when the alien stack is + * exhausted in a signal handler. */ + unblock_signals_in_context_and_maybe_warn(context); + arrange_return_to_lisp_function + (context, StaticSymbolFunction(ALIEN_STACK_EXHAUSTED_ERROR)); + return 1; + } + else if(addr >= ALIEN_STACK_RETURN_GUARD_PAGE(th) && + addr < ALIEN_STACK_RETURN_GUARD_PAGE(th) + os_vm_page_size) { + fprintf(stderr, "INFO: Alien stack guard page reprotected\n"); + protect_alien_stack_guard_page(1, NULL); + protect_alien_stack_return_guard_page(0, NULL); return 1; } else if (addr >= undefined_alien_address && diff --git a/src/runtime/thread.c b/src/runtime/thread.c index 038f16b..e158680 100644 --- a/src/runtime/thread.c +++ b/src/runtime/thread.c @@ -63,8 +63,6 @@ #define LOCK_CREATE_THREAD #endif -#define ALIEN_STACK_SIZE (1*1024*1024) /* 1Mb size chosen at random */ - #ifdef LISP_FEATURE_SB_THREAD struct thread_post_mortem { #ifdef DELAY_THREAD_POST_MORTEM @@ -135,7 +133,9 @@ initial_thread_trampoline(struct thread *th) link_thread(th); th->os_thread=thread_self(); #ifndef LISP_FEATURE_WIN32 - protect_control_stack_guard_page(1); + protect_control_stack_guard_page(1, NULL); + protect_binding_stack_guard_page(1, NULL); + protect_alien_stack_guard_page(1, NULL); #endif #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64) @@ -268,7 +268,9 @@ new_thread_trampoline(struct thread *th) } th->os_thread=thread_self(); - protect_control_stack_guard_page(1); + protect_control_stack_guard_page(1, NULL); + protect_binding_stack_guard_page(1, NULL); + protect_alien_stack_guard_page(1, NULL); /* Since GC can only know about this thread from the all_threads * list and we're just adding this thread to it, there is no * danger of deadlocking even with SIG_STOP_FOR_GC blocked (which diff --git a/src/runtime/validate.c b/src/runtime/validate.c index d901625..967775a 100644 --- a/src/runtime/validate.c +++ b/src/runtime/validate.c @@ -83,37 +83,35 @@ validate(void) #endif } -void -protect_control_stack_guard_page(int protect_p) { - struct thread *th = arch_os_get_current_thread(); - os_protect(CONTROL_STACK_GUARD_PAGE(th), - os_vm_page_size,protect_p ? - (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL); +static inline void +protect_page(void *page, int protect_p, os_vm_prot_t flags) { + os_protect(page, os_vm_page_size, protect_p ? + flags : OS_VM_PROT_ALL); } -void -protect_control_stack_return_guard_page(int protect_p) { - struct thread *th = arch_os_get_current_thread(); - os_protect(CONTROL_STACK_RETURN_GUARD_PAGE(th), - os_vm_page_size,protect_p ? - (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL); -} - -/* these OOAO violations are here because with mach exception handlers - * we need to protect the stack guard pages from the mach exception - * handlers which run on a different thread, so we take a thread - * argument here. Too bad we don't have keywords args in C. */ -void -protect_control_stack_guard_page_thread(int protect_p, struct thread *th) { - os_protect(CONTROL_STACK_GUARD_PAGE(th), - os_vm_page_size,protect_p ? - (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL); -} +#define DEF_PROTECT_PAGE(name,page_name,flags) \ + void \ + protect_##name(int protect_p, struct thread *thread) { \ + if (!thread) \ + thread = arch_os_get_current_thread(); \ + protect_page(page_name(thread), protect_p, flags); \ + } -void -protect_control_stack_return_guard_page_thread(int protect_p, - struct thread* th) { - os_protect(CONTROL_STACK_RETURN_GUARD_PAGE(th), - os_vm_page_size,protect_p ? - (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL); -} +DEF_PROTECT_PAGE(control_stack_guard_page, + CONTROL_STACK_GUARD_PAGE, + OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) +DEF_PROTECT_PAGE(control_stack_return_guard_page, + CONTROL_STACK_RETURN_GUARD_PAGE, + OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) +DEF_PROTECT_PAGE(binding_stack_guard_page, + BINDING_STACK_GUARD_PAGE, + OS_VM_PROT_NONE) +DEF_PROTECT_PAGE(binding_stack_return_guard_page, + BINDING_STACK_RETURN_GUARD_PAGE, + OS_VM_PROT_NONE) +DEF_PROTECT_PAGE(alien_stack_guard_page, + ALIEN_STACK_GUARD_PAGE, + OS_VM_PROT_NONE) +DEF_PROTECT_PAGE(alien_stack_return_guard_page, + ALIEN_STACK_RETURN_GUARD_PAGE, + OS_VM_PROT_NONE) diff --git a/src/runtime/validate.h b/src/runtime/validate.h index b0f9122..de4e345 100644 --- a/src/runtime/validate.h +++ b/src/runtime/validate.h @@ -18,6 +18,8 @@ #endif #define BINDING_STACK_SIZE (1024*1024) /* chosen at random */ +#define ALIEN_STACK_SIZE (1024*1024) /* chosen at random */ + /* eventually choosable per-thread: */ #define DEFAULT_CONTROL_STACK_SIZE (2*1024*1024) @@ -41,20 +43,42 @@ ((os_vm_address_t)(th->control_stack_start)) #define CONTROL_STACK_RETURN_GUARD_PAGE(th) \ (CONTROL_STACK_GUARD_PAGE(th) + os_vm_page_size) +#define ALIEN_STACK_GUARD_PAGE(th) \ + ((os_vm_address_t)(th->alien_stack_start)) +#define ALIEN_STACK_RETURN_GUARD_PAGE(th) \ + (ALIEN_STACK_GUARD_PAGE(th) + os_vm_page_size) #else #define CONTROL_STACK_GUARD_PAGE(th) \ (((os_vm_address_t)(th->control_stack_end)) - os_vm_page_size) #define CONTROL_STACK_RETURN_GUARD_PAGE(th) \ (CONTROL_STACK_GUARD_PAGE(th) - os_vm_page_size) +#define ALIEN_STACK_GUARD_PAGE(th) \ + (((os_vm_address_t)th->alien_stack_start) + ALIEN_STACK_SIZE - \ + os_vm_page_size) +#define ALIEN_STACK_RETURN_GUARD_PAGE(th) \ + (ALIEN_STACK_GUARD_PAGE(th) - os_vm_page_size) #endif +#define BINDING_STACK_GUARD_PAGE(th) \ + (((os_vm_address_t)th->binding_stack_start) + BINDING_STACK_SIZE - \ + os_vm_page_size) +#define BINDING_STACK_RETURN_GUARD_PAGE(th) \ + (BINDING_STACK_GUARD_PAGE(th) - os_vm_page_size) + extern void validate(void); -extern void protect_control_stack_guard_page(int protect_p); -extern void protect_control_stack_return_guard_page(int protect_p); -extern void protect_control_stack_guard_page_thread(int protect_p, - struct thread *th); -extern void protect_control_stack_return_guard_page_thread(int protect_p, - struct thread* th); + +extern void +protect_control_stack_guard_page(int protect_p, struct thread *thread); +extern void +protect_control_stack_return_guard_page(int protect_p, struct thread *thread); +extern void +protect_binding_stack_guard_page(int protect_p, struct thread *thread); +extern void +protect_binding_stack_return_guard_page(int protect_p, struct thread *thread); +extern void +protect_alien_stack_guard_page(int protect_p, struct thread *thread); +extern void +protect_alien_stack_return_guard_page(int protect_p, struct thread *thread); extern os_vm_address_t undefined_alien_address; #endif diff --git a/src/runtime/x86-64-darwin-os.c b/src/runtime/x86-64-darwin-os.c index 6360e00..2b987dc 100644 --- a/src/runtime/x86-64-darwin-os.c +++ b/src/runtime/x86-64-darwin-os.c @@ -355,8 +355,8 @@ catch_exception_raise(mach_port_t exception_port, * protection so the error handler has some headroom, protect the * previous page so that we can catch returns from the guard page * and restore it. */ - protect_control_stack_guard_page_thread(0, th); - protect_control_stack_return_guard_page_thread(1, th); + protect_control_stack_guard_page(0, th); + protect_control_stack_return_guard_page(1, th); backup_thread_state = thread_state; open_stack_allocation(&thread_state); @@ -397,8 +397,8 @@ catch_exception_raise(mach_port_t exception_port, * unprotect this one. This works even if we somehow missed * the return-guard-page, and hit it on our way to new * exhaustion instead. */ - protect_control_stack_guard_page_thread(1, th); - protect_control_stack_return_guard_page_thread(0, th); + protect_control_stack_guard_page(1, th); + protect_control_stack_return_guard_page(0, th); } else if (addr >= undefined_alien_address && addr < undefined_alien_address + os_vm_page_size) { diff --git a/src/runtime/x86-darwin-os.c b/src/runtime/x86-darwin-os.c index f1d7b6c..3c32ec4 100644 --- a/src/runtime/x86-darwin-os.c +++ b/src/runtime/x86-darwin-os.c @@ -432,15 +432,15 @@ catch_exception_raise(mach_port_t exception_port, } /* At stack guard */ if (os_trunc_to_page(addr) == CONTROL_STACK_GUARD_PAGE(th)) { - protect_control_stack_guard_page_thread(0, th); - protect_control_stack_return_guard_page_thread(1, th); + protect_control_stack_guard_page(0, th); + protect_control_stack_return_guard_page(1, th); handler = control_stack_exhausted_handler; break; } /* Return from stack guard */ if (os_trunc_to_page(addr) == CONTROL_STACK_RETURN_GUARD_PAGE(th)) { - protect_control_stack_guard_page_thread(1, th); - protect_control_stack_return_guard_page_thread(0, th); + protect_control_stack_guard_page(1, th); + protect_control_stack_return_guard_page(0, th); break; } /* Regular memory fault */ diff --git a/tests/exhaust.impure.lisp b/tests/exhaust.impure.lisp index 79f44a4..ef21e7c 100644 --- a/tests/exhaust.impure.lisp +++ b/tests/exhaust.impure.lisp @@ -70,4 +70,31 @@ (recurse))))) (assert (= exhaust-count recurse-count *count*))) +(with-test (:name (:exhaust :binding-stack)) + (let ((ok nil) + (symbols (loop repeat 1024 collect (gensym))) + (values (loop repeat 1024 collect nil))) + (gc :full t) + (labels ((exhaust-binding-stack (i) + (progv symbols values + (exhaust-binding-stack (1+ i))))) + (handler-case + (exhaust-binding-stack 0) + (sb-kernel::binding-stack-exhausted () + (setq ok t))) + (assert ok)))) + +#+c-stack-is-control-stack +(with-test (:name (:exhaust :alien-stack)) + (let ((ok nil)) + (labels ((exhaust-alien-stack (i) + (with-alien ((integer-array (array int 500))) + (+ (deref integer-array 0) + (exhaust-alien-stack (1+ i)))))) + (handler-case + (exhaust-alien-stack 0) + (sb-kernel::alien-stack-exhausted () + (setq ok t))) + (assert ok)))) + ;;; OK! diff --git a/version.lisp-expr b/version.lisp-expr index dcc475a..1bfc001 100644 --- a/version.lisp-expr +++ b/version.lisp-expr @@ -17,4 +17,4 @@ ;;; checkins which aren't released. (And occasionally for internal ;;; versions, especially for internal versions off the main CVS ;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".) -"1.0.25.49" +"1.0.25.50" -- 1.7.10.4