diff --git a/src/context/memory.rs b/src/context/memory.rs
index c23d8dfa98fc8fbbe34ebfd765bb1ad1ce081e30..68ebe472fa37b46baa52f69bfd4582eb00f8bcbf 100644
--- a/src/context/memory.rs
+++ b/src/context/memory.rs
@@ -205,11 +205,16 @@ impl AddrSpace {
         }
     }
     pub fn mmap(&mut self, page: Option<Page>, page_count: NonZeroUsize, flags: MapFlags, map: impl FnOnce(Page, PageFlags<RmmA>, &mut PageMapper, &mut dyn Flusher<RmmA>) -> Result<Grant>) -> Result<Page> {
+        self.mmap_multiple(page, page_count, flags, move |page, flags, mapper, flusher| Ok(Some(map(page, flags, mapper, flusher)?)))
+    }
+    pub fn mmap_multiple<I: IntoIterator<Item = Grant>>(&mut self, page: Option<Page>, page_count: NonZeroUsize, flags: MapFlags, map: impl FnOnce(Page, PageFlags<RmmA>, &mut PageMapper, &mut dyn Flusher<RmmA>) -> Result<I>) -> Result<Page> {
         // Finally, the end of all "T0DO: Abstract with other grant creation"!
         let selected_span = self.grants.find_free_at(self.mmap_min, page, page_count.get(), flags)?;
 
         // TODO: Threads share address spaces, so not only the inactive flusher should be sending
-        // out IPIs.
+        // out IPIs. IPIs will only be sent when downgrading mappings (i.e. when a stale TLB entry
+        // will not be corrected by a page fault), and will furthermore require proper
+        // synchronization.
         let (mut active, mut inactive);
         let flusher = if self.is_current() {
             active = PageFlushAll::new();
@@ -219,7 +224,10 @@ impl AddrSpace {
             &mut inactive as &mut dyn Flusher<RmmA>
         };
 
-        self.grants.insert(map(selected_span.base, page_flags(flags), &mut self.table.utable, flusher)?);
+        let iter = map(selected_span.base, page_flags(flags), &mut self.table.utable, flusher)?;
+        for grant in iter {
+            self.grants.insert(grant);
+        }
 
         Ok(selected_span.base)
     }
@@ -401,6 +409,7 @@ impl UserGrants {
                 return Err(Error::new(EEXIST));
             }
             if flags.contains(MapFlags::MAP_FIXED) {
+                // TODO: find_free_at -> Result<(PageSpan, needs_to_unmap: PageSpan)>
                 return Err(Error::new(EOPNOTSUPP));
             } else {
                 // TODO: Find grant close to requested address?
@@ -703,7 +712,13 @@ impl Grant {
         })
     }
 
-    // TODO: Return multiple grants.
+    // TODO: Do not return Vec, return an iterator perhaps? Referencing the source address space?
+
+    /// Borrow all pages in the range `[src_base, src_base+page_count)` from `src_address_space`,
+    /// mapping them into `[dst_base, dst_base+page_count)`. While the pages are borrowed,
+    /// subsequent mappings/mprotects/etc. will not be visible in the destination address space;
+    /// the *pages present at that time* are borrowed, rather than the source range permanently, by
+    /// reference.
     pub fn borrow(
         src_address_space_lock: Arc<RwLock<AddrSpace>>,
         src_address_space: &AddrSpace,
@@ -714,7 +729,7 @@ impl Grant {
         dst_mapper: &mut PageMapper,
         dst_flusher: impl Flusher<RmmA>,
         eager: bool,
-    ) -> Result<Grant, Enomem> {
+    ) -> Result<Vec<Grant>, Enomem> {
         /*
         if eager {
             for page in PageSpan::new(src_base, page_count) {
@@ -723,20 +738,40 @@ impl Grant {
         }
         */
 
-        Ok(Grant {
-            base: dst_base,
-            info: GrantInfo {
-                page_count,
-                flags,
-                mapped: true,
-                provider: Provider::External {
-                    src_base,
-                    address_space: src_address_space_lock,
-                    cow: false,
-                    pages: None,
-                }
-            },
-        })
+        let mut dst_grants = Vec::with_capacity(1);
+
+        let src_span = PageSpan::new(src_base, page_count);
+
+        for (src_base, src_grant) in src_address_space.grants.conflicts(src_span) {
+            let grant_span = PageSpan::new(src_base, src_grant.page_count);
+
+            let common_span = src_span.intersection(grant_span);
+            let offset_from_src_base = common_span.base.offset_from(src_base);
+
+            dst_grants.push(Grant {
+                base: dst_base.next_by(offset_from_src_base),
+                info: GrantInfo {
+                    page_count: common_span.count,
+                    flags,
+                    mapped: true,
+                    provider: match src_grant.provider {
+                        Provider::Allocated { ref pages } => Provider::External {
+                            src_base,
+                            address_space: Arc::clone(&src_address_space_lock),
+                            cow: false,
+                            pages: None,
+                        },
+                        Provider::PhysBorrowed { base: src_phys_base } => Provider::PhysBorrowed {
+                            base: src_phys_base.next_by(offset_from_src_base),
+                        },
+                        Provider::Fmap { .. } => todo!(),
+                        Provider::External { ref address_space, src_base, cow, ref pages } => Provider::External { address_space: Arc::clone(address_space), src_base, cow, pages: pages.as_ref().map(|pages| pages.iter().map(|pg| pg.as_ref().map(|pg| pg.ref_clone(false))).collect::<Vec<_>>().into()) },
+                    }
+                },
+            });
+        }
+
+        Ok(dst_grants)
     }
     // TODO: This is limited to one page. Should it be (if some magic new proc: API is introduced)?
     pub fn cow(
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
index 1fe4026c6707461e0ca3f2ac98f5f59d3e1c0a66..52c3fba431fa24f2fea6046b60262a94fbf55cc6 100644
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -687,9 +687,6 @@ impl KernelScheme for ProcScheme {
                 if Arc::ptr_eq(addrspace, dst_addr_space) {
                     return Err(Error::new(EBUSY));
                 }
-                /*
-                // Limit to transferring/borrowing at most one grant, or part of a grant (splitting
-                // will be mandatory if grants are coalesced).
 
                 let (requested_dst_page, page_count) = crate::syscall::validate_region(map.address, map.size)?;
                 let (src_page, _) = crate::syscall::validate_region(map.offset, map.size)?;
@@ -718,7 +715,7 @@ impl KernelScheme for ProcScheme {
                 let src_mapper = &mut src_addr_space.table.utable;
                 let src_page_count = NonZeroUsize::new(src_grant_span.count).ok_or(Error::new(EINVAL))?;
 
-                let result_page = if consume {
+                let result_base = if consume {
                     let grant = src_addr_space.grants.remove(src_grant_span.base).expect("grant cannot disappear");
                     let (before, middle, after) = grant.extract(src_grant_span).expect("called intersect(), must succeed");
 
@@ -727,12 +724,10 @@ impl KernelScheme for ProcScheme {
 
                     dst_addr_space.mmap(requested_dst_page, src_page_count, map.flags, |dst_page, _flags, dst_mapper, dst_flusher| Grant::transfer(middle, dst_page, src_mapper, dst_mapper, InactiveFlusher::new(), dst_flusher))?
                 } else {
-                    dst_addr_space.mmap(requested_dst_page, src_page_count, map.flags, |dst_page, flags, dst_mapper, flusher| Ok(Grant::borrow(src_grant_span.base, dst_page, src_grant_span.count, flags, None, src_mapper, dst_mapper, flusher)?))?
+                    dst_addr_space.mmap_multiple(requested_dst_page, src_page_count, map.flags, |dst_page, flags, dst_mapper, flusher| Ok(Grant::borrow(Arc::clone(addrspace), src_addr_space, src_grant_span.base, dst_page, src_grant_span.count, flags, dst_mapper, flusher, true)?))?
                 };
-                */
 
-                //Ok(result_page.start_address().data())
-                todo!()
+                Ok(result_base.start_address().data())
             }
             _ => Err(Error::new(EBADF)),
         }
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index 2a0b19cbf4c0bb58b2f4d7f010ebc2953e944967..d46013b6ab62e8e83e32963180e8d3f0ba9cb3dc 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -261,7 +261,7 @@ impl UserInner {
         let (_middle_part_of_buf, tail_part_of_buf) = middle_tail_part_of_buf.split_at(middle_page_count * PAGE_SIZE).expect("split must succeed");
 
         if let Some(middle_page_count) = NonZeroUsize::new(middle_page_count) {
-            dst_space.mmap(Some(first_middle_dst_page), middle_page_count, map_flags, move |dst_page, page_flags, mapper, flusher| {
+            dst_space.mmap_multiple(Some(first_middle_dst_page), middle_page_count, map_flags, move |dst_page, page_flags, mapper, flusher| {
                 Ok(Grant::borrow(Arc::clone(&cur_space_lock), &mut *cur_space_lock.write(), first_middle_src_page, dst_page, middle_page_count.get(), page_flags, mapper, flusher, true)?)
             })?;
         }