]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Make page aligned pools easier to use
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sun, 12 May 2024 16:47:19 +0000 (10:47 -0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sun, 12 May 2024 16:47:19 +0000 (10:47 -0600)
src/bin/radiusd.c
src/lib/util/talloc.c
src/lib/util/talloc.h

index f864edef9cfece035c6493fe3dd869cce95de17a..45085b2123a74a77b13f43d7e429c6ab566e8512 100644 (file)
@@ -52,7 +52,6 @@ RCSID("$Id$")
 #include <freeradius-devel/util/cap.h>
 #endif
 
-#include <ctype.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <sys/file.h>
@@ -239,7 +238,8 @@ int main(int argc, char *argv[])
        bool                    raddb_dir_set = false;
 
        size_t                  pool_size = 0;
-       void                    *pool_page_start = NULL, *pool_page_end = NULL;
+       void                    *pool_page_start = NULL;
+       size_t                  pool_page_len = 0;
        bool                    do_mprotect;
 
 #ifndef NDEBUG
@@ -285,7 +285,7 @@ int main(int argc, char *argv[])
                         *      catch any stray writes.
                         */
                        global_ctx = talloc_page_aligned_pool(talloc_autofree_context(),
-                                                             &pool_page_start, &pool_page_end, pool_size);
+                                                             &pool_page_start, &pool_page_len, 0, pool_size);
                        do_mprotect = true;
                } else {
                        global_ctx = talloc_new(talloc_autofree_context());
@@ -963,7 +963,7 @@ int main(int argc, char *argv[])
         *  to write to this memory we get a SIGBUS.
         */
        if (do_mprotect) {
-               if (mprotect(pool_page_start, (uintptr_t)pool_page_end - (uintptr_t)pool_page_start, PROT_READ) < 0) {
+               if (mprotect(pool_page_start, pool_page_len, PROT_READ) < 0) {
                        PERROR("Protecting global memory failed: %s", fr_syserror(errno));
                        EXIT_WITH_FAILURE;
                }
@@ -994,7 +994,7 @@ int main(int argc, char *argv[])
         *  Unprotect global memory
         */
        if (do_mprotect) {
-               if (mprotect(pool_page_start, (uintptr_t)pool_page_end - (uintptr_t)pool_page_start,
+               if (mprotect(pool_page_start, pool_page_len,
                             PROT_READ | PROT_WRITE) < 0) {
                        PERROR("Unprotecting global memory failed: %s", fr_syserror(errno));
                        EXIT_WITH_FAILURE;
index 9254b6d9c64568c0938e827812400cd36e3ae410..cb9e9429f58e341d3fad4180fb61265d6ea43d5d 100644 (file)
@@ -28,6 +28,10 @@ RCSID("$Id$")
 #include <freeradius-devel/util/dlist.h>
 #include <freeradius-devel/util/sbuff.h>
 #include <freeradius-devel/util/strerror.h>
+#include <freeradius-devel/util/syserror.h>
+
+#include <talloc.h>
+#include <stdatomic.h>
 
 static TALLOC_CTX *global_ctx;
 static _Thread_local TALLOC_CTX *thread_local_ctx;
@@ -216,6 +220,73 @@ TALLOC_CTX *talloc_aligned_array(TALLOC_CTX *ctx, void **start, size_t alignment
        return array;
 }
 
+/** How big the chunk header is
+ *
+ * Non-zero portion will always fit in a uint8_t, so we don't need to worry about atomicity.
+ */
+static size_t          t_hdr_size;
+
+/** Calculate the size of talloc chunk headers, once, on startup
+ *
+ */
+static void _talloc_hdr_size(void)
+{
+       TALLOC_CTX      *pool;
+       void            *chunk;
+
+       pool = talloc_pool(NULL, 1024); /* Allocate 1k of memory, this will always be bigger than the chunk header */
+       if (unlikely(pool == NULL)) {
+       oom:
+               fr_strerror_const("Out of memory");
+               return;
+       }
+       chunk = talloc_size(pool, 1);   /* Get the starting address */
+       if (unlikely(chunk == NULL)) goto oom;
+
+       /*
+        *      Sanity check... make sure the chunk is within the pool
+        *      if it's not, we're in trouble.
+        */
+       if (!fr_cond_assert((chunk > pool) && ((uintptr_t)chunk < ((uintptr_t)pool + 1024)))) {
+               fr_strerror_const("Initial allocation outside of pool memory");
+               return;
+       }
+
+       /*
+        *      Talloc returns the address after the chunk header, so
+        *      the address of the pool is <malloc address> + <hdr_size>.
+        *
+        *      If we allocate a chunk inside the pool, then the address
+        *      of the chunk will be <malloc address> + <hdr_size> + <hdr_size>.
+        *      If we subtrace the chunk from the pool address, we get
+        *      the size of the talloc header.
+        */
+       t_hdr_size = (uintptr_t)chunk - (uintptr_t)pool;
+
+       talloc_free(pool); /* Free the memory we used */
+}
+
+/** Calculate the size of the talloc chunk header
+ *
+ * @return
+ *     - >0 the size of the talloc chunk header.
+ *     - -1 on error.
+ */
+ssize_t talloc_hdr_size(void)
+{
+       static pthread_once_t once_control = PTHREAD_ONCE_INIT;
+
+       if (t_hdr_size > 0) return t_hdr_size;  /* We've already calculated it */
+
+       if (pthread_once(&once_control, _talloc_hdr_size) != 0) {
+               fr_strerror_printf("pthread_once failed: %s", fr_syserror(errno));
+               return -1;
+       }
+       if (t_hdr_size == 0) return -1; /* Callback should set error */
+
+       return t_hdr_size;
+}
+
 /** Return a page aligned talloc memory pool
  *
  * Because we can't intercept talloc's malloc() calls, we need to do some tricks
@@ -231,65 +302,85 @@ TALLOC_CTX *talloc_aligned_array(TALLOC_CTX *ctx, void **start, size_t alignment
  * @param[in] ctx      to allocate pool memory in.
  * @param[out] start   A page aligned address within the pool.  This can be passed
  *                     to mprotect().
- * @param[out] end     of the pages that should be protected.
+ * @param[out] end_len how many bytes to protect.
+ * @param[in] headers  how much room we should allocate for talloc headers.
+ *                     This value should usually be >= 1.
  * @param[in] size     How big to make the pool.  Will be corrected to a multiple
  *                     of the page size.  The actual pool size will be size
  *                     rounded to a multiple of the (page_size), + page_size
  */
-TALLOC_CTX *talloc_page_aligned_pool(TALLOC_CTX *ctx, void **start, void **end, size_t size)
+TALLOC_CTX *talloc_page_aligned_pool(TALLOC_CTX *ctx, void **start, size_t *end_len, unsigned int headers, size_t size)
 {
        size_t          rounded, page_size = (size_t)getpagesize();
-       size_t          hdr_size, pool_size;
-       void            *next, *chunk;
+       size_t          hdr_size;
+       void            *next;
        TALLOC_CTX      *pool;
 
-       rounded = ROUND_UP(size, page_size);                    /* Round up to a multiple of the page size */
-       if (rounded == 0) rounded = page_size;
+       hdr_size = talloc_hdr_size();
+       size += (hdr_size * headers);   /* Add more space for the chunks headers of the pool's children */
+       size += hdr_size;               /* Add one more header to the pool for the padding allocation */
 
-       pool_size = rounded + page_size;
-       pool = talloc_pool(ctx, pool_size);                     /* Over allocate */
-       if (!pool) {
-               fr_strerror_const("Out of memory");
-               return NULL;
+       /*
+        *  Allocate enough space for the padding header the other headers
+        *  and the actual data, and sure it's a multiple of the page_size.
+        *
+        *   |<--- page_size --->|<-- page_size -->|
+        *   |<-- hdr_size -->|<-- size -->|
+        */
+       if (size % page_size == 0) {
+               rounded = size;
+       } else {
+               rounded = ROUND_UP(size, page_size);
        }
 
-       chunk = talloc_size(pool, 1);                           /* Get the starting address */
-       if (!fr_cond_assert((chunk > pool) && ((uintptr_t)chunk < ((uintptr_t)pool + rounded)))) {
-               fr_strerror_const("Initial allocation outside of pool memory");
-       error:
-               talloc_free(pool);
+       /*
+        *  Add another page, so that we can align the first allocation in
+        *  the pool to a page boundary.  We have no idea what chunk malloc
+        *  will give to talloc, and what the first address after adding the
+        *  pools header will be
+        */
+       rounded += page_size;
+
+       pool = talloc_pool(ctx, rounded);
+       if (!pool) {
+               fr_strerror_const("Out of memory");
                return NULL;
        }
-       hdr_size = (uintptr_t)chunk - (uintptr_t)pool;
-
-       next = (void *)ROUND_UP((uintptr_t)chunk, page_size);   /* Round up address to the next page */
 
        /*
-        *      Depending on how talloc allocates the chunk headers,
-        *      the memory allocated here might not align to a page
-        *      boundary, but that's ok, we just need future allocations
-        *      to occur on or after 'next'.
+        *  If we didn't get lucky, and the first address in the pool is
+        *  not the start of a page, we need to allocate some padding to
+        *  get the first allocation in the pool to be on or after the
+        *  start of the next page.
         */
-       if (((uintptr_t)next - (uintptr_t)chunk) > 0) {
+       if ((uintptr_t)pool % page_size != 0) {
                size_t  pad_size;
                void    *padding;
 
-               pad_size = ((uintptr_t)next - (uintptr_t)chunk);
+               next = (void *)ROUND_UP((uintptr_t)pool, page_size);    /* Round up address to the next page */
+
+               /*
+                *      We don't care if the first address if on a page
+                *      boundary, just that it comes after one.
+                */
+               pad_size = ((uintptr_t)next - (uintptr_t)pool);
                if (pad_size > hdr_size) {
                        pad_size -= hdr_size;                   /* Save ~111 bytes by not over-padding */
                } else {
-                       pad_size = 1;
+                       pad_size = 0;                           /* Allocate as few bytes as possible */
                }
 
                padding = talloc_size(pool, pad_size);
                if (!fr_cond_assert(((uintptr_t)padding + (uintptr_t)pad_size) >= (uintptr_t)next)) {
                        fr_strerror_const("Failed padding pool memory");
-                       goto error;
+                       return NULL;
                }
+       } else {
+               next = pool;
        }
 
-       *start = next;                                          /* This is the address we feed into mprotect */
-       *end = (void *)((uintptr_t)next + (uintptr_t)rounded);
+       *start = next;                  /* This is the address we feed into mprotect */
+       *end_len = rounded;             /* This is how much memory we protect */
 
        return pool;
 }
index 1d7cc13b2df104e1dda30f82c3a15c5816e72fc9..36b87cf73bc2d93c9162133de910911f101a4f6c 100644 (file)
@@ -132,7 +132,8 @@ void                talloc_destructor_disarm(fr_talloc_destructor_t *d);
 
 int            talloc_link_ctx(TALLOC_CTX *parent, TALLOC_CTX *child);
 
-TALLOC_CTX     *talloc_page_aligned_pool(TALLOC_CTX *ctx, void **start, void **end, size_t size);
+ssize_t                talloc_hdr_size(void);
+TALLOC_CTX     *talloc_page_aligned_pool(TALLOC_CTX *ctx, void **start, size_t *end_len, unsigned int headers, size_t size);
 TALLOC_CTX     *talloc_aligned_array(TALLOC_CTX *ctx, void **start, size_t alignment, size_t size);
 
 /*