Subject: bug#27585: closed (Re: bug#27585: segfault when evaluating a file containing only backticks)

From: [email-masked] (GNU bug Tracking System)
Date: Fri, 14 Jul 2017 12:10:02 +0000

Your bug report

#27585: segfault when evaluating a file containing only backticks

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
27585: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=27585
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#27585: segfault when evaluating a file containing only backticks
From: Paul Eggert <[email-masked]>
To: Steve Kemp <[email-masked]>
Cc: [email-masked]
Date: Fri, 14 Jul 2017 05:09:34 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Thanks for reporting the bug. I reproduced the problem on Fedora 26 x86-64, 
fixed it in master by applying the attached patch, and am boldly marking the bug 
as fixed.

As Eli and Daniel mentioned, this area of Emacs cannot be 100% reliable and to 
some extent is indeed a "ticking time bomb". That being said, the problem in 
this particular case was that Emacs had a bad heuristic for guessing whether a 
segmentation violation address was due to stack overflow on GNU/Linux. This bad 
heuristic has been in place for years without anybody reporting it. It's good 
that we fixed this bug (though I hope "normal" users never notice the bug fix :-).
From 9dee1c884eb50ba282eb9dd2495c5269add25963 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Fri, 14 Jul 2017 04:54:05 -0700
Subject: [PATCH] Improve stack-overflow heuristic on GNU/Linux

Problem reported by Steve Kemp (Bug#27585).
* src/eval.c (near_C_stack_top): Remove.  All uses replaced
by current_thread->stack_top.
(record_in_backtrace): Set current_thread->stack_top.
This is for when the Lisp interpreter calls itself.
* src/lread.c (read1): Set current_thread->stack_top.
This is for recursive s-expression reads.
* src/print.c (print_object): Set current_thread->stack_top.
This is for recursive s-expression printing.
* src/thread.c (mark_one_thread): Get stack top first.
* src/thread.h (struct thread_state.stack_top): Now void *, not char *.
---
 src/eval.c   |  9 +--------
 src/lisp.h   |  1 -
 src/lread.c  |  1 +
 src/print.c  |  2 +-
 src/sysdep.c |  2 +-
 src/thread.c | 10 ++++++----
 src/thread.h | 10 ++++++++--
 7 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/src/eval.c b/src/eval.c
index 8f293c9..e590038 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -213,13 +213,6 @@ backtrace_next (union specbinding *pdl)
   return pdl;
 }
 
-/* Return a pointer to somewhere near the top of the C stack.  */
-void *
-near_C_stack_top (void)
-{
-  return backtrace_args (backtrace_top ());
-}
-
 void
 init_eval_once (void)
 {
@@ -2090,7 +2083,7 @@ record_in_backtrace (Lisp_Object function, Lisp_Object *args, ptrdiff_t nargs)
   specpdl_ptr->bt.kind = SPECPDL_BACKTRACE;
   specpdl_ptr->bt.debug_on_exit = false;
   specpdl_ptr->bt.function = function;
-  specpdl_ptr->bt.args = args;
+  current_thread->stack_top = specpdl_ptr->bt.args = args;
   specpdl_ptr->bt.nargs = nargs;
   grow_specpdl ();
 
diff --git a/src/lisp.h b/src/lisp.h
index f5cb6c7..1e8ef7a 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -3874,7 +3874,6 @@ extern Lisp_Object vformat_string (const char *, va_list)
   ATTRIBUTE_FORMAT_PRINTF (1, 0);
 extern void un_autoload (Lisp_Object);
 extern Lisp_Object call_debugger (Lisp_Object arg);
-extern void *near_C_stack_top (void);
 extern void init_eval_once (void);
 extern Lisp_Object safe_call (ptrdiff_t, Lisp_Object, ...);
 extern Lisp_Object safe_call1 (Lisp_Object, Lisp_Object);
diff --git a/src/lread.c b/src/lread.c
index fe5de38..901e40b 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -2676,6 +2676,7 @@ read1 (Lisp_Object readcharfun, int *pch, bool first_in_list)
   bool uninterned_symbol = false;
   bool multibyte;
   char stackbuf[MAX_ALLOCA];
+  current_thread->stack_top = stackbuf;
 
   *pch = 0;
 
diff --git a/src/print.c b/src/print.c
index b6ea3ff..12edf01 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1748,7 +1748,7 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
   char buf[max (sizeof "from..to..in " + 2 * INT_STRLEN_BOUND (EMACS_INT),
 		max (sizeof " . #" + INT_STRLEN_BOUND (printmax_t),
 		     40))];
-
+  current_thread->stack_top = buf;
   maybe_quit ();
 
   /* Detect circularities and truncate them.  */
diff --git a/src/sysdep.c b/src/sysdep.c
index b522367..db99f53 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -1772,7 +1772,7 @@ stack_overflow (siginfo_t *siginfo)
   /* The known top and bottom of the stack.  The actual stack may
      extend a bit beyond these boundaries.  */
   char *bot = stack_bottom;
-  char *top = near_C_stack_top ();
+  char *top = current_thread->stack_top;
 
   /* Log base 2 of the stack heuristic ratio.  This ratio is the size
      of the known stack divided by the size of the guard area past the
diff --git a/src/thread.c b/src/thread.c
index e378797..1f7ced3 100644
--- a/src/thread.c
+++ b/src/thread.c
@@ -595,14 +595,15 @@ thread_select (select_func *func, int max_fds, fd_set *rfds,
 static void
 mark_one_thread (struct thread_state *thread)
 {
-  struct handler *handler;
-  Lisp_Object tem;
+  /* Get the stack top now, in case mark_specpdl changes it.  */
+  void *stack_top = thread->stack_top;
 
   mark_specpdl (thread->m_specpdl, thread->m_specpdl_ptr);
 
-  mark_stack (thread->m_stack_bottom, thread->stack_top);
+  mark_stack (thread->m_stack_bottom, stack_top);
 
-  for (handler = thread->m_handlerlist; handler; handler = handler->next)
+  for (struct handler *handler = thread->m_handlerlist;
+       handler; handler = handler->next)
     {
       mark_object (handler->tag_or_ch);
       mark_object (handler->val);
@@ -610,6 +611,7 @@ mark_one_thread (struct thread_state *thread)
 
   if (thread->m_current_buffer)
     {
+      Lisp_Object tem;
       XSETBUFFER (tem, thread->m_current_buffer);
       mark_object (tem);
     }
diff --git a/src/thread.h b/src/thread.h
index 9e94de5..52b16f1 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -62,8 +62,14 @@ struct thread_state
   char *m_stack_bottom;
 #define stack_bottom (current_thread->m_stack_bottom)
 
-  /* An address near the top of the stack.  */
-  char *stack_top;
+  /* The address of an object near the C stack top, used to determine
+     which words need to be scanned by the garbage collector.  This is
+     also used to detect heuristically whether segmentation violation
+     address indicates stack overflow, as opposed to some internal
+     error in Emacs.  If the C function F calls G which calls H which
+     calls ... F, then at least one of the functions in the chain
+     should set this to the address of a local variable.  */
+  void *stack_top;
 
   struct catchtag *m_catchlist;
 #define catchlist (current_thread->m_catchlist)
-- 
2.7.4

Subject: segfault when evaluating a file containing only backticks
From: Steve Kemp <[email-masked]>
To: [email-masked]
Date: Wed, 05 Jul 2017 06:21:10 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

  I've recently started fuzzing GNU Emacs, using the current git sources.
 During the course of that work I stumbled upon this easily reproduced bug:

   deagol ~ $ perl -e 'print "`" x ( 1024 * 1024  * 12);' > t.el
   deagol ~ $ /usr/bin/emacs --batch --script ./t.el
   ..
   Segmentation fault (core dumped)

  (So I'm trying to call "emacs --batch --script $file" where the file
 contains thousands of repeated backtick-characters.)

  Because I've built from source I can see this backtrace:

#5  handle_sigsegv (sig=11, siginfo=<optimized out>, arg=<optimized out>)
    at sysdep.c:1811
#6  <signal handler called>
#7  read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc661e010, first_in_list=first_in_list@entry=false)
    at lread.c:2923
#8  0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#9  read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc66220c0, first_in_list=first_in_list@entry=false)
    at lread.c:3149
#10 0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#11 read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc6626170, first_in_list=first_in_list@entry=false)
    at lread.c:3149
#12 0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#13 read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc662a220, first_in_list=first_in_list@entry=false)
    at lread.c:3149
#14 0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#15 read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc662e2d0, first_in_list=first_in_list@entry=false)
    at lread.c:3149
#16 0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#17 read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc6632380, first_in_list=first_in_list@entry=false)
    at lread.c:3149
#18 0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#19 read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc6636430, first_in_list=first_in_list@entry=false)
    at lread.c:3149
#20 0x0000000000ad8cda in read0 (readcharfun=35581829) at lread.c:2220
#21 read1 (readcharfun=readcharfun@entry=35581829, 
    pch=pch@entry=0x7ffcc663a4e0, first_in_list=first_in_list@entry=false)
    at lread.c:3149
....

  I've replicated this upon the package of GNU Emacs as available to
 the old-stable/jessie release of Debian GNU/Linux, which identifies
 itself as:

In GNU Emacs 24.5.1 (x86_64-pc-linux-gnu, GTK+ Version 3.14.5)
 of 2016-03-19 on trouble, modified by Debian
Windowing system distributor `The X.Org Foundation', version 11.0.11604000
System Description: Debian GNU/Linux 8.8 (jessie)

Configured using:
 `configure --build x86_64-linux-gnu --prefix=/usr
 --sharedstatedir=/var/lib --libexecdir=/usr/lib
 --localstatedir=/var/lib --infodir=/usr/share/info
 --mandir=/usr/share/man --with-pop=yes
 --enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.5/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.5/site-lisp:/usr/share/emacs/site-lisp
 --build x86_64-linux-gnu --prefix=/usr --sharedstatedir=/var/lib
 --libexecdir=/usr/lib --localstatedir=/var/lib
 --infodir=/usr/share/info --mandir=/usr/share/man --with-pop=yes
 --enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.5/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.5/site-lisp:/usr/share/emacs/site-lisp
 --with-x=yes --with-x-toolkit=gtk3 --with-toolkit-scroll-bars
 'CFLAGS=-g -O2 -fstack-protector-strong -Wformat
 -Werror=format-security -Wall' CPPFLAGS=-D_FORTIFY_SOURCE=2
 LDFLAGS=-Wl,-z,relro'

  My current-git build reports as:

In GNU Emacs 26.0.50 (build 1, x86_64-pc-linux-gnu)
 of 2017-07-05 built on kernel.default.skx.uk0.bigv.io
Repository revision: 5d62247323f53f3ae9c7d9f51e951635887b2fb6
Recent messages:
For information about GNU Emacs and the GNU system, type C-h C-a.
Making completion list...

Configured using:
 'configure --prefix=/tmp/emacs/ --without-makeinfo --with-gnutls=no'

Configured features:
SOUND NOTIFY ZLIB

Important settings:
  value of $LC_ALL: en_US.UTF8
  value of $LANG: en_GB.UTF-8
  locale-coding-system: utf-8-unix

  "Obviously" this same bug can be reproduced inside emacs:

    1.  Open Emacs.
    2.  Create a new buffer.
    3.  Fill the buffer with `
    4.  Ctrl-x h
    5.  M-x eval-region

Steve
-- 
https://www.steve.org.uk/