>From 7e640c93f4b64b972a9e7a02bb092b6b6cfc069d Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Sun, 10 Jan 2021 01:13:04 +0100 Subject: [PATCH 1/2] 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. --- ChangeLog | 9 ++ lib/immutable.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/immutable.h | 93 ++++++++++++++++++++ m4/immutable.m4 | 11 +++ m4/mprotect.m4 | 162 ++++++++++++++++++++++++++++++++++ modules/immutable | 30 +++++++ 6 files changed, 559 insertions(+) create mode 100644 lib/immutable.c create mode 100644 lib/immutable.h create mode 100644 m4/immutable.m4 create mode 100644 m4/mprotect.m4 create mode 100644 modules/immutable diff --git a/ChangeLog b/ChangeLog index 4329b81..d319a98 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2021-01-09 Bruno Haible + + 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-09 Paul Eggert snippet/_Noreturn: port to pedantic clang diff --git a/lib/immutable.c b/lib/immutable.c new file mode 100644 index 0000000..089ad17 --- /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 . */ + +/* Written by Bruno Haible , 2021. */ + +#include + +/* Specification. */ +#include "immutable.h" + +#include +#include +#include + +#if IMMUTABLE_EFFECTIVE +/* Real implementation. */ + +/* Get CHAR_BIT. */ +# include + +/* Get intptr_t, uintptr_t. */ +# include + +# include + +/* Declare getpagesize(). */ +# include +/* On HP-UX, getpagesize exists, but it is not declared in 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 +# include + +/* Declare open(). */ +# include +# include + +# 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 . */ +# 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 0000000..75a2f5a --- /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 . */ + +/* Written by Bruno Haible , 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 + +#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 0000000..ffc62d2 --- /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 0000000..13e27e2 --- /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 + /* Declare malloc(). */ + #include + /* Declare getpagesize(). */ + #if HAVE_UNISTD_H + #include + #endif + #ifdef __hpux + extern + #ifdef __cplusplus + "C" + #endif + int getpagesize (void); + #endif + /* Declare mprotect(). */ + #include + 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 0000000..fef376a --- /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 -- 2.7.4