public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Paul Eggert <eggert@cs.ucla.edu>
To: Carlos O'Donell <carlos@redhat.com>
Cc: libc-alpha@sourceware.org
Subject: Re: [PATCH] free: preserve errno [BZ#17924]
Date: Sun, 20 Dec 2020 23:20:33 -0800	[thread overview]
Message-ID: <f33d3233-58cc-12eb-e68f-2aee35490799@cs.ucla.edu> (raw)
In-Reply-To: <ad0ae4cb-ef86-1586-998c-c7de8ae94345@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 311 bytes --]

On 12/20/20 8:27 PM, Carlos O'Donell wrote:
> Please provide a reasonable commit message.

Thanks, unfortunately I slipped up and used the old-style commits still 
common in other projects. Revised patch attached. This revision also 
addresses Siddhesh's comments (he found another way errno could be munged).


[-- Attachment #2: 0001-free-preserve-errno-BZ-17924.patch --]
[-- Type: text/x-patch, Size: 10573 bytes --]

From e4fec6f28270c9b0979f9fe8920dc52f8f7d70cd Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat, 19 Dec 2020 12:52:09 -0800
Subject: [PATCH] free: preserve errno [BZ#17924]

In the next release of POSIX, free must preserve errno
<https://www.austingroupbugs.net/view.php?id=385>.
Modify __libc_free to save and restore errno, so that
any internal munmap etc. syscalls do not disturb the caller's errno.
Add a test malloc/tst-free-errno.c (almost all by Bruno Haible),
and document that free preserves errno.
---
 malloc/Makefile         |   1 +
 malloc/malloc.c         |  13 +++-
 malloc/tst-free-errno.c | 167 ++++++++++++++++++++++++++++++++++++++++
 manual/memory.texi      |   9 +++
 4 files changed, 186 insertions(+), 4 deletions(-)
 create mode 100644 malloc/tst-free-errno.c

diff --git a/malloc/Makefile b/malloc/Makefile
index ab64dcfd73..4b3975f90d 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -34,6 +34,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
 	 tst-interpose-nothread \
 	 tst-interpose-thread \
 	 tst-alloc_buffer \
+	 tst-free-errno \
 	 tst-malloc-tcache-leak \
 	 tst-malloc_info tst-mallinfo2 \
 	 tst-malloc-too-large \
diff --git a/malloc/malloc.c b/malloc/malloc.c
index 326075e704..44020d1fd8 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -3124,6 +3124,8 @@ __libc_free (void *mem)
   if (mem == 0)                              /* free(0) has no effect */
     return;
 
+  int err = errno;
+
   p = mem2chunk (mem);
 
   if (chunk_is_mmapped (p))                       /* release mmapped memory. */
@@ -3141,13 +3143,16 @@ __libc_free (void *mem)
                       mp_.mmap_threshold, mp_.trim_threshold);
         }
       munmap_chunk (p);
-      return;
     }
+  else
+    {
+      MAYBE_INIT_TCACHE ();
 
-  MAYBE_INIT_TCACHE ();
+      ar_ptr = arena_for_chunk (p);
+      _int_free (ar_ptr, p, 0);
+    }
 
-  ar_ptr = arena_for_chunk (p);
-  _int_free (ar_ptr, p, 0);
+  __set_errno (err);
 }
 libc_hidden_def (__libc_free)
 
diff --git a/malloc/tst-free-errno.c b/malloc/tst-free-errno.c
new file mode 100644
index 0000000000..960c419512
--- /dev/null
+++ b/malloc/tst-free-errno.c
@@ -0,0 +1,167 @@
+/* Test that free preserves errno.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined __linux__
+# include <fcntl.h>
+# include <stdint.h>
+# include <string.h>
+# include <sys/mman.h>
+#endif
+
+#define ASSERT_NO_STDIO(expr) \
+  do                                                        \
+    {                                                       \
+      if (!(expr))                                          \
+        {                                                   \
+          WRITE_TO_STDERR (__FILE__);                       \
+          WRITE_TO_STDERR (":");                            \
+          WRITE_MACROEXPANDED_INTEGER_TO_STDERR (__LINE__); \
+          WRITE_TO_STDERR (": assertion '");                \
+          WRITE_TO_STDERR (#expr);                          \
+          WRITE_TO_STDERR ("' failed\n");                   \
+          abort ();                                         \
+        }                                                   \
+    }                                                       \
+  while (0)
+#define WRITE_MACROEXPANDED_INTEGER_TO_STDERR(integer) \
+  WRITE_INTEGER_TO_STDERR(integer)
+#define WRITE_INTEGER_TO_STDERR(integer) \
+  WRITE_TO_STDERR (#integer)
+#define WRITE_TO_STDERR(string_literal) \
+  {                                     \
+    const char *s = string_literal;     \
+    int ret = write (2, s, strlen (s)); \
+    (void) ret;                         \
+  }
+
+/* The indirection through a volatile function pointer is necessary to prevent
+   a GCC optimization.  Without it, when optimizing, GCC would "know" that errno
+   is unchanged by calling free(ptr), when ptr was the result of a malloc(...)
+   call in the same function.  */
+static int
+get_errno (void)
+{
+  volatile int err = errno;
+  return err;
+}
+
+static int (* volatile get_errno_func) (void) = get_errno;
+
+static int
+do_test (void)
+{
+  /* Check that free() preserves errno.  */
+  {
+    errno = 1789; /* Liberté, égalité, fraternité.  */
+    free (NULL);
+    ASSERT_NO_STDIO (get_errno_func () == 1789);
+  }
+  { /* Large memory allocations.  */
+    #define N 2
+    void * volatile ptrs[N];
+    size_t i;
+    for (i = 0; i < N; i++)
+      ptrs[i] = malloc (5318153);
+    for (i = 0; i < N; i++)
+      {
+        errno = 1789;
+        free (ptrs[i]);
+        ASSERT_NO_STDIO (get_errno_func () == 1789);
+      }
+    #undef N
+  }
+
+  /* Test a less common code path.
+     When malloc() is based on mmap(), free() can sometimes call munmap().
+     munmap() usually succeeds, but fails in a particular situation: when
+       - it has to unmap the middle part of a VMA, and
+       - the number of VMAs of a process is limited and the limit is
+         already reached.
+     The latter condition is fulfilled on Linux, when the file
+     /proc/sys/vm/max_map_count exists.  This file contains the limit
+       - for Linux >= 2.4.19: 65536 (DEFAULT_MAX_MAP_COUNT in linux/include/linux/sched.h)
+       - for Linux >= 2.6.31: 65530 (DEFAULT_MAX_MAP_COUNT in linux/include/linux/mm.h).
+   */
+  #if defined __linux__
+  if (open ("/proc/sys/vm/max_map_count", O_RDONLY) >= 0)
+    {
+      /* Preparations.  */
+      size_t pagesize = getpagesize ();
+      void *firstpage_backup = malloc (pagesize);
+      void *lastpage_backup = malloc (pagesize);
+      /* Allocate a large memory area, as a bumper, so that the MAP_FIXED
+         allocation later will not overwrite parts of the memory areas
+         allocated to ld.so or libc.so.  */
+      void *bumper_region =
+        mmap (NULL, 0x1000000, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+      /* A file descriptor pointing to a regular file.  */
+      int fd = open ("/etc/hosts", O_RDONLY);
+
+      if (firstpage_backup != NULL && lastpage_backup != NULL
+          && bumper_region != (void *)(-1)
+          && fd >= 0)
+        {
+          /* Do a large memory allocation.  */
+          size_t big_size = 0x1000000;
+          void * volatile ptr = malloc (big_size - 0x100);
+          char *ptr_aligned = (char *) ((uintptr_t) ptr & ~(pagesize - 1));
+          /* This large memory allocation allocated a memory area
+             from ptr_aligned to ptr_aligned + big_size.
+             Enlarge this memory area by adding a page before and a page
+             after it.  */
+          memcpy (firstpage_backup, ptr_aligned, pagesize);
+          memcpy (lastpage_backup, ptr_aligned + big_size - pagesize, pagesize);
+          if (mmap (ptr_aligned - pagesize, pagesize + big_size + pagesize,
+                    PROT_READ | PROT_WRITE,
+                    MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0)
+              != (void *)(-1))
+            {
+              memcpy (ptr_aligned, firstpage_backup, pagesize);
+              memcpy (ptr_aligned + big_size - pagesize, lastpage_backup, pagesize);
+
+              /* Now add as many mappings as we can.
+                 Stop at 65536, in order not to crash the machine (in case the
+                 limit has been increased by the system administrator).  */
+              size_t i;
+              for (i = 0; i < 65536; i++)
+                if (mmap (NULL, pagesize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0)
+                    == (void *)(-1))
+                  break;
+              /* Now the number of VMAs of this process has hopefully attained
+                 its limit.  */
+
+              errno = 1789;
+              /* This call to free() is supposed to call
+                   munmap (ptr_aligned, big_size);
+                 which increases the number of VMAs by 1, which is supposed
+                 to fail.  */
+              free (ptr);
+              ASSERT_NO_STDIO (get_errno_func () == 1789);
+            }
+        }
+    }
+  #endif
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/manual/memory.texi b/manual/memory.texi
index c132261084..b2cc65228a 100644
--- a/manual/memory.texi
+++ b/manual/memory.texi
@@ -738,6 +738,12 @@ later call to @code{malloc} to reuse the space.  In the meantime, the
 space remains in your program as part of a free-list used internally by
 @code{malloc}.
 
+The @code{free} function preserves the value of @code{errno}, so that
+cleanup code need not worry about saving and restoring @code{errno}
+around a call to @code{free}.  Although neither @w{ISO C} nor
+POSIX.1-2017 requires @code{free} to preserve @code{errno}, a future
+version of POSIX is planned to require it.
+
 There is no point in freeing blocks at the end of a program, because all
 of the program's space is given back to the system when the process
 terminates.
@@ -1935,6 +1941,9 @@ linking against @code{libc.a} (explicitly or implicitly).
 functions (that is, all the functions used by the application,
 @theglibc{}, and other linked-in libraries) can lead to static linking
 failures, and, at run time, to heap corruption and application crashes.
+Replacement functions should implement the behavior documented for
+their counterparts in @theglibc{}; for example, the replacement
+@code{free} should also preserve @code{errno}.
 
 The minimum set of functions which has to be provided by a custom
 @code{malloc} is given in the table below.
-- 
2.29.2


  reply	other threads:[~2020-12-21  7:20 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-20 20:25 Paul Eggert
2020-12-21  2:03 ` Siddhesh Poyarekar
2020-12-21  4:27 ` Carlos O'Donell
2020-12-21  7:20   ` Paul Eggert [this message]
2020-12-21  7:43     ` Siddhesh Poyarekar
2020-12-21  9:33     ` Florian Weimer
2020-12-21 10:03       ` Siddhesh Poyarekar
2020-12-21 10:05         ` Siddhesh Poyarekar
2020-12-23  5:30       ` Paul Eggert
2020-12-23 19:19         ` Adhemerval Zanella
2020-12-24  1:03           ` Paul Eggert
2020-12-28 19:24             ` Adhemerval Zanella
2020-12-29 13:38               ` H.J. Lu
2020-12-29 18:32                 ` Adhemerval Zanella

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f33d3233-58cc-12eb-e68f-2aee35490799@cs.ucla.edu \
    --to=eggert@cs.ucla.edu \
    --cc=carlos@redhat.com \
    --cc=libc-alpha@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).