]> xenbits.xensource.com Git - unikraft/unikraft.git/commitdiff
plat/common: Introduce ukplat_page_mapx()
authorMarc Rittinghaus <marc.rittinghaus@unikraft.io>
Thu, 2 Feb 2023 18:11:39 +0000 (19:11 +0100)
committerUnikraft <monkey@unikraft.io>
Fri, 3 Feb 2023 17:23:15 +0000 (17:23 +0000)
The current ukplat_page_map() functions allows to map a range of virtual
pages to physical ones with a defined set of protections. This function
has the following limitations that create problems in the context of
virtual memory management:
1) If a mapping the virtual range already exists, the function
aborts with an error. The caller has no knowledge about the virtual
address that caused the error.
2) If the page attributes are set to read only while physical memory
should be allocated at the same time with paddr=__PADDR_ANY, the
caller has no chance of initializing the frame contents.

This commit replaces ukplat_page_map() with ukplat_page_mapx()
with the x being execute/extended. The mapx version allows the caller
to specify a function that should be called during mapping before
writing a PTE. The function can modify the PTE, instruct the mapping to
skip the current page, or switch to a smaller page size. The original
function behavior is provided when the mapx argument is NULL.

The mapx function can be used in case 1) to either overwrite or skip
existing mappings. The mapx function can also be used together with
a temporary mapping to initialize the physical memory of the current
page before establishing the mapping. This way, we avoid
map/rw -> remap/ro windows, which can also pose a security problem
besides the runtime overhead. If the caller sets paddr=0, it is also
possible to completely leave physical memory allocation to the mapx
function, for example, to serve memory from a cache.

For compatibility, a wrapper for ukplat_page_map() is provided.

Signed-off-by: Marc Rittinghaus <marc.rittinghaus@unikraft.io>
Reviewed-by: Marco Schlumpp <marco@unikraft.io>
Approved-by: Michalis Pappas <michalis.pappas@opensynergy.com>
Tested-by: Unikraft CI <monkey@unikraft.io>
GitHub-Closes: #603

include/uk/plat/paging.h
plat/common/paging.c

index 8c9c6f084a9221e7c3124975f6f8cec41b822f99..5ab2965b8ae059fc3d2ed4a65fdd419d9a2f76a7 100644 (file)
@@ -184,35 +184,122 @@ int ukplat_pt_free(struct uk_pagetable *pt, unsigned long flags);
 int ukplat_pt_walk(struct uk_pagetable *pt, __vaddr_t vaddr,
                   unsigned int *level, __vaddr_t *pt_vaddr, __pte_t *pte);
 
+/* Forward declaration */
+struct ukplat_page_mapx;
+
+/**
+ * Page mapper function that allows controlling the mappings that are created
+ * in a call to ukplat_page_mapx().
+ *
+ * @param pt
+ *   The page table that the mapping is done in
+ * @param vaddr
+ *   The virtual address for which a mapping is established
+ * @param pt_vaddr
+ *   The virtual address of the actual hardware page table that is modified.
+ *   Use this to retrieve the current PTE, if needed.
+ * @param level
+ *   The page table level at which the mapping will be created
+ * @param[in,out] pte
+ *   The new PTE that will be set. The handler may freely modify the PTE to
+ *   control the mapping.
+ * @param ctx
+ *   An optional user-supplied context
+ *
+ * @return
+ *   - 0 on success (i.e., the PTE should be applied)
+ *   - a negative error code to indicate a fatal error
+ *   - UKPLAT_PAGE_MAPX_ESKIP to skip the current PTE (do not apply the changes)
+ *   - UKPLAT_PAGE_MAPX_ETOOBIG to indicate that the mapping should be retried
+ *     using a smaller page size
+ */
+typedef int (*ukplat_page_mapx_func_t)(struct uk_pagetable *pt,
+                                      __vaddr_t vaddr, __vaddr_t pt_vaddr,
+                                      unsigned int level, __pte_t *pte,
+                                      void *ctx);
+
+/* Page mapper function return codes */
+#define UKPLAT_PAGE_MAPX_ESKIP         1 /* Skip the current PTE */
+#define UKPLAT_PAGE_MAPX_ETOOBIG       2 /* Retry with smaller page */
+
+struct ukplat_page_mapx {
+       /** Handler called before updating the PTE in the page table */
+       ukplat_page_mapx_func_t map;
+       /** Optional user context */
+       void *ctx;
+};
+
 /**
- * Creates a mapping from a range of continuous virtual addresses to a range of
+ * Creates a mapping from a range of contiguous virtual addresses to a range of
  * physical addresses using the specified attributes.
  *
- * @param pt the page table instance on which to operate.
- * @param vaddr the virtual address of the first page in the new mapping.
- * @param paddr the physical address of the memory which the virtual region is
- *   mapped to. This parameter can be __PADDR_ANY to dynamically allocate
- *   physical memory as needed.
- * @param pages the number of pages in requested page size to map.
- * @param attr page attributes to set for the new mapping (PAGE_ATTR_* flags).
- * @param flags page flags (PAGE_FLAG_* flags). The page size can be specified
- *   with PAGE_FLAG_SIZE(). If PAGE_FLAG_FORCE_SIZE is not specified, the
- *   function tries to map the given range (i.e., pages * requested page size)
- *   using the largest possible pages. The actual mapping thus may use larger
- *   or smaller pages than requested depending on address alignment, supported
- *   page sizes, and available continuous physical memory (if paddr is
- *   __PADDR_ANY).
+ * @param pt
+ *   The page table instance on which to operate
+ * @param vaddr
+ *   The virtual address of the first page in the new mapping
+ * @param paddr
+ *   The physical address of the memory which the virtual region is mapped to.
+ *   This parameter can be __PADDR_ANY to dynamically allocate physical memory
+ *   as needed. Note that, the physical memory might not be contiguous.
  *
- * @return 0 on success, a non-zero value otherwise. May fail if:
- *   - the physical or virtual address is not aligned to the page size;
- *   - a page in the region is already mapped;
- *   - a page table could not be set up;
- *   - if __PADDR_ANY flag is set and there are no more free frames;
+ *   paddr should be 0 if physical addresses should be handled by the mapx page
+ *   mapper.
+ * @param pages
+ *   The number of pages in requested page size to map
+ * @param attr
+ *   Page attributes to set for the new mapping (see PAGE_ATTR_* flags)
+ * @param flags
+ *   Page flags (see PAGE_FLAG_* flags)
+ *
+ *   The page size can be specified with PAGE_FLAG_SIZE(). If
+ *   PAGE_FLAG_FORCE_SIZE is not specified, the function tries to map the given
+ *   range (i.e., pages * requested page size) using the largest possible pages.
+ *   The actual mapping thus may use larger or smaller pages than requested
+ *   depending on address alignment, supported page sizes, and, if paddr is
+ *   __PADDR_ANY, the available contiguous physical memory. If
+ *   PAGE_FLAG_FORCE_SIZE is specified, only mappings of the given page size
+ *   are created.
+ *
+ *   If PAGE_FLAG_KEEP_PTES is specified, the new mapping will incorporate the
+ *   PTEs currently present in the page table. The physical address and
+ *   permission flags will be updated according to the new mapping.
+ * @param mapx
+ *   Optional page mapper object. If the page mapper is supplied, it is called
+ *   right before applying a new PTE, giving the mapper a chance to affect the
+ *   mapping. Depending on the return code of the mapper it is also possible to
+ *   skip the current PTE or force a smaller page size (if PAGE_FLAG_FORCE_SIZE
+ *   is not set). Note that the page mapper is not called for PTEs referencing
+ *   other page tables.
+ *
+ *   If paddr is __PADDR_ANY, the PTE supplied to the mapper will point to newly
+ *   allocated physical memory that can be initialized before becoming visible.
+ *   Use PT_Lx_PTE_PADDR() to retrieve the physical address from the PTE and
+ *   ukplat_page_kmap() to temporarily map the physical memory. Note that, if
+ *   the PTE currently present in the page table already points to a valid
+ *   mapping (i.e., PT_Lx_PTE_PRESENT() returns a non-zero value), no new
+ *   physical memory will be allocated. Instead, the physical address will
+ *   remain unchanged. It is the mapper's responsibilty to properly free the
+ *   referenced physical memory, if the physical address is changed.
+ *
+ *   Before calling the page mapper, existing large pages may be split up if a
+ *   smaller page size is enforced.
+ *
+ * @return
+ *   0 on success, a non-zero value otherwise. May fail if:
+ *   - the physical or virtual address is not aligned to the page size
+ *   - a page in the region is already mapped and no mapx is supplied
+ *   - a page table could not be set up
+ *   - if __PADDR_ANY flag is set and there are no more free frames
  *   - the platform rejected the operation
+ *   - the mapx page mapper returned a fatal error
  */
-int ukplat_page_map(struct uk_pagetable *pt, __vaddr_t vaddr,
-                   __paddr_t paddr, unsigned long pages,
-                   unsigned long attr, unsigned long flags);
+int ukplat_page_mapx(struct uk_pagetable *pt, __vaddr_t vaddr,
+                    __paddr_t paddr, unsigned long pages,
+                    unsigned long attr, unsigned long flags,
+                    struct ukplat_page_mapx *mapx);
+
+#define ukplat_page_map(pt, va, pa, pages, attr, flags)                        \
+       ukplat_page_mapx(pt, va, pa, pages, attr, flags, __NULL)
 
 /**
  * Removes the mappings from a range of continuous virtual addresses and frees
index 227902d4731f9ce60f919e4232683b2d9f6cda5e..b306016a377dae46111ac236144bf5577a800c5e 100644 (file)
@@ -64,6 +64,9 @@ static inline int pg_pt_alloc(struct uk_pagetable *pt, __vaddr_t *pt_vaddr,
 static inline void pg_pt_free(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
                              unsigned int level);
 
+static int pg_page_split(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
+                        __vaddr_t vaddr, unsigned int level);
+
 static int pg_page_unmap(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
                         unsigned int level, __vaddr_t vaddr, __sz len,
                         unsigned long flags);
@@ -504,10 +507,11 @@ static inline int pg_largest_level(__vaddr_t vaddr, __paddr_t paddr, __sz len,
        return lvl;
 }
 
-static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
-                      unsigned int level, __vaddr_t vaddr, __paddr_t paddr,
-                      __sz len, unsigned long attr, unsigned long flags,
-                      __pte_t template, unsigned int template_level)
+static int pg_page_mapx(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
+                       unsigned int level, __vaddr_t vaddr, __paddr_t paddr,
+                       __sz len, unsigned long attr, unsigned long flags,
+                       __pte_t template, unsigned int template_level,
+                       struct ukplat_page_mapx *mapx)
 {
        unsigned int to_lvl = PAGE_FLAG_SIZE_TO_LEVEL(flags);
        unsigned int max_lvl = pg_page_largest_level;
@@ -515,7 +519,7 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
        unsigned int tmp_lvl;
        __vaddr_t pt_vaddr_cache[PT_LEVELS];
        __paddr_t pt_paddr;
-       __pte_t pte;
+       __pte_t pte, orig_pte;
        __sz page_size;
        unsigned int pte_idx;
        int rc, alloc_pmem;
@@ -560,10 +564,22 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
 
                        if (PT_Lx_PTE_PRESENT(pte, lvl)) {
                                /* If there is already a larger page mapped
-                                * at this address return an error.
+                                * at this address and we have a mapx, we
+                                * split the page until we reach the target
+                                * level and let the mapx decide what to do.
+                                * Otherwise, we bail out.
                                 */
-                               if (PAGE_Lx_IS(pte, lvl))
-                                       return -EEXIST;
+                               if (PAGE_Lx_IS(pte, lvl)) {
+                                       if (!mapx)
+                                               return -EEXIST;
+
+                                       rc = pg_page_split(pt, pt_vaddr, vaddr,
+                                                          lvl);
+                                       if (unlikely(rc))
+                                               return rc;
+
+                                       continue;
+                               }
 
                                pt_vaddr = pgarch_pt_pte_to_vaddr(pt, pte, lvl);
                        } else {
@@ -609,6 +625,8 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
                if (unlikely(rc))
                        return rc;
 
+               orig_pte = pte;
+
                if (PT_Lx_PTE_PRESENT(pte, lvl)) {
                        /* It could be that there is a page table linked at
                         * this PTE. In this case, we descent further down to
@@ -624,17 +642,22 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
                                continue;
                        }
 
-                       return -EEXIST;
-               }
+                       /* This is not a page table. However, we are on the
+                        * correct level. If we have a mapping function we let
+                        * the mapping function decide what we should do with
+                        * the existing mapping. Otherwise, bail out.
+                        */
+                       if (!mapx)
+                               return -EEXIST;
 
-               UK_ASSERT(!PT_Lx_PTE_PRESENT(pte, lvl));
+                       paddr = PT_Lx_PTE_PADDR(pte, lvl);
+               } else if (alloc_pmem) {
+                       UK_ASSERT(!PT_Lx_PTE_PRESENT(pte, lvl));
 
-               /* This is a non-present PTE so we can map the new page here */
-               if (alloc_pmem) {
                        paddr = __PADDR_ANY;
-
                        rc = pg_falloc(pt, &paddr, lvl);
-                       if (rc) {
+                       if (unlikely(rc)) {
+TOO_BIG:
                                /* We could not allocate a contiguous,
                                 * self-aligned block of physical memory with
                                 * the requested size. If we should map largest
@@ -654,6 +677,10 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
 
                                continue;
                        }
+
+                       /* If something goes wrong in the following, we must
+                        * free the physical memory!
+                        */
                }
 
                UK_ASSERT(PAGE_Lx_ALIGNED(vaddr, lvl));
@@ -665,9 +692,40 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
                        template_level = lvl;
 
                pte = pgarch_pte_create(paddr, attr, lvl, pte, template_level);
+
+               if (mapx) {
+                       /* pte will always be prepared with the PTE that we
+                        * intend to write if the mapx returns success.
+                        * Accessing the current PTE can be done by reading it
+                        * from the page table.
+                        */
+                       UK_ASSERT(mapx->map);
+                       rc = mapx->map(pt, vaddr, pt_vaddr, lvl, &pte,
+                                      mapx->ctx);
+                       if (unlikely(rc)) {
+                               if (alloc_pmem &&
+                                   !PT_Lx_PTE_PRESENT(orig_pte, lvl))
+                                       pg_ffree(pt, paddr, lvl);
+
+                               if (rc == UKPLAT_PAGE_MAPX_ESKIP)
+                                       goto NEXT_PTE;
+
+                               if (rc == UKPLAT_PAGE_MAPX_ETOOBIG) {
+                                       rc = -ENOMEM;
+                                       goto TOO_BIG;
+                               }
+
+                               UK_ASSERT(rc < 0);
+                               return rc;
+                       }
+               }
+
+               UK_ASSERT(PAGE_Lx_ALIGNED(PT_Lx_PTE_PADDR(pte, lvl), lvl));
+
                rc = ukarch_pte_write(pt_vaddr, lvl, pte_idx, pte);
                if (unlikely(rc)) {
-                       if (alloc_pmem)
+                       if (alloc_pmem &&
+                           !PT_Lx_PTE_PRESENT(orig_pte, lvl))
                                pg_ffree(pt, paddr, lvl);
 
                        return rc;
@@ -678,6 +736,10 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
                        pt->nr_lx_pages[lvl]++;
 #endif /* CONFIG_PAGING_STATS */
 
+               if (PT_Lx_PTE_PRESENT(orig_pte, lvl))
+                       ukarch_tlb_flush_entry(vaddr);
+
+NEXT_PTE:
                UK_ASSERT(len >= page_size);
                len -= page_size;
 
@@ -754,9 +816,10 @@ static int pg_page_map(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
        return 0;
 }
 
-int ukplat_page_map(struct uk_pagetable *pt, __vaddr_t vaddr,
-                   __paddr_t paddr, unsigned long pages,
-                   unsigned long attr, unsigned long flags)
+int ukplat_page_mapx(struct uk_pagetable *pt, __vaddr_t vaddr,
+                    __paddr_t paddr, unsigned long pages,
+                    unsigned long attr, unsigned long flags,
+                    struct ukplat_page_mapx *mapx)
 {
        unsigned int level = PAGE_FLAG_SIZE_TO_LEVEL(flags);
        __sz len;
@@ -777,9 +840,9 @@ int ukplat_page_map(struct uk_pagetable *pt, __vaddr_t vaddr,
        UK_ASSERT(pt->pt_vbase != __VADDR_INV);
        UK_ASSERT(pt->pt_pbase != __PADDR_INV);
 
-       return pg_page_map(pt, pt->pt_vbase, PT_LEVELS - 1, vaddr, paddr, len,
-                          attr, flags, PT_Lx_PTE_INVALID(PAGE_LEVEL),
-                          PAGE_LEVEL);
+       return pg_page_mapx(pt, pt->pt_vbase, PT_LEVELS - 1, vaddr, paddr, len,
+                           attr, flags, PT_Lx_PTE_INVALID(PAGE_LEVEL),
+                           PAGE_LEVEL, mapx);
 }
 
 static int pg_page_split(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
@@ -826,8 +889,8 @@ static int pg_page_split(struct uk_pagetable *pt, __vaddr_t pt_vaddr,
         * contiguous range of physical memory than the input page
         */
        paddr = PT_Lx_PTE_PADDR(pte, level);
-       rc = pg_page_map(pt, new_pt_vaddr, level - 1, vaddr, paddr,
-                        PAGE_Lx_SIZE(level), attr, flags, pte, level);
+       rc = pg_page_mapx(pt, new_pt_vaddr, level - 1, vaddr, paddr,
+                         PAGE_Lx_SIZE(level), attr, flags, pte, level, NULL);
        if (unlikely(rc))
                goto EXIT_FREE;