/* Detect stack overflow (when getrlimit and sigaction or sigvec are available) Copyright (C) 1993-1994, 2006-2007, 2010, 2013 Free Software Foundation, Inc. Jim Avera , October 1993. This file is part of GNU M4. GNU M4 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU M4 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Compiled only when USE_STACKOVF is defined, which itself requires getrlimit with the RLIMIT_STACK option, and support for alternate signal stacks using either SVR4 or BSD interfaces. This should compile on ANY system which supports either sigaltstack() or sigstack(), with or without or another way to determine the fault address. There is no completely portable way to determine if a SIGSEGV signal indicates a stack overflow. The fault address can be used to infer this. However, the fault address is passed to the signal handler in different ways on various systems. One of three methods are used to determine the fault address: 1. The siginfo parameter (with siginfo.h, i.e., SVR4). 2. 4th "addr" parameter (assumed if struct sigcontext is defined, i.e., SunOS 4.x/BSD). 3. None (if no method is available). This case just prints a message before aborting with a core dump. That way the user at least knows that it *might* be a recursion problem. Jim Avera writes, on Tue, 5 Oct 93 19:27 PDT: "I got interested finding out how a program could catch and diagnose its own stack overflow, and ended up modifying m4 to do this. Now it prints a nice error message and exits. How it works: SIGSEGV is caught using a separate signal stack. The signal handler declares a stack overflow if the fault address is near the end of the stack region, or if the maximum VM address space limit has been reached. Otherwise, it returns to re-execute the instruction with SIG_DFL set, so that any real bugs cause a core dump as usual." Jim Avera writes, on Fri, 24 Jun 94 12:14 PDT: "The stack-overflow detection code would still be needed to avoid a SIGSEGV abort if swap space was exhausted at the moment the stack tried to grow. This is probably unlikely to occur with the explicit nesting limit option of GNU m4." Jim Avera writes, on Wed, 6 Jul 1994 14:41 PDT: "When a stack overflow occurs, a SIGSEGV signal is sent, which by default aborts the process with a core dump. The code in stackovf.c catches SIGSEGV using a separate signal stack. The signal handler determines whether or not the SIGSEGV arose from a stack overflow. If it is a stack overflow, an external function is called (which, in m4, prints a message an exits). Otherwise the SIGSEGV represents an m4 bug, and the signal is re-raised with SIG_DFL set, which results in an abort and core dump in the usual way. It seems important (to me) that internal m4 bugs not be reported as user recursion errors, or vice-versa." */ /* Define this to see runtime debug info. Implied by DEBUG. */ /*#define DEBUG_STKOVF */ #include #include "m4.h" #ifdef USE_STACKOVF #include #include #if HAVE_SIGINFO_H # include #endif #ifndef SA_RESETHAND # define SA_RESETHAND 0 #endif #ifndef SA_SIGINFO # define SA_SIGINFO 0 #endif #ifndef SIGSTKSZ # define SIGSTKSZ 8192 #endif /* If the trap address is within STACKOVF_DETECT bytes of the calculated stack limit, we diagnose a stack overflow. This must be large enough to cover errors in our estimatation of the limit address, and to account for the maximum size of local variables (the amount the trapping reference might exceed the stack limit). Also, some machines may report an arbitrary address within the same page frame. If the value is too large, we might call some other SIGSEGV a stack overflow, masking a bug. */ #ifndef STACKOVF_DETECT # define STACKOVF_DETECT 16384 #endif typedef void (*handler_t) (void); #if defined __ultrix && defined __vax extern char *sbrk (int); extern int getrlimit (int, struct rlimit *); extern int sigstack (struct sigstack *, struct sigstack *); extern int sigvec (int, struct sigvec *, struct sigvec *); #endif static void *stackbuf; static const char *stackbot; static const char *stackend; static const char *arg0; static handler_t stackovf_handler; /* The following OS-independent procedure is called from the SIGSEGV signal handler. The signal handler obtains information about the trap in an OS-dependent manner, and passes a parameter with the meanings as explained below. If the OS explicitly identifies a stack overflow trap, either pass PARAM_STACKOVF if a stack overflow, or pass PARAM_NOSTACKOVF if not (id est, it is a random bounds violation). Otherwise, if the fault address is available, pass the fault address. Otherwise (if no information is available), pass NULL. Not given an explicit indication, we compare the fault address with the estimated stack limit, and test to see if overall VM space is exhausted. If a stack overflow is identified, then the external *stackovf_handler function is called, which should print an error message and exit. If it is NOT a stack overflow, then we silently abort with a core dump by returning to re-raise the SIGSEGV with SIG_DFL set. If indeterminate, then we do not call *stackovf_handler, but instead print an ambiguous message and abort with a core dump. This only occurs on systems which provide no information, but is better than nothing. */ #define PARAM_STACKOVF ((const char *) (1 + STACKOVF_DETECT)) #define PARAM_NOSTACKOVF ((const char *) (2 + STACKOVF_DETECT)) static void process_sigsegv (int signo, const char *p) { ptrdiff_t diff; diff = (p - stackend); #ifdef DEBUG_STKOVF { char buf[200]; sprintf (buf, "process_sigsegv: p=%p stackend=%p diff=%" PRIdPTR "bot=%p\n", p, stackend, diff, stackbot); write (2, buf, strlen (buf)); } #endif if (p != PARAM_NOSTACKOVF) { if ((long) sbrk (8192) == (long) -1) { const char *cp; /* sbrk failed. Assume the RLIMIT_VMEM prevents expansion even if the stack limit has not been reached. */ /* FIXME - calling gettext inside a signal handler is dangerous, since it can call malloc, which is not signal safe. We can sort of justify it by the fact that this handler is designed to exit() the program, but it could really use a better fix. */ cp = _("VMEM limit exceeded?\n"); write (2, cp, strlen (cp)); p = PARAM_STACKOVF; } if (diff >= -STACKOVF_DETECT && diff <= STACKOVF_DETECT) { /* The fault address is "sufficiently close" to the stack lim. */ p = PARAM_STACKOVF; } if (p == PARAM_STACKOVF) { /* We have determined that this is indeed a stack overflow. */ (*stackovf_handler) (); /* should call exit() */ } } if (p == NULL) { const char *cp; /* FIXME - calling gettext inside a signal handler is dangerous, since it can call malloc, which is not signal safe. */ cp = _("\ Memory bounds violation detected (SIGSEGV). Either a stack overflow\n\ occurred, or there is a bug in "); write (2, cp, strlen (cp)); write (2, arg0, strlen (arg0)); cp = _(". Check for possible infinite recursion.\n"); write (2, cp, strlen (cp)); } /* Return to re-execute the instruction which caused the trap with SIGSEGV set to SIG_DFL. An abort with core dump should occur. */ signal (signo, SIG_DFL); } #if HAVE_STRUCT_SIGACTION_SA_SIGACTION /* POSIX. */ static void sigsegv_handler (int signo, siginfo_t *ip, void *context) { process_sigsegv (signo, (ip != NULL && ip->si_signo == SIGSEGV ? (char *) ip->si_addr : NULL)); } #elif HAVE_SIGINFO_T /* SVR4. */ static void sigsegv_handler (int signo, siginfo_t *ip) { process_sigsegv (signo, (ip != NULL && ip->si_signo == SIGSEGV ? (char *) ip->si_addr : NULL)); } #elif HAVE_SIGCONTEXT /* SunOS 4.x (and BSD?). (not tested) */ static void sigsegv_handler (int signo, int code, struct sigcontext *scp, char *addr) { process_sigsegv (signo, addr); } #else /* not HAVE_SIGCONTEXT */ /* OS provides no information. */ static void sigsegv_handler (int signo) { process_sigsegv (signo, NULL); } #endif /* not HAVE_SIGCONTEXT */ /* Arrange to trap a stack-overflow and call a specified handler. The call is on a dedicated signal stack. argv and envp are as passed to main. If a stack overflow is not detected, then the SIGSEGV is re-raised with action set to SIG_DFL, causing an abort and coredump in the usual way. Detection of a stack overflow depends on the trap address being near the stack limit address. The stack limit cannot be directly determined in a portable way, but we make an estimate based on the address of the argv and environment vectors, their contents, and the maximum stack size obtained using getrlimit. */ void setup_stackovf_trap (char *const *argv, char *const *envp, handler_t handler) { struct rlimit rl; rlim_t stack_len; int grows_upward; register char *const *v; register char *p; #if HAVE_SIGACTION && defined SA_ONSTACK struct sigaction act; #elif HAVE_SIGVEC && defined SV_ONSTACK struct sigvec vec; #else Error - Do not know how to set up stack-ovf trap handler... #endif arg0 = argv[0]; stackovf_handler = handler; /* Calculate the approximate expected addr for a stack-ovf trap. */ if (getrlimit (RLIMIT_STACK, &rl) < 0) error (EXIT_FAILURE, errno, _("getrlimit")); stack_len = (rl.rlim_cur < rl.rlim_max ? rl.rlim_cur : rl.rlim_max); stackbot = (char *) argv; grows_upward = ((char *) &stack_len > stackbot); if (grows_upward) { /* Grows toward increasing addresses. */ for (v = argv; (p = (char *) *v) != NULL; v++) { if (p < stackbot) stackbot = p; } if ((char *) envp < stackbot) stackbot = (char *) envp; for (v = envp; (p = (char *) *v) != NULL; v++) { if (p < stackbot) stackbot = p; } stackend = stackbot + stack_len; } else { /* The stack grows "downward" (toward decreasing addresses). */ for (v = argv; (p = (char *) *v) != NULL; v++) { if (p > stackbot) stackbot = p; } if ((char *) envp > stackbot) stackbot = (char *) envp; for (v = envp; (p = (char *) *v) != NULL; v++) { if (p > stackbot) stackbot = p; } stackend = stackbot - stack_len; } /* Allocate a separate signal-handler stack. */ #if HAVE_SIGALTSTACK && (HAVE_SIGINFO_T || ! HAVE_SIGSTACK) /* Use sigaltstack only if siginfo_t is available, unless there is no choice. */ { stack_t ss; # ifndef HAVE_STACK_T_SS_SP /* This workaround is for BSD/OS 4.0.1: http://lists.gnu.org/archive/html/bug-m4/2006-12/msg00004.html */ # define ss_sp ss_base # endif /* ! HAVE_STACK_T_SS_SP */ stackbuf = xmalloc (SIGSTKSZ); ss.ss_size = SIGSTKSZ; ss.ss_sp = stackbuf; ss.ss_flags = 0; if (sigaltstack (&ss, NULL) < 0) { /* Oops - sigstack exists but doesn't work. We can't install the overflow detector, but should gracefully treat it as though sigstack doesn't exist. For example, this happens when compiled with Linux 2.1 headers but run against Linux 2.0 kernel. */ free (stackbuf); if (errno == ENOSYS) return; error (EXIT_FAILURE, errno, _("sigaltstack")); } } #elif HAVE_SIGSTACK { struct sigstack ss; stackbuf = xmalloc (2 * SIGSTKSZ); ss.ss_sp = stackbuf + SIGSTKSZ; ss.ss_onstack = 0; if (sigstack (&ss, NULL) < 0) { /* Oops - sigstack exists but doesn't work. We can't install the overflow detector, but should gracefully treat it as though sigstack doesn't exist. For example, this happens when compiled with Linux 2.1 headers but run against Linux 2.0 kernel. */ free (stackbuf); if (errno == ENOSYS) return; error (EXIT_FAILURE, errno, _("sigstack")); } } #else /* not HAVE_SIGSTACK */ Error - Do not know how to set up stack-ovf trap handler... #endif /* not HAVE_SIGSTACK */ /* Arm the SIGSEGV signal handler. */ #if HAVE_SIGACTION && defined SA_ONSTACK sigaction (SIGSEGV, NULL, &act); # if HAVE_STRUCT_SIGACTION_SA_SIGACTION act.sa_sigaction = sigsegv_handler; # else /* ! HAVE_STRUCT_SIGACTION_SA_SIGACTION */ act.sa_handler = (RETSIGTYPE (*) (int)) sigsegv_handler; # endif /* ! HAVE_STRUCT_SIGACTION_SA_SIGACTION */ sigemptyset (&act.sa_mask); act.sa_flags = (SA_ONSTACK | SA_RESETHAND | SA_SIGINFO); if (sigaction (SIGSEGV, &act, NULL) < 0) error (EXIT_FAILURE, errno, _("sigaction")); #else /* ! HAVE_SIGACTION */ vec.sv_handler = (RETSIGTYPE (*) (int)) sigsegv_handler; vec.sv_mask = 0; vec.sv_flags = (SV_ONSTACK | SV_RESETHAND); if (sigvec (SIGSEGV, &vec, NULL) < 0) error (EXIT_FAILURE, errno, _("sigvec")); #endif /* ! HAVE_SIGACTION */ } void stackovf_exit (void) { DELETE (stackbuf); } #endif /* USE_STACKOVF */