c41d59
commit bde121872001d8f3224eeafa5b7effb871c3fbca
c41d59
Author: Simon Kissane <skissane@gmail.com>
c41d59
Date:   Sat Feb 11 08:58:02 2023 +1100
c41d59
c41d59
    gmon: fix memory corruption issues [BZ# 30101]
c41d59
c41d59
    V2 of this patch fixes an issue in V1, where the state was changed to ON not
c41d59
    OFF at end of _mcleanup. I hadn't noticed that (counterintuitively) ON=0 and
c41d59
    OFF=3, hence zeroing the buffer turned it back on. So set the state to OFF
c41d59
    after the memset.
c41d59
c41d59
    1. Prevent double free, and reads from unallocated memory, when
c41d59
       _mcleanup is (incorrectly) called two or more times in a row,
c41d59
       without an intervening call to __monstartup; with this patch, the
c41d59
       second and subsequent calls effectively become no-ops instead.
c41d59
       While setting tos=NULL is minimal fix, safest action is to zero the
c41d59
       whole gmonparam buffer.
c41d59
c41d59
    2. Prevent memory leak when __monstartup is (incorrectly) called two
c41d59
       or more times in a row, without an intervening call to _mcleanup;
c41d59
       with this patch, the second and subsequent calls effectively become
c41d59
       no-ops instead.
c41d59
c41d59
    3. After _mcleanup, treat __moncontrol(1) as __moncontrol(0) instead.
c41d59
       With zeroing of gmonparam buffer in _mcleanup, this stops the
c41d59
       state incorrectly being changed to GMON_PROF_ON despite profiling
c41d59
       actually being off. If we'd just done the minimal fix to _mcleanup
c41d59
       of setting tos=NULL, there is risk of far worse memory corruption:
c41d59
       kcount would point to deallocated memory, and the __profil syscall
c41d59
       would make the kernel write profiling data into that memory,
c41d59
       which could have since been reallocated to something unrelated.
c41d59
c41d59
    4. Ensure __moncontrol(0) still turns off profiling even in error
c41d59
       state. Otherwise, if mcount overflows and sets state to
c41d59
       GMON_PROF_ERROR, when _mcleanup calls __moncontrol(0), the __profil
c41d59
       syscall to disable profiling will not be invoked. _mcleanup will
c41d59
       free the buffer, but the kernel will still be writing profiling
c41d59
       data into it, potentially corrupted arbitrary memory.
c41d59
c41d59
    Also adds a test case for (1). Issues (2)-(4) are not feasible to test.
c41d59
c41d59
    Signed-off-by: Simon Kissane <skissane@gmail.com>
c41d59
    Reviewed-by: DJ Delorie <dj@redhat.com>
c41d59
c41d59
Conflicts:
c41d59
	gmon/Makefile
c41d59
	  (copyright year update)
c41d59
c41d59
diff --git a/gmon/Makefile b/gmon/Makefile
c41d59
index 54f05894d4dd8c4a..1bc4ad6e14e292a9 100644
c41d59
--- a/gmon/Makefile
c41d59
+++ b/gmon/Makefile
c41d59
@@ -1,4 +1,5 @@
c41d59
-# Copyright (C) 1995-2018 Free Software Foundation, Inc.
c41d59
+# Copyright (C) 1995-2023 Free Software Foundation, Inc.
c41d59
+# Copyright The GNU Toolchain Authors.
c41d59
 # This file is part of the GNU C Library.
c41d59
 
c41d59
 # The GNU C Library is free software; you can redistribute it and/or
c41d59
@@ -25,7 +26,7 @@ include ../Makeconfig
c41d59
 headers	:= sys/gmon.h sys/gmon_out.h sys/profil.h
c41d59
 routines := gmon mcount profil sprofil prof-freq
c41d59
 
c41d59
-tests	= tst-sprofil tst-gmon tst-mcount-overflow
c41d59
+tests	= tst-sprofil tst-gmon tst-mcount-overflow tst-mcleanup
c41d59
 ifeq ($(build-profile),yes)
c41d59
 tests	+= tst-profile-static
c41d59
 tests-static	+= tst-profile-static
c41d59
@@ -68,6 +69,14 @@ ifeq ($(run-built-tests),yes)
c41d59
 tests-special += $(objpfx)tst-mcount-overflow-check.out
c41d59
 endif
c41d59
 
c41d59
+CFLAGS-tst-mcleanup.c := -fno-omit-frame-pointer -pg
c41d59
+tst-mcleanup-no-pie = yes
c41d59
+CRT-tst-mcleanup := $(csu-objpfx)g$(start-installed-name)
c41d59
+tst-mcleanup-ENV := GMON_OUT_PREFIX=$(objpfx)tst-mcleanup.data
c41d59
+ifeq ($(run-built-tests),yes)
c41d59
+tests-special += $(objpfx)tst-mcleanup.out
c41d59
+endif
c41d59
+
c41d59
 CFLAGS-tst-gmon-static.c := $(PIE-ccflag) -fno-omit-frame-pointer -pg
c41d59
 CRT-tst-gmon-static := $(csu-objpfx)gcrt1.o
c41d59
 tst-gmon-static-no-pie = yes
c41d59
@@ -123,6 +132,10 @@ $(objpfx)tst-mcount-overflow-check.out: tst-mcount-overflow-check.sh $(objpfx)ts
c41d59
 	$(SHELL) $< $(objpfx)tst-mcount-overflow > $@; \
c41d59
 	$(evaluate-test)
c41d59
 
c41d59
+$(objpfx)tst-mcleanup.out: clean-tst-mcleanup-data
c41d59
+clean-tst-mcleanup-data:
c41d59
+	rm -f $(objpfx)tst-mcleanup.data.*
c41d59
+
c41d59
 $(objpfx)tst-gmon-gprof.out: tst-gmon-gprof.sh $(objpfx)tst-gmon.out
c41d59
 	$(SHELL) $< $(GPROF) $(objpfx)tst-gmon $(objpfx)tst-gmon.data.* > $@; \
c41d59
 	$(evaluate-test)
c41d59
diff --git a/gmon/gmon.c b/gmon/gmon.c
c41d59
index 689bf80141e559ca..5e99a7351dc71666 100644
c41d59
--- a/gmon/gmon.c
c41d59
+++ b/gmon/gmon.c
c41d59
@@ -102,11 +102,8 @@ __moncontrol (int mode)
c41d59
 {
c41d59
   struct gmonparam *p = &_gmonparam;
c41d59
 
c41d59
-  /* Don't change the state if we ran into an error.  */
c41d59
-  if (p->state == GMON_PROF_ERROR)
c41d59
-    return;
c41d59
-
c41d59
-  if (mode)
c41d59
+  /* Treat start request as stop if error or gmon not initialized. */
c41d59
+  if (mode && p->state != GMON_PROF_ERROR && p->tos != NULL)
c41d59
     {
c41d59
       /* start */
c41d59
       __profil((void *) p->kcount, p->kcountsize, p->lowpc, s_scale);
c41d59
@@ -116,7 +113,9 @@ __moncontrol (int mode)
c41d59
     {
c41d59
       /* stop */
c41d59
       __profil(NULL, 0, 0, 0);
c41d59
-      p->state = GMON_PROF_OFF;
c41d59
+      /* Don't change the state if we ran into an error. */
c41d59
+      if (p->state != GMON_PROF_ERROR)
c41d59
+        p->state = GMON_PROF_OFF;
c41d59
     }
c41d59
 }
c41d59
 libc_hidden_def (__moncontrol)
c41d59
@@ -146,6 +145,14 @@ __monstartup (u_long lowpc, u_long highpc)
c41d59
   maxarcs = MAXARCS;
c41d59
 #endif
c41d59
 
c41d59
+  /*
c41d59
+   * If we are incorrectly called twice in a row (without an
c41d59
+   * intervening call to _mcleanup), ignore the second call to
c41d59
+   * prevent leaking memory.
c41d59
+   */
c41d59
+  if (p->tos != NULL)
c41d59
+      return;
c41d59
+
c41d59
   /*
c41d59
    * round lowpc and highpc to multiples of the density we're using
c41d59
    * so the rest of the scaling (here and in gprof) stays in ints.
c41d59
@@ -463,9 +470,14 @@ _mcleanup (void)
c41d59
 {
c41d59
   __moncontrol (0);
c41d59
 
c41d59
-  if (_gmonparam.state != GMON_PROF_ERROR)
c41d59
+  if (_gmonparam.state != GMON_PROF_ERROR && _gmonparam.tos != NULL)
c41d59
     write_gmon ();
c41d59
 
c41d59
   /* free the memory. */
c41d59
   free (_gmonparam.tos);
c41d59
+
c41d59
+  /* reset buffer to initial state for safety */
c41d59
+  memset(&_gmonparam, 0, sizeof _gmonparam);
c41d59
+  /* somewhat confusingly, ON=0, OFF=3 */
c41d59
+  _gmonparam.state = GMON_PROF_OFF;
c41d59
 }
c41d59
diff --git a/gmon/tst-mcleanup.c b/gmon/tst-mcleanup.c
c41d59
new file mode 100644
c41d59
index 0000000000000000..b259653ec833aca4
c41d59
--- /dev/null
c41d59
+++ b/gmon/tst-mcleanup.c
c41d59
@@ -0,0 +1,31 @@
c41d59
+/* Test program for repeated invocation of _mcleanup
c41d59
+   Copyright The GNU Toolchain Authors.
c41d59
+   This file is part of the GNU C Library.
c41d59
+
c41d59
+   The GNU C Library is free software; you can redistribute it and/or
c41d59
+   modify it under the terms of the GNU Lesser General Public
c41d59
+   License as published by the Free Software Foundation; either
c41d59
+   version 2.1 of the License, or (at your option) any later version.
c41d59
+
c41d59
+   The GNU C Library is distributed in the hope that it will be useful,
c41d59
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
c41d59
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
c41d59
+   Lesser General Public License for more details.
c41d59
+
c41d59
+   You should have received a copy of the GNU Lesser General Public
c41d59
+   License along with the GNU C Library; if not, see
c41d59
+   <https://www.gnu.org/licenses/>.  */
c41d59
+
c41d59
+/* Intentionally calls _mcleanup() twice: once manually, it will be
c41d59
+   called again as an atexit handler. This is incorrect use of the API,
c41d59
+   but the point of the test is to make sure we don't crash when the
c41d59
+   API is misused in this way. */
c41d59
+
c41d59
+#include <sys/gmon.h>
c41d59
+
c41d59
+int
c41d59
+main (void)
c41d59
+{
c41d59
+  _mcleanup();
c41d59
+  return 0;
c41d59
+}