diff mbox series

[12/12] dlfcn,elf: impl DLMEM_GENBUF_SRC dlmem() flag

Message ID 20230403090421.560208-13-stsp2@yandex.ru
State New
Headers show
Series implement dlmem() function | expand

Commit Message

stsp April 3, 2023, 9:04 a.m. UTC
This flag allows to use a generic unaligned memory buffer or a
private anonymous mapping as a source. It should not be preferred
over a file-backed mapping when possible, but the "bad" cases also
needs to be supported.

New tests added to tst-dlmem-shm test-case.

The test-suite was run on x86_64/64 and showed no regressions.

Signed-off-by: Stas Sergeev <stsp2@yandex.ru>
---
 dlfcn/dlfcn.h         |  4 ++++
 dlfcn/dlmem.c         |  6 +++++-
 dlfcn/tst-dlmem-shm.c | 37 ++++++++++++++++++++++++++++++++++++-
 elf/dl-load.c         |  6 ++++++
 4 files changed, 51 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/dlfcn/dlfcn.h b/dlfcn/dlfcn.h
index 7aa9d7d3cf..976d93a464 100644
--- a/dlfcn/dlfcn.h
+++ b/dlfcn/dlfcn.h
@@ -76,6 +76,10 @@  typedef void *
 /* Do not replace mapping created by premap callback.
    dlmem() will then use memcpy(). */
 #define DLMEM_DONTREPLACE 1
+/* Treat source memory buffer as a generic unaligned buffer, rather
+   than a file-backed or anonymously-shared mapping. Anonymous private
+   mapping also needs this flag to be set. */
+#define DLMEM_GENBUF_SRC 2
 
 struct dlmem_args {
   /* Optional name to associate with the loaded object. */
diff --git a/dlfcn/dlmem.c b/dlfcn/dlmem.c
index d59eb99ec1..fc8facb6d2 100644
--- a/dlfcn/dlmem.c
+++ b/dlfcn/dlmem.c
@@ -40,12 +40,16 @@  static void
 dlmem_doit (void *a)
 {
   struct _dlmem_args *args = (struct _dlmem_args *) a;
+  const struct dlmem_args *dlm_args = args->args;
 
   if (args->mode & ~(RTLD_BINDING_MASK | RTLD_NOLOAD | RTLD_DEEPBIND
 		     | RTLD_GLOBAL | RTLD_LOCAL | RTLD_NODELETE
 		     | __RTLD_SPROF))
     _dl_signal_error (EINVAL, NULL, NULL, _("invalid mode parameter"));
-  if ((uintptr_t) args->buffer & (GLRO(dl_pagesize) - 1))
+
+  /* Unaligned buffer is only permitted when DLMEM_GENBUF_SRC flag set. */
+  if (((uintptr_t) args->buffer & (GLRO(dl_pagesize) - 1))
+      && (!dlm_args || !(dlm_args->flags & DLMEM_GENBUF_SRC)))
     _dl_signal_error (EINVAL, NULL, NULL, _("buffer not aligned"));
 
   args->new = GLRO(dl_mem) (args->buffer, args->size,
diff --git a/dlfcn/tst-dlmem-shm.c b/dlfcn/tst-dlmem-shm.c
index 7899dfc909..16d13f16d6 100644
--- a/dlfcn/tst-dlmem-shm.c
+++ b/dlfcn/tst-dlmem-shm.c
@@ -70,10 +70,11 @@  do_test (void)
   int fd;
   int num;
   off_t len;
+  off_t orig_len;
   struct link_map *lm;
   const char *shm_name = "/tst-dlmem";
   int shm_fd;
-  struct dlmem_args a;
+  struct dlmem_args a = {};
 
   shm_fd = memfd_create (shm_name, 0);
   if (shm_fd == -1)
@@ -84,9 +85,43 @@  do_test (void)
     error (EXIT_FAILURE, 0, "cannot open: glreflib1.so");
   len = lseek (fd, 0, SEEK_END);
   lseek (fd, 0, SEEK_SET);
+  /* For the sake of testing add extra space. */
+  orig_len = len;
+  len += 4096;
   addr = mmap (NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
   if (addr == MAP_FAILED)
     error (EXIT_FAILURE, 0, "cannot mmap: glreflib1.so");
+
+  /* Try unaligned buffer. */
+  handle = dlmem (addr + 4, len, RTLD_NOW | RTLD_LOCAL, NULL);
+  TEST_VERIFY (handle == NULL);
+  /* errno is set by dlerror() so needs to print something. */
+  printf ("unaligned buf gives %s\n", dlerror ());
+  TEST_COMPARE (errno, EINVAL);
+  /* Try allow unaligned buffer but not at the beginning of solib. */
+  a.flags = DLMEM_GENBUF_SRC;
+  handle = dlmem (addr + 4, len, RTLD_NOW | RTLD_LOCAL, &a);
+  TEST_VERIFY (handle == NULL);
+  printf ("non-elf data gives %s\n", dlerror ());
+  TEST_COMPARE (errno, EINVAL);
+  /* Try allow unaligned buffer but with good solib. */
+  mprotect (addr, len, PROT_READ | PROT_WRITE);
+  memmove (addr + 4, addr, orig_len);
+  /* Forgot to allow unaligned buffer. */
+  handle = dlmem (addr + 4, len, RTLD_NOW | RTLD_LOCAL, NULL);
+  TEST_VERIFY (handle == NULL);
+  /* Should now be well. */
+  handle = dlmem (addr + 4, len, RTLD_NOW | RTLD_LOCAL, &a);
+  TEST_VERIFY (handle != NULL);
+
+  /* Lets do this all again, now for real. */
+  dlclose (handle);
+  munmap (addr, len);
+  len = orig_len;
+  addr = mmap (NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (addr == MAP_FAILED)
+    error (EXIT_FAILURE, 0, "cannot mmap: glreflib1.so");
+
   a.soname = "glreflib1.so";
   a.flags = DLMEM_DONTREPLACE;
   a.nsid = LM_ID_BASE;
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 422c03459b..002afbee3f 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -2382,6 +2382,7 @@  do_memremap (void *addr, size_t length, int prot, int flags,
              void *arg, off_t offset)
 {
   const struct dlmem_fbuf *fb = arg;
+  const struct dlmem_args *dlm_args = fb->dlm_args;
   size_t to_copy = 0;
 
   assert (flags & MAP_FIXED);
@@ -2390,6 +2391,11 @@  do_memremap (void *addr, size_t length, int prot, int flags,
   if (flags & MAP_ANONYMOUS)
     return __mmap (addr, length, prot, flags, -1, 0);
 
+  /* With DLMEM_GENBUF_SRC flag, everything but anonymous mmaps goes
+     to memcpy. */
+  if (dlm_args && (dlm_args->flags & DLMEM_GENBUF_SRC))
+    return do_mmapcpy(addr, length, prot, flags, arg, offset);
+
   if (offset < fb->len)
     {
       to_copy = length;