<sys/mman.h> has functions for creating memory areas with a given protection,
and mprotect() for changing the protection. But it has no API for returning
the protection of an area!

This new module fills that gap.

Not by trying to write or execute the given memory (because that would
make side effects, and because establishing a SIGSEGV / SIGBUS handler
is not multithread-safe), but instead by walking through the VMAs.


2024-08-29  Bruno Haible  <br...@clisp.org>

        vma-prot: Add tests.
        * tests/test-vma-prot.c: New file.
        * modules/vma-prot-tests: New file.

        vma-prot: New module.
        * lib/vma-prot.h: New file.
        * lib/vma-prot.c: New file.
        * modules/vma-prot: New file.

>From 0adfb2bff3c39bc98f3f52a7f295f3aef246eb0d Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 29 Aug 2024 22:48:52 +0200
Subject: [PATCH 1/2] vma-prot: New module.

* lib/vma-prot.h: New file.
* lib/vma-prot.c: New file.
* modules/vma-prot: New file.
---
 ChangeLog        |  7 ++++
 lib/vma-prot.c   | 86 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/vma-prot.h   | 50 ++++++++++++++++++++++++++++
 modules/vma-prot | 24 ++++++++++++++
 4 files changed, 167 insertions(+)
 create mode 100644 lib/vma-prot.c
 create mode 100644 lib/vma-prot.h
 create mode 100644 modules/vma-prot

diff --git a/ChangeLog b/ChangeLog
index 922aba9e8b..709509e07a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2024-08-29  Bruno Haible  <br...@clisp.org>
+
+	vma-prot: New module.
+	* lib/vma-prot.h: New file.
+	* lib/vma-prot.c: New file.
+	* modules/vma-prot: New file.
+
 2024-08-29  Bruno Haible  <br...@clisp.org>
 
 	vma-iter: Relicense under GPLv2+.
diff --git a/lib/vma-prot.c b/lib/vma-prot.c
new file mode 100644
index 0000000000..ebf9699a61
--- /dev/null
+++ b/lib/vma-prot.c
@@ -0,0 +1,86 @@
+/* Determine the protection of a virtual memory area.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file 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 2 of the License,
+   or (at your option) any later version.
+
+   This file 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 <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "vma-prot.h"
+
+#include <stdint.h>
+
+struct locals
+{
+  /* The common protections seen so far.  */
+  unsigned int prot_so_far;
+  /* The remaining address interval.
+     remaining_start <= remaining_end-1.  */
+  uintptr_t remaining_start;
+  uintptr_t remaining_end;
+};
+
+static int
+callback (void *data, uintptr_t start, uintptr_t end, unsigned int flags)
+{
+  struct locals *l = (struct locals *) data;
+
+  if (start > l->remaining_start)
+    {
+      /* The interval [remaining_start, min (remaining_end, start)) is
+         unmapped.  */
+      l->prot_so_far = 0;
+      return 1;
+    }
+  if (end - 1 < l->remaining_start)
+    /* The interval [start,end) lies before [remaining_start,remaining_end).  */
+    return 0;
+  /* Here  start <= remaining_start <= remaining_end-1
+     and            remaining_start <= end-1,
+     hence start <= remaining_start <= min (end-1, remaining_end-1).
+     So, the two intervals intersect.  */
+  l->prot_so_far &= flags;
+  if (l->prot_so_far == 0)
+    return 1;
+  if (l->remaining_end - 1 <= end - 1)
+    /* Done.  */
+    return 1;
+  /* Trim the remaining address interval.  */
+  l->remaining_start = end;
+  return 0;
+}
+
+int
+get_vma_prot (void *start, size_t size)
+{
+  uintptr_t start_address = (uintptr_t) start;
+
+  struct locals l;
+  l.prot_so_far = VMA_PROT_READ | VMA_PROT_WRITE | VMA_PROT_EXECUTE;
+  l.remaining_start = start_address;
+  l.remaining_end = start_address + size;
+
+  if (l.remaining_end < l.remaining_start)
+    /* Invalid arguments.  */
+    return -1;
+
+  if (l.remaining_end > l.remaining_start)
+    {
+      if (vma_iterate (callback, &l) < 0)
+        return -1;
+    }
+  return l.prot_so_far;
+}
diff --git a/lib/vma-prot.h b/lib/vma-prot.h
new file mode 100644
index 0000000000..aa99048ffe
--- /dev/null
+++ b/lib/vma-prot.h
@@ -0,0 +1,50 @@
+/* Determine the protection of a virtual memory area.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file 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 2 of the License,
+   or (at your option) any later version.
+
+   This file 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 <br...@clisp.org>, 2024.  */
+
+#ifndef _VMA_PROT_H
+#define _VMA_PROT_H
+
+/* Get size_t.  */
+#include <stddef.h>
+
+/* Get VMA_PROT_READ, VMA_PROT_WRITE, VMA_PROT_EXECUTE.  */
+#include "vma-iter.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Returns the declared permissions available on the memory area that
+   starts at START and is SIZE bytes long, as a combination of the bit masks
+   VMA_PROT_READ, VMA_PROT_WRITE, VMA_PROT_EXECUTE.
+   Note: The effective permissions may be larger.  For example, some hardware
+   allows execute permission anywhere where read permission is present.
+   Returns -1 if it cannot be determined.
+
+   Note: This function is expensive.  If possible, an application should find
+   faster alternatives for memory areas that it has allocated itself, such as
+   through malloc(), mmap(), or shmat().  */
+extern int get_vma_prot (void *start, size_t size);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _VMA_PROT_H */
diff --git a/modules/vma-prot b/modules/vma-prot
new file mode 100644
index 0000000000..5ff9277535
--- /dev/null
+++ b/modules/vma-prot
@@ -0,0 +1,24 @@
+Description:
+Determine the protection of a virtual memory area.
+
+Files:
+lib/vma-prot.h
+lib/vma-prot.c
+
+Depends-on:
+stdint
+vma-iter
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += vma-prot.c
+
+Include:
+"vma-prot.h"
+
+License:
+GPLv2+
+
+Maintainer:
+all
-- 
2.34.1

>From 389788a7ee44b1a0dfa8bc479967929c0ccfa684 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 29 Aug 2024 22:52:02 +0200
Subject: [PATCH 2/2] vma-prot: Add tests.

* tests/test-vma-prot.c: New file.
* modules/vma-prot-tests: New file.
---
 ChangeLog              |   4 ++
 modules/vma-prot-tests |  13 +++++
 tests/test-vma-prot.c  | 107 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 124 insertions(+)
 create mode 100644 modules/vma-prot-tests
 create mode 100644 tests/test-vma-prot.c

diff --git a/ChangeLog b/ChangeLog
index 709509e07a..02a11b3629 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2024-08-29  Bruno Haible  <br...@clisp.org>
 
+	vma-prot: Add tests.
+	* tests/test-vma-prot.c: New file.
+	* modules/vma-prot-tests: New file.
+
 	vma-prot: New module.
 	* lib/vma-prot.h: New file.
 	* lib/vma-prot.c: New file.
diff --git a/modules/vma-prot-tests b/modules/vma-prot-tests
new file mode 100644
index 0000000000..61d060133d
--- /dev/null
+++ b/modules/vma-prot-tests
@@ -0,0 +1,13 @@
+Files:
+tests/test-vma-prot.c
+tests/macros.h
+m4/mmap-anon.m4
+
+Depends-on:
+
+configure.ac:
+gl_FUNC_MMAP_ANON
+
+Makefile.am:
+TESTS += test-vma-prot
+check_PROGRAMS += test-vma-prot
diff --git a/tests/test-vma-prot.c b/tests/test-vma-prot.c
new file mode 100644
index 0000000000..24b5b1873a
--- /dev/null
+++ b/tests/test-vma-prot.c
@@ -0,0 +1,107 @@
+/* Test of getting protection of a virtual memory area.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file 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 file 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 <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+#include "vma-prot.h"
+
+#include <stdlib.h>
+
+#if HAVE_MAP_ANONYMOUS
+# include <sys/mman.h>
+# include <unistd.h>
+#endif
+
+#include "macros.h"
+
+int dummy;
+
+int
+f (int x)
+{
+  return x;
+}
+
+int
+main (void)
+{
+  int prot;
+
+  /* Test on memory allocated through malloc().  */
+  {
+    char *mem = malloc (10);
+    prot = get_vma_prot (mem + 2, 5);
+    ASSERT (prot != -1);
+    ASSERT (((VMA_PROT_READ | VMA_PROT_WRITE) & ~prot) == 0);
+  }
+  {
+    char *mem = malloc (1000000);
+    prot = get_vma_prot (mem, 1000000);
+    ASSERT (prot != -1);
+    ASSERT (((VMA_PROT_READ | VMA_PROT_WRITE) & ~prot) == 0);
+  }
+
+#if HAVE_MAP_ANONYMOUS
+  /* Test on memory allocated through mmap().  */
+  {
+    char *mem = mmap (NULL, 1024*1024, PROT_READ | PROT_WRITE,
+                      MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+    if (mem != (char *)(-1))
+      {
+        prot = get_vma_prot (mem, 1024*1024);
+        ASSERT (prot != -1);
+        ASSERT (prot == VMA_PROT_READ | VMA_PROT_WRITE);
+
+        size_t pagesize = sysconf (_SC_PAGESIZE);
+        if (pagesize <= 512*1024
+            && munmap (mem + pagesize, pagesize) >= 0)
+          {
+            prot = get_vma_prot (mem, 1024*1024);
+            ASSERT (prot != -1);
+            ASSERT (prot == 0);
+          }
+      }
+  }
+#endif
+
+  /* Test on a global variable.  */
+  prot = get_vma_prot (&dummy, sizeof (dummy));
+  ASSERT (prot != -1);
+  ASSERT (((VMA_PROT_READ | VMA_PROT_WRITE) & ~prot) == 0);
+
+  /* Test on a function.  */
+  prot = get_vma_prot (&f, 1);
+  ASSERT (prot != -1);
+#if (defined __hppa || defined __hppa64__ \
+     || defined __ia64__ \
+     || defined _AIX \
+     || (defined __powerpc64__ && defined __linux__ && _CALL_ELF != 2))
+  /* On these platforms, a function pointer is actually a pointer to
+     a struct of pointers.  */
+#else
+  /* On these platforms, a function pointer is a pointer to or into some
+     machine instruction.  */
+  ASSERT ((VMA_PROT_EXECUTE & ~prot) == 0);
+#endif
+#if !defined __OpenBSD__
+  /* Except on OpenBSD/arm64, the machine instructions are also readable.  */
+  ASSERT ((VMA_PROT_READ & ~prot) == 0);
+#endif
+
+  return test_exit_status;
+}
-- 
2.34.1

Reply via email to