Skip to content

Commit be2a26f

Browse files
committed
Disable stack checks by default.
1 parent dd05bfc commit be2a26f

File tree

19 files changed

+258
-19
lines changed

19 files changed

+258
-19
lines changed

backend/amd64/emit.mlp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1905,7 +1905,7 @@ let fundecl fundecl =
19051905
D.label (label_name (emit_symbol fundecl.fun_name));
19061906
emit_debug_info fundecl.fun_dbg;
19071907
cfi_startproc ();
1908-
if Config.runtime5 && !Clflags.runtime_variant = "d" then begin
1908+
if Config.runtime5 && (not Config.no_stack_checks) && !Clflags.runtime_variant = "d" then begin
19091909
emit_call (Cmm.global_symbol "caml_assert_stack_invariants");
19101910
end;
19111911
emit_all ~first:true ~fallthrough:true fundecl.fun_body;

ocaml/configure

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ocaml/configure.ac

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,18 @@ AS_IF([test x"$enable_runtime5" = xyes],
9797
[runtime_suffix=],
9898
[runtime_suffix=4])
9999

100+
AC_ARG_ENABLE([stack_checks],
101+
[AS_HELP_STRING([--enable-stack_checks],
102+
[Enable stack checks])])
103+
AS_IF([test x"$enable_stack_checks" = xyes],
104+
[AC_DEFINE([STACK_CHECKS_ENABLED])],
105+
[])
106+
100107
## Output variables
101108

102109
AC_SUBST([enable_runtime5])
103110
AC_SUBST([runtime_suffix])
111+
AC_SUBST([enable_stack_checks])
104112
AC_SUBST([CONFIGURE_ARGS])
105113
AC_SUBST([native_compiler])
106114
AC_SUBST([default_build_target])

ocaml/otherlibs/systhreads/st_stubs.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,16 @@ static void caml_thread_leave_blocking_section(void)
354354
restore_runtime_state(th);
355355
}
356356

357+
int get_pthreads_stack_size(void) {
358+
pthread_attr_t attr;
359+
size_t res = 8388608;
360+
if (pthread_attr_init(&attr) == 0) {
361+
pthread_attr_getstacksize(&attr, &res);
362+
}
363+
pthread_attr_destroy(&attr);
364+
return res;
365+
}
366+
357367
/* Create and setup a new thread info block.
358368
This block has no associated thread descriptor and
359369
is not inserted in the list of threads. */
@@ -362,7 +372,7 @@ static caml_thread_t caml_thread_new_info(void)
362372
{
363373
caml_thread_t th;
364374
caml_domain_state *domain_state;
365-
uintnat stack_wsize = caml_get_init_stack_wsize();
375+
uintnat stack_wsize = caml_get_init_stack_wsize(get_pthreads_stack_size());
366376

367377
domain_state = Caml_state;
368378
th = NULL;

ocaml/runtime/amd64.S

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,23 @@ LBL(caml_call_gc):
545545
CFI_ENDPROC
546546
ENDFUNCTION(G(caml_call_gc))
547547

548+
FUNCTION(G(caml_raise_stack_overflow_nat))
549+
CFI_STARTPROC
550+
CFI_SIGNAL_FRAME
551+
ENTER_FUNCTION
552+
LBL(caml_raise_stack_overflow_nat):
553+
SAVE_ALL_REGS
554+
movq %r15, Caml_state(gc_regs)
555+
SWITCH_OCAML_TO_C
556+
C_call (GCALL(caml_raise_stack_overflow))
557+
SWITCH_C_TO_OCAML
558+
movq Caml_state(gc_regs), %r15
559+
RESTORE_ALL_REGS
560+
LEAVE_FUNCTION
561+
ret
562+
CFI_ENDPROC
563+
ENDFUNCTION(G(caml_raise_stack_overflow_nat))
564+
548565
FUNCTION(G(caml_alloc1))
549566
CFI_STARTPROC
550567
ENTER_FUNCTION
@@ -800,16 +817,10 @@ LBL(117):
800817
movq %rax, %r12 /* Save exception bucket */
801818
movq Caml_state(c_stack), %rsp
802819
movq %rax, C_ARG_1 /* arg 1: exception bucket */
803-
#ifdef WITH_FRAME_POINTERS
804-
movq 8(%r10), C_ARG_2 /* arg 2: pc of raise */
805-
leaq 16(%r10), C_ARG_3 /* arg 3: sp at raise */
806-
#else
807-
movq (%r10), C_ARG_2 /* arg 2: pc of raise */
808-
leaq 8(%r10), C_ARG_3 /* arg 3: sp at raise */
809-
#endif
810-
movq Caml_state(exn_handler), C_ARG_4
811-
/* arg 4: sp of handler */
812-
C_call (GCALL(caml_stash_backtrace))
820+
movq %r10, C_ARG_2 /* arg 2: passed rsp */
821+
movq Caml_state(exn_handler), C_ARG_3
822+
/* arg 3: sp of handler */
823+
C_call (GCALL(caml_stash_backtrace_wrapper))
813824
movq %r12, %rax /* Recover exception bucket */
814825
RESTORE_EXN_HANDLER_OCAML
815826
ret

ocaml/runtime/backtrace_nat.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <stdio.h>
2121
#include <stdlib.h>
2222
#include <string.h>
23+
#include <unistd.h>
2324

2425
#include "caml/alloc.h"
2526
#include "caml/backtrace.h"
@@ -132,6 +133,32 @@ void caml_stash_backtrace(value exn, uintnat pc, char * sp, char* trapsp)
132133
}
133134
}
134135

136+
void caml_stash_backtrace_wrapper(value exn, char* rsp, char* trapsp) {
137+
#if defined(NATIVE_CODE) && !defined(STACK_CHECKS_ENABLED)
138+
/* if we have protected part of the memory, and get an rsp in the
139+
* protected range, just do nothing - using rsp would trigger another
140+
* segfault, while we are probably in the process of raising the
141+
* exception from a segfault... */
142+
struct stack_info *block = Caml_state->current_stack;
143+
int page_size = getpagesize();
144+
char* protected_low = (char *) block + page_size;
145+
char* protected_high = protected_low + page_size;
146+
if ((rsp >= protected_low) && (rsp < protected_high)) {
147+
return;
148+
}
149+
#endif
150+
char* pc;
151+
char* sp;
152+
#ifdef WITH_FRAME_POINTERS
153+
pc = rsp + 8;
154+
sp = rsp + 16;
155+
#else
156+
pc = rsp;
157+
sp = rsp + 8;
158+
#endif
159+
caml_stash_backtrace(exn, *((uintnat*) pc), sp, trapsp);
160+
}
161+
135162
/* minimum size to allocate a backtrace (in slots) */
136163
#define MIN_BACKTRACE_SIZE 16
137164

ocaml/runtime/caml/fiber.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ struct stack_info* caml_alloc_stack_noexc(mlsize_t wosize, value hval,
257257
/* try to grow the stack until at least required_size words are available.
258258
returns nonzero on success */
259259
CAMLextern int caml_try_realloc_stack (asize_t required_wsize);
260-
CAMLextern uintnat caml_get_init_stack_wsize(void);
260+
CAMLextern uintnat caml_get_init_stack_wsize(int thread_stack_wsz);
261261
void caml_change_max_stack_size (uintnat new_max_wsize);
262262
void caml_maybe_expand_stack(void);
263263
CAMLextern void caml_free_stack(struct stack_info* stk);

ocaml/runtime/caml/m.h.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,5 @@
101101
#undef USE_MMAP_MAP_STACK
102102

103103
#undef STACK_ALLOCATION
104+
105+
#undef STACK_CHECKS_ENABLED

ocaml/runtime/caml/signals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
extern "C" {
2828
#endif
2929

30+
CAMLextern void caml_init_nat_signals(void);
31+
3032
CAMLextern void caml_enter_blocking_section (void);
3133
CAMLextern void caml_enter_blocking_section_no_pending (void);
3234
CAMLextern void caml_leave_blocking_section (void);

ocaml/runtime/caml/startup_aux.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ struct caml_params {
4646
uintnat init_custom_minor_ratio;
4747
uintnat init_custom_minor_max_bsz;
4848

49+
uintnat init_main_stack_wsz;
50+
uintnat init_thread_stack_wsz;
4951
uintnat init_max_stack_wsz;
5052

5153
uintnat backtrace_enabled;

ocaml/runtime/domain.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ static void domain_create(uintnat initial_minor_heap_wsize,
545545
dom_internal* d = 0;
546546
caml_domain_state* domain_state;
547547
struct interruptor* s;
548-
uintnat stack_wsize = caml_get_init_stack_wsize();
548+
uintnat stack_wsize = caml_get_init_stack_wsize(-1 /* main thread */);
549549

550550
CAMLassert (domain_self == 0);
551551

ocaml/runtime/fiber.c

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,21 @@
4949

5050
static _Atomic int64_t fiber_id = 0;
5151

52-
uintnat caml_get_init_stack_wsize (void)
52+
uintnat caml_get_init_stack_wsize (int thread_stack_wsz)
5353
{
54-
uintnat default_stack_wsize = Wsize_bsize(Stack_init_bsize);
54+
#if defined(NATIVE_CODE) && !defined(STACK_CHECKS_ENABLED)
55+
uintnat init_stack_wsize =
56+
thread_stack_wsz < 0
57+
? caml_params->init_main_stack_wsz
58+
: caml_params->init_thread_stack_wsz > 0 ? caml_params->init_thread_stack_wsz : thread_stack_wsz;
59+
#else
60+
(void) thread_stack_wsz;
61+
uintnat init_stack_wsize = Wsize_bsize(Stack_init_bsize);
62+
#endif
5563
uintnat stack_wsize;
5664

57-
if (default_stack_wsize < caml_max_stack_wsize)
58-
stack_wsize = default_stack_wsize;
65+
if (init_stack_wsize < caml_max_stack_wsize)
66+
stack_wsize = init_stack_wsize;
5967
else
6068
stack_wsize = caml_max_stack_wsize;
6169

@@ -97,11 +105,11 @@ struct stack_info** caml_alloc_stack_cache (void)
97105

98106
Caml_inline struct stack_info* alloc_for_stack (mlsize_t wosize)
99107
{
108+
#ifdef USE_MMAP_MAP_STACK
100109
size_t len = sizeof(struct stack_info) +
101110
sizeof(value) * wosize +
102111
8 /* for alignment to 16-bytes, needed for arm64 */ +
103112
sizeof(struct stack_handler);
104-
#ifdef USE_MMAP_MAP_STACK
105113
struct stack_info* si;
106114
si = mmap(NULL, len, PROT_WRITE | PROT_READ,
107115
MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, -1, 0);
@@ -111,7 +119,65 @@ Caml_inline struct stack_info* alloc_for_stack (mlsize_t wosize)
111119
si->size = len;
112120
return si;
113121
#else
122+
#if defined(NATIVE_CODE) && !defined(STACK_CHECKS_ENABLED)
123+
/* (We use the following strategy only in native code, because bytecode
124+
* has its own way of dealing with stack checks.)
125+
*
126+
* We want to detect a stack overflow by triggering a segfault when a
127+
* given part of the memory is accessed; in order to do so, we protect
128+
* a page near the end of the stack to make it unreadable/unwritable.
129+
* A signal handler for segfault will be installed, that will check if
130+
* the invalid address is in the range we protect, and will raise a stack
131+
* overflow exception accordingly.
132+
*
133+
* The sequence of steps to achieve that is loosely based on the glibc
134+
* code (See nptl/allocatestack.c):
135+
* - first, we mmap the memory for the stack, with PROT_NONE so that
136+
* the allocated memory is not committed;
137+
* - second, we madvise to not use huge pages for this memory chunk;
138+
* - third, we restore the read/write permissions for the whole memory
139+
* chunk;
140+
* - finally, we disable the read/write permissions again, but only
141+
* for the page that will act as the guard.
142+
*
143+
* The reasoning behind this convoluted process is that if we only
144+
* mmap and then mprotect, we incur the risk of splitting a huge page
145+
* and losing its benefits while causing more bookkeeping.
146+
*/
147+
size_t bsize = Bsize_wsize(wosize);
148+
int page_size = getpagesize();
149+
int num_pages = (bsize + page_size - 1) / page_size;
150+
bsize = (num_pages + 2) * page_size;
151+
size_t len = sizeof(struct stack_info) +
152+
bsize +
153+
8 /* for alignment to 16-bytes, needed for arm64 */ +
154+
sizeof(struct stack_handler);
155+
struct stack_info* block;
156+
block = mmap(NULL, len, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
157+
if (block == MAP_FAILED) {
158+
return NULL;
159+
}
160+
if (madvise (block, len, MADV_NOHUGEPAGE)) {
161+
munmap(block, len);
162+
return NULL;
163+
}
164+
if (mprotect(block, len, PROT_READ | PROT_WRITE)) {
165+
munmap(block, len);
166+
return NULL;
167+
}
168+
if (mprotect((char *) block + page_size, page_size, PROT_NONE)) {
169+
munmap(block, len);
170+
return NULL;
171+
}
172+
block->size = len;
173+
return block;
174+
#else
175+
size_t len = sizeof(struct stack_info) +
176+
sizeof(value) * wosize +
177+
8 /* for alignment to 16-bytes, needed for arm64 */ +
178+
sizeof(struct stack_handler);
114179
return caml_stat_alloc_noexc(len);
180+
#endif /* NATIVE_CODE */
115181
#endif /* USE_MMAP_MAP_STACK */
116182
}
117183

@@ -808,6 +874,16 @@ void caml_free_stack (struct stack_info* stack)
808874

809875
CAMLassert(stack->magic == 42);
810876
CAMLassert(cache != NULL);
877+
878+
#ifndef USE_MMAP_MAP_STACK
879+
#if defined(NATIVE_CODE) && !defined(STACK_CHECKS_ENABLED)
880+
int page_size = getpagesize();
881+
mprotect((void *) ((char *) stack + page_size),
882+
page_size,
883+
PROT_READ | PROT_WRITE);
884+
#endif
885+
#endif
886+
811887
if (stack->cache_bucket != -1) {
812888
stack->exception_ptr =
813889
(void*)(cache[stack->cache_bucket]);
@@ -822,8 +898,12 @@ void caml_free_stack (struct stack_info* stack)
822898
#endif
823899
#ifdef USE_MMAP_MAP_STACK
824900
munmap(stack, stack->size);
901+
#else
902+
#if defined(NATIVE_CODE) && !defined(STACK_CHECKS_ENABLED)
903+
munmap(stack, stack->size);
825904
#else
826905
caml_stat_free(stack);
906+
#endif
827907
#endif
828908
}
829909
}

0 commit comments

Comments
 (0)