summaryrefslogtreecommitdiff
path: root/src/stackovf.c
blob: 39d6f5e6bf9e6d63467f43a20becb128ece6f2c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
/* 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 <jima@netcom.com>, 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 <http://www.gnu.org/licenses/>.
 */

/* 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 <siginfo.h> 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 <jima@netcom.com> 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 <jima@netcom.com> 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 <jima@netcom.com> 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 <config.h>

#include "m4.h"

#ifdef USE_STACKOVF

#include <sys/time.h>
#include <sys/resource.h>

#if HAVE_SIGINFO_H
# include <siginfo.h>
#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 */