summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog9
-rw-r--r--lib/immutable.c254
-rw-r--r--lib/immutable.h93
-rw-r--r--m4/immutable.m411
-rw-r--r--m4/mprotect.m4162
-rw-r--r--modules/immutable30
6 files changed, 559 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index aad572748a..8e89903fab 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2021-01-09 Bruno Haible <bruno@clisp.org>
+
+ immutable: New module.
+ * lib/immutable.h: New file.
+ * lib/immutable.c: New file.
+ * m4/immutable.m4: New file.
+ * m4/mprotect.m4: New file, based on libffcall/m4/codeexec.m4.
+ * modules/immutable: New file.
+
2021-01-10 Simon Josefsson <simon@josefsson.org>
Use https:// instead of git://.
diff --git a/lib/immutable.c b/lib/immutable.c
new file mode 100644
index 0000000000..089ad170f4
--- /dev/null
+++ b/lib/immutable.c
@@ -0,0 +1,254 @@
+/* Immutable data.
+
+ Copyright (C) 2021 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2021. */
+
+#include <config.h>
+
+/* Specification. */
+#include "immutable.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if IMMUTABLE_EFFECTIVE
+/* Real implementation. */
+
+/* Get CHAR_BIT. */
+# include <limits.h>
+
+/* Get intptr_t, uintptr_t. */
+# include <stdint.h>
+
+# include <stdio.h>
+
+/* Declare getpagesize(). */
+# include <unistd.h>
+/* On HP-UX, getpagesize exists, but it is not declared in <unistd.h> even if
+ the compiler options -D_HPUX_SOURCE -D_XOPEN_SOURCE=600 are used. */
+# ifdef __hpux
+extern
+# ifdef __cplusplus
+ "C"
+# endif
+ int getpagesize (void);
+# endif
+
+/* Declare mmap(), mprotect(). */
+# include <sys/types.h>
+# include <sys/mman.h>
+
+/* Declare open(). */
+# include <unistd.h>
+# include <fcntl.h>
+
+# include "glthread/lock.h"
+
+
+/* ================= Back end of the malloc implementation ================= */
+
+/* The memory page size.
+ Once it is initialized, a power of 2. Typically 4096 or 8192. */
+static uintptr_t pagesize;
+
+/* Initializes pagesize. */
+static void
+init_pagesize (void)
+{
+ /* Simultaneous execution of this initialization in multiple threads is OK. */
+ pagesize = getpagesize ();
+}
+
+
+/* Variables needed for obtaining memory pages via mmap(). */
+static int file_fd;
+static long file_length;
+
+/* Initialization of these variables. */
+static void
+do_init_mmap_file (void)
+{
+ char filename[100];
+ sprintf (filename, "%s/glimmdata-%d-%ld", "/tmp", getpid (), random ());
+ file_fd = open (filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0700);
+ if (file_fd < 0)
+ {
+ fprintf (stderr, "glimm: Cannot open %s!\n", filename);
+ abort ();
+ }
+ /* Remove the file from the file system as soon as possible, to make
+ sure there is no leftover after this process terminates or crashes. */
+ unlink (filename);
+
+ file_length = 0;
+}
+
+/* Once-only initializer for these variables. */
+gl_once_define (static, for_mmap_once)
+
+static inline void
+init_mmap_file (void)
+{
+ /* Use a once-only initializer here, since simultaneous execution of
+ do_init_mmap_file() in multiple threads must be avoided. */
+ gl_once (for_mmap_once, do_init_mmap_file);
+}
+
+
+/* Size of the (page-aligned) header that links the writable mapping
+ and the read-only mapping together. */
+# define SHARED_LINK_HEADER_SIZE \
+ (INTPTR_WIDTH / CHAR_BIT) /* = sizeof (void *) */
+
+/* Allocates a contiguous set of pages of memory.
+ size > 0, must be a multiple of pagesize.
+ Returns a multiple of PAGESIZE, or 0 upon failure. */
+static uintptr_t
+alloc_pages (size_t size)
+{
+ /* Extend the file by size/pagesize pages. */
+ long new_file_length = file_length + size;
+ if (ftruncate (file_fd, new_file_length) < 0)
+ {
+ fprintf (stderr, "glimm: Cannot extend backing file!\n");
+ return 0;
+ }
+ /* Create separate writable mapping and read-only mapping. */
+ char *mem_w = (char *) mmap (NULL, size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, file_fd, file_length);
+ char *mem_r = (char *) mmap (NULL, size, PROT_READ,
+ MAP_SHARED, file_fd, file_length);
+ if (mem_w == (char *)(-1) || mem_r == (char *)(-1))
+ {
+ if (mem_w != (char *)(-1))
+ munmap (mem_w, size);
+ if (mem_r != (char *)(-1))
+ munmap (mem_r, size);
+ return 0;
+ }
+ file_length = new_file_length;
+
+ /* Link the two memory areas together. */
+ ((intptr_t *) mem_w)[0] = mem_r - mem_w;
+ return (uintptr_t) mem_w;
+}
+
+/* Frees a contiguous set of pages of memory, returned by alloc_pages.
+ size > 0, must be a multiple of pagesize. */
+static void
+free_pages (uintptr_t pages, size_t size)
+{
+ pages -= SHARED_LINK_HEADER_SIZE;
+ if ((pages & (pagesize - 1)) != 0)
+ abort ();
+ char *mem_w = (char *) pages;
+ char *mem_r = mem_w + ((intptr_t *) mem_w)[0];
+ if (munmap (mem_w, size) < 0)
+ abort ();
+ if (munmap (mem_r, size) < 0)
+ abort ();
+}
+
+/* Cygwin defines PAGESIZE in <limits.h>. */
+# undef PAGESIZE
+
+/* ======================= Instantiate the front end ======================= */
+
+# define PAGESIZE pagesize
+/* On Cygwin and Linux/PowerPC, PAGESIZE is 65536. On macOS 11, it is 16384.
+ On all other platforms, it is either 4096 or 8192. */
+# if defined __CYGWIN__ || (defined __linux__ && defined __powerpc__)
+# define PAGESIZE_MAX 65536
+# else
+# define PAGESIZE_MAX 16384
+# endif
+
+# define ALLOC_PAGES alloc_pages
+# define FREE_PAGES free_pages
+# define ALIGNMENT sizeof (void *)
+# define PAGE_RESERVED_HEADER_SIZE SHARED_LINK_HEADER_SIZE
+
+# include "ssfmalloc.h"
+
+
+void *
+immmalloc (size_t size)
+{
+ /* Initializations. */
+ if (!pagesize)
+ {
+ init_mmap_file ();
+ init_pagesize ();
+ }
+
+ void *writable_pointer = (void *) allocate_block (size);
+ if (writable_pointer == NULL)
+ errno = ENOMEM;
+ return writable_pointer;
+}
+
+const void *
+immfreeze (void *writable_pointer)
+{
+ uintptr_t mem_w = (uintptr_t) writable_pointer & -(intptr_t)pagesize;
+ return (void *) ((uintptr_t) writable_pointer + ((intptr_t *) mem_w)[0]);
+}
+
+void
+immfree (const void *readonly_pointer)
+{
+ uintptr_t mem_r = (uintptr_t) readonly_pointer & -(intptr_t)pagesize;
+ free_block ((uintptr_t) readonly_pointer - ((intptr_t *) mem_r)[0]);
+}
+
+#else
+/* Dummy implementation. */
+
+void *
+immmalloc (size_t size)
+{
+ void *p = malloc (size);
+ if (p == NULL)
+ errno = ENOMEM;
+ return p;
+}
+
+const void *
+immfreeze (void *writable_pointer)
+{
+ return writable_pointer;
+}
+
+void
+immfree (const void *readonly_pointer)
+{
+ void *writable_pointer = (void *) readonly_pointer;
+ free (writable_pointer);
+}
+
+#endif
+
+
+const char *
+immstrdup (const char *string)
+{
+ size_t size = strlen (string) + 1;
+ void *wp = immmalloc (size);
+ memcpy (wp, string, size);
+ return (const char *) immfreeze (wp);
+}
diff --git a/lib/immutable.h b/lib/immutable.h
new file mode 100644
index 0000000000..75a2f5a4fb
--- /dev/null
+++ b/lib/immutable.h
@@ -0,0 +1,93 @@
+/* Immutable data.
+
+ Copyright (C) 2021 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2021. */
+
+#ifndef _IMMUTABLE_H
+#define _IMMUTABLE_H
+
+/* This file provide a facility to allocate and free immutable data objects.
+
+ An immutable data object is allocated in three steps:
+ 1. You allocate an immutable memory region.
+ DATA *wp = immmalloc (sizeof (*wp));
+ The pointer wp is actually a writable view to the memory region.
+ 2. You fill the memory region, through the pointer wp:
+ wp->x = ...;
+ wp->y = ...;
+ ...
+ 3. You declare the memory region as frozen. This means that you relinquish
+ write access.
+ DATA const *p = immfreeze (wp);
+ You can now let wp get out-of-scope.
+
+ Then the pointer p can be used only in read-only ways. That is, if you cast
+ away the 'const' and attempt to write to the memory region, it will crash at
+ runtime (through a SIGSEGV signal).
+ p->x = ...; // rejected by the compiler
+ ((DATA *) p)->x = ...; // crashes at runtime
+
+ Finally, you can free the immutable data object:
+ immfree (p);
+ */
+
+/* If you compile this module with the C macro NO_IMMUTABLE set to 1, or on a
+ platform that lacks support for read-only and writeable memory areas, the
+ functions work alike, except that the "read-only" pointers are actually
+ writable. */
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This macro tells whether the implementation effectively rejects writes to
+ immutable data. */
+#if !NO_IMMUTABLE && HAVE_WORKING_MPROTECT
+# define IMMUTABLE_EFFECTIVE 1
+#else
+# define IMMUTABLE_EFFECTIVE 0
+#endif
+
+/* Allocates an immutable memory region.
+ SIZE if the number of bytes; should be > 0.
+ Returns a writeable pointer to the memory region.
+ Upon memory allocation failure, returns NULL with errno set to ENOMEM. */
+extern void * immmalloc (size_t size);
+
+/* Freezes an immutable memory region.
+ WRITABLE_POINTER is a non-NULL return value from immmalloc().
+ Returns a read-only pointer to the same memory region. */
+extern const void * immfreeze (void *writable_pointer);
+
+/* Frees an immutable memory region.
+ READONLY_POINTER is a return value from immfreeze(). */
+extern void immfree (const void *readonly_pointer);
+
+/* The following is just an application to some data types. */
+
+/* Allocates an immutable memory region that contains a copy of the given string.
+ Returns a read-only pointer to this duplicated string.
+ Upon memory allocation failure, returns NULL with errno set to ENOMEM. */
+extern const char * immstrdup (const char *string);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IMMUTABLE_H */
diff --git a/m4/immutable.m4 b/m4/immutable.m4
new file mode 100644
index 0000000000..ffc62d29b4
--- /dev/null
+++ b/m4/immutable.m4
@@ -0,0 +1,11 @@
+# immutable.m4 serial 1
+dnl Copyright (C) 2021 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_IMMUTABLE],
+[
+ AC_REQUIRE([gl_FUNC_MPROTECT_WORKS])
+ AC_REQUIRE([AC_C_INLINE])
+])
diff --git a/m4/mprotect.m4 b/m4/mprotect.m4
new file mode 100644
index 0000000000..13e27e2fd2
--- /dev/null
+++ b/m4/mprotect.m4
@@ -0,0 +1,162 @@
+# mprotect.m4 serial 1
+dnl Copyright (C) 1993-2021 Free Software Foundation, Inc.
+dnl This file is free software, distributed under the terms of the GNU
+dnl General Public License as published by the Free Software Foundation;
+dnl either version 2 of the License, or (at your option) any later version.
+dnl As a special exception to the GNU General Public License, this file
+dnl may be distributed as part of a program that contains a configuration
+dnl script generated by Autoconf, under the same distribution terms as
+dnl the rest of that program.
+
+dnl Test whether mprotect() works.
+dnl Sets gl_cv_func_mprotect_works and defines HAVE_WORKING_MPROTECT.
+
+AC_DEFUN([gl_FUNC_MPROTECT_WORKS],
+[
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+ AC_REQUIRE([gl_FUNC_MMAP_ANON])
+
+ AC_CHECK_FUNCS([mprotect])
+ if test $ac_cv_func_mprotect = yes; then
+ AC_CACHE_CHECK([for working mprotect], [gl_cv_func_mprotect_works],
+ [if test $cross_compiling = no; then
+ mprotect_prog='
+ #include <sys/types.h>
+ /* Declare malloc(). */
+ #include <stdlib.h>
+ /* Declare getpagesize(). */
+ #if HAVE_UNISTD_H
+ #include <unistd.h>
+ #endif
+ #ifdef __hpux
+ extern
+ #ifdef __cplusplus
+ "C"
+ #endif
+ int getpagesize (void);
+ #endif
+ /* Declare mprotect(). */
+ #include <sys/mman.h>
+ char foo;
+ int main ()
+ {
+ unsigned long pagesize = getpagesize ();
+ #define page_align(address) (char*)((unsigned long)(address) & -pagesize)
+ '
+ no_mprotect=
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([
+ [$mprotect_prog
+ if ((pagesize - 1) & pagesize)
+ return 1;
+ return 0;
+ }
+ ]])
+ ],
+ [],
+ [no_mprotect=1],
+ [:])
+ mprotect_prog="$mprotect_prog"'
+ char* area = (char*) malloc (6 * pagesize);
+ char* fault_address = area + pagesize*7/2;
+ '
+ if test -z "$no_mprotect"; then
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([
+ GL_NOCRASH
+ [$mprotect_prog
+ nocrash_init();
+ if (mprotect (page_align (fault_address), pagesize, PROT_NONE) < 0)
+ return 0;
+ foo = *fault_address; /* this should cause an exception or signal */
+ return 0;
+ }
+ ]])
+ ],
+ [no_mprotect=1],
+ [],
+ [:])
+ fi
+ if test -z "$no_mprotect"; then
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([
+ GL_NOCRASH
+ [$mprotect_prog
+ nocrash_init();
+ if (mprotect (page_align (fault_address), pagesize, PROT_NONE) < 0)
+ return 0;
+ *fault_address = 'z'; /* this should cause an exception or signal */
+ return 0;
+ }
+ ]])
+ ],
+ [no_mprotect=1],
+ [],
+ [:])
+ fi
+ if test -z "$no_mprotect"; then
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([
+ GL_NOCRASH
+ [$mprotect_prog
+ nocrash_init();
+ if (mprotect (page_align (fault_address), pagesize, PROT_READ) < 0)
+ return 0;
+ *fault_address = 'z'; /* this should cause an exception or signal */
+ return 0;
+ }
+ ]])
+ ],
+ [no_mprotect=1],
+ [],
+ [:])
+ fi
+ if test -z "$no_mprotect"; then
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([
+ GL_NOCRASH
+ [$mprotect_prog
+ nocrash_init();
+ if (mprotect (page_align (fault_address), pagesize, PROT_READ) < 0)
+ return 1;
+ if (mprotect (page_align (fault_address), pagesize, PROT_READ | PROT_WRITE) < 0)
+ return 1;
+ *fault_address = 'z'; /* this should not cause an exception or signal */
+ return 0;
+ }
+ ]])
+ ],
+ [],
+ [no_mprotect=1],
+ [:])
+ fi
+ if test -z "$no_mprotect"; then
+ gl_cv_func_mprotect_works=yes
+ else
+ gl_cv_func_mprotect_works=no
+ fi
+ else
+ dnl When cross-compiling, assume the known behaviour.
+ case "$host_os" in
+ dnl Guess yes on Linux systems, glibc systems,
+ dnl macOS, BSD systems, AIX, HP-UX, IRIX, Solaris, Cygwin.
+ linux-* | linux | *-gnu* | gnu* | \
+ darwin* | freebsd* | dragonfly* | netbsd* | openbsd* | \
+ aix* | hpux* | irix* | solaris* | cygwin*)
+ gl_cv_func_mprotect_works="guessing yes" ;;
+ mingw*)
+ gl_cv_func_mprotect_works="guessing no" ;;
+ *)
+ dnl If we don't know, obey --enable-cross-guesses.
+ gl_cv_func_mprotect_works="$gl_cross_guess_normal" ;;
+ esac
+ fi
+ ])
+ case "$gl_cv_func_mprotect_works" in
+ *yes)
+ AC_DEFINE([HAVE_WORKING_MPROTECT], [1],
+ [have a working mprotect() function])
+ ;;
+ esac
+ fi
+])
diff --git a/modules/immutable b/modules/immutable
new file mode 100644
index 0000000000..fef376ac07
--- /dev/null
+++ b/modules/immutable
@@ -0,0 +1,30 @@
+Description:
+Immutable data.
+
+Files:
+lib/immutable.h
+lib/immutable.c
+m4/immutable.m4
+m4/mprotect.m4
+m4/mmap-anon.m4
+m4/nocrash.m4
+
+Depends-on:
+stdint
+open
+ssfmalloc
+
+configure.ac:
+gl_IMMUTABLE
+
+Makefile.am:
+lib_SOURCES += immutable.c
+
+Include:
+"immutable.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+Bruno Haible