diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs
index bfaa0794b5a3d1960704aa509714bb2154f03bfd..909385d225b5d42ba4e2d22827a44c5f89489928 100644
--- a/src/scheme/mod.rs
+++ b/src/scheme/mod.rs
@@ -13,9 +13,11 @@ use alloc::{
     sync::Arc,
     vec::Vec,
 };
+use syscall::CallerCtx;
 use core::sync::atomic::AtomicUsize;
 use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard};
 
+use crate::context::file::FileDescription;
 use crate::context::{memory::AddrSpace, file::FileDescriptor};
 use crate::syscall::error::*;
 use crate::syscall::scheme::Scheme;
@@ -310,4 +312,22 @@ pub trait KernelScheme: Scheme + Send + Sync + 'static {
     fn kfmap(&self, number: usize, addr_space: &Arc<RwLock<AddrSpace>>, map: &crate::syscall::data::Map, consume: bool) -> Result<usize> {
         Err(Error::new(EOPNOTSUPP))
     }
+
+    fn kopen(&self, path: &str, flags: usize, caller: CallerCtx) -> Result<OpenResult> {
+        self.open(path, flags, caller.uid, caller.gid).map(OpenResult::SchemeLocal)
+    }
+    fn kdup(&self, old_id: usize, buf: &[u8], _caller: CallerCtx) -> Result<OpenResult> {
+        self.dup(old_id, buf).map(OpenResult::SchemeLocal)
+    }
+}
+
+pub enum OpenResult {
+    SchemeLocal(usize),
+    External(Arc<RwLock<FileDescription>>),
+}
+
+pub fn current_caller_ctx() -> Result<CallerCtx> {
+    match crate::context::current()?.read() {
+        ref context => Ok(CallerCtx { pid: context.id.into(), uid: context.euid, gid: context.egid }),
+    }
 }
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index e6b09e3c63eaba2084ee5075b6b57982d25d175a..1f3f131eb6c918245d12f58854a511731263a639 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -1,13 +1,14 @@
 use alloc::sync::{Arc, Weak};
 use alloc::boxed::Box;
 use alloc::collections::BTreeMap;
+use syscall::{SKMSG_FRETURNFD, CallerCtx};
 use core::sync::atomic::{AtomicBool, Ordering};
 use core::{mem, slice, usize};
 use core::convert::TryFrom;
 use spin::{Mutex, RwLock};
 
 use crate::context::{self, Context};
-use crate::context::file::FileDescriptor;
+use crate::context::file::{FileDescriptor, FileDescription};
 use crate::context::memory::{AddrSpace, DANGLING, Grant, Region, GrantFileRef};
 use crate::event;
 use crate::paging::{PAGE_SIZE, mapper::InactiveFlusher, Page, round_down_pages, round_up_pages, VirtualAddress};
@@ -19,6 +20,8 @@ use crate::syscall::flag::{EventFlags, EVENT_READ, O_NONBLOCK, MapFlags, PROT_RE
 use crate::syscall::number::*;
 use crate::syscall::scheme::Scheme;
 
+use super::{FileHandle, OpenResult, KernelScheme, current_caller_ctx};
+
 pub struct UserInner {
     root_id: SchemeId,
     handle_id: usize,
@@ -29,9 +32,13 @@ pub struct UserInner {
     context: Weak<RwLock<Context>>,
     todo: WaitQueue<Packet>,
     fmap: Mutex<BTreeMap<u64, (Weak<RwLock<Context>>, FileDescriptor, Map)>>,
-    done: WaitMap<u64, usize>,
+    done: WaitMap<u64, Response>,
     unmounting: AtomicBool,
 }
+pub enum Response {
+    Regular(usize),
+    Fd(Arc<RwLock<FileDescription>>),
+}
 
 impl UserInner {
     pub fn new(root_id: SchemeId, handle_id: usize, name: Box<str>, flags: usize, context: Weak<RwLock<Context>>) -> UserInner {
@@ -72,20 +79,24 @@ impl UserInner {
     }
 
     pub fn call(&self, a: usize, b: usize, c: usize, d: usize) -> Result<usize> {
-        let (pid, uid, gid) = {
-            let contexts = context::contexts();
-            let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
-            let context = context_lock.read();
-            (context.id, context.euid, context.egid)
-        };
+        match self.call_extended(current_caller_ctx()?, [a, b, c, d])? {
+            Response::Regular(code) => Error::demux(code),
+            Response::Fd(_) => {
+                if a & SYS_RET_FILE == SYS_RET_FILE {
+                    log::warn!("Kernel code using UserScheme::call wrongly, as an external file descriptor was returned.");
+                }
 
-        let id = self.next_id();
+                Err(Error::new(EIO))
+            }
+        }
+    }
 
-        self.call_inner(Packet {
-            id,
-            pid: pid.into(),
-            uid,
-            gid,
+    pub fn call_extended(&self, ctx: CallerCtx, [a, b, c, d]: [usize; 4]) -> Result<Response> {
+        self.call_extended_inner(Packet {
+            id: self.next_id(),
+            pid: ctx.pid,
+            uid: ctx.uid,
+            gid: ctx.gid,
             a,
             b,
             c,
@@ -93,7 +104,7 @@ impl UserInner {
         })
     }
 
-    fn call_inner(&self, packet: Packet) -> Result<usize> {
+    fn call_extended_inner(&self, packet: Packet) -> Result<Response> {
         if self.unmounting.load(Ordering::SeqCst) {
             return Err(Error::new(ENODEV));
         }
@@ -103,7 +114,7 @@ impl UserInner {
         self.todo.send(packet);
         event::trigger(self.root_id, self.handle_id, EVENT_READ);
 
-        Error::demux(self.done.receive(&id, "UserInner::call_inner"))
+        Ok(self.done.receive(&id, "UserInner::call_inner"))
     }
 
     /// Map a readable structure to the scheme's userspace and return the
@@ -231,54 +242,78 @@ impl UserInner {
     }
 
     pub fn write(&self, buf: &[u8]) -> Result<usize> {
-        let packet_size = mem::size_of::<Packet>();
-        let len = buf.len()/packet_size;
-        let mut i = 0;
-        while i < len {
-            let mut packet = unsafe { *(buf.as_ptr() as *const Packet).add(i) };
-            if packet.id == 0 {
-                match packet.a {
-                    SYS_FEVENT => event::trigger(self.scheme_id.load(Ordering::SeqCst), packet.b, EventFlags::from_bits_truncate(packet.c)),
-                    _ => println!("Unknown scheme -> kernel message {}", packet.a)
+        // TODO: Alignment
+
+        let packets = unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::<Packet>(), buf.len() / mem::size_of::<Packet>()) };
+        let mut packets_read = 0;
+
+        for packet in packets {
+            match self.handle_packet(packet) {
+                Ok(()) => packets_read += 1,
+                Err(_) if packets_read > 0 => break,
+                Err(error) => return Err(error),
+            }
+        }
+
+        Ok(packets_read * mem::size_of::<Packet>())
+    }
+    fn handle_packet(&self, packet: &Packet) -> Result<()> {
+        if packet.id == 0 {
+            match packet.a {
+                SYS_FEVENT => event::trigger(self.scheme_id.load(Ordering::SeqCst), packet.b, EventFlags::from_bits_truncate(packet.c)),
+                _ => log::warn!("Unknown scheme -> kernel message {}", packet.a)
+            }
+        } else if Error::demux(packet.a) == Err(Error::new(ESKMSG)) {
+            // The reason why the new ESKMSG mechanism was introduced, is that passing packet IDs
+            // in packet.id is much cleaner than having to convert it into 1 or 2 usizes etc.
+            match packet.b {
+                SKMSG_FRETURNFD => {
+                    let fd = packet.c;
+
+                    let desc = context::current()?.read().remove_file(FileHandle::from(fd)).ok_or(Error::new(EINVAL))?.description;
+
+                    self.done.send(packet.id, Response::Fd(desc));
                 }
-            } else {
-                // The motivation of doing this here instead of within the fmap handler, is that we
-                // can operate on an inactive table. This reduces the number of page table reloads
-                // from two (context switch + active TLB flush) to one (context switch).
-                if let Some((context_weak, desc, map)) = self.fmap.lock().remove(&packet.id) {
-                    if let Ok(address) = Error::demux(packet.a) {
-                        if address % PAGE_SIZE > 0 {
-                            log::warn!("scheme returned unaligned address, causing extra frame to be allocated");
-                        }
-                        let file_ref = GrantFileRef { desc, offset: map.offset, flags: map.flags };
-                        let res = UserInner::capture_inner(&context_weak, map.address, address, map.size, map.flags, Some(file_ref));
-                        if let Ok(grant_address) = res {
-                            if let Some(context_lock) = context_weak.upgrade() {
-                                let context = context_lock.read();
-                                let mut addr_space = context.addr_space()?.write();
-                                //TODO: ensure all mappings are aligned!
-                                let map_pages = (map.size + PAGE_SIZE - 1) / PAGE_SIZE;
-                                addr_space.grants.funmap.insert(
-                                    Region::new(grant_address, map_pages * PAGE_SIZE),
-                                    VirtualAddress::new(address)
-                                );
-                            } else {
-                                //TODO: packet.pid is an assumption
-                                println!("UserInner::write: failed to find context {} for fmap", packet.pid);
-                            }
+                _ => return Err(Error::new(EINVAL)),
+            }
+        } else {
+            let mut retcode = packet.a;
+
+            // The motivation of doing this here instead of within the fmap handler, is that we
+            // can operate on an inactive table. This reduces the number of page table reloads
+            // from two (context switch + active TLB flush) to one (context switch).
+            if let Some((context_weak, desc, map)) = self.fmap.lock().remove(&packet.id) {
+                if let Ok(address) = Error::demux(packet.a) {
+                    if address % PAGE_SIZE > 0 {
+                        log::warn!("scheme returned unaligned address, causing extra frame to be allocated");
+                    }
+                    let file_ref = GrantFileRef { desc, offset: map.offset, flags: map.flags };
+                    let res = UserInner::capture_inner(&context_weak, map.address, address, map.size, map.flags, Some(file_ref));
+                    if let Ok(grant_address) = res {
+                        if let Some(context_lock) = context_weak.upgrade() {
+                            let context = context_lock.read();
+                            let mut addr_space = context.addr_space()?.write();
+                            //TODO: ensure all mappings are aligned!
+                            let map_pages = (map.size + PAGE_SIZE - 1) / PAGE_SIZE;
+                            addr_space.grants.funmap.insert(
+                                Region::new(grant_address, map_pages * PAGE_SIZE),
+                                VirtualAddress::new(address)
+                            );
+                        } else {
+                            //TODO: packet.pid is an assumption
+                            println!("UserInner::write: failed to find context {} for fmap", packet.pid);
                         }
-                        packet.a = Error::mux(res.map(|addr| addr.data()));
-                    } else {
-                        let _ = desc.close();
                     }
+                    retcode = Error::mux(res.map(|addr| addr.data()));
+                } else {
+                    let _ = desc.close();
                 }
-
-                self.done.send(packet.id, packet.a);
             }
-            i += 1;
+
+            self.done.send(packet.id, Response::Regular(retcode));
         }
 
-        Ok(i * packet_size)
+        Ok(())
     }
 
     pub fn fevent(&self, _flags: EventFlags) -> Result<EventFlags> {
@@ -319,7 +354,7 @@ impl UserInner {
 
         self.fmap.lock().insert(id, (context_weak, desc, *map));
 
-        let result = self.call_inner(Packet {
+        let result = self.call_extended_inner(Packet {
             id,
             pid: pid.into(),
             uid,
@@ -332,7 +367,14 @@ impl UserInner {
 
         let _ = self.release(address);
 
-        result
+        result.and_then(|response| match response {
+            Response::Regular(code) => Error::demux(code),
+            Response::Fd(_) => {
+                log::debug!("Scheme incorrectly returned an fd for fmap.");
+
+                Err(Error::new(EIO))
+            }
+        })
     }
 }
 
@@ -347,13 +389,19 @@ impl UserScheme {
     }
 }
 
+fn handle_open_res(res: OpenResult) -> Result<usize> {
+    match res {
+        OpenResult::SchemeLocal(num) => Ok(num),
+        OpenResult::External(_) => {
+            log::warn!("Used Scheme::open when forwarding fd!");
+            Err(Error::new(EIO))
+        }
+    }
+}
+
 impl Scheme for UserScheme {
-    fn open(&self, path: &str, flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
-        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
-        let address = inner.capture(path.as_bytes())?;
-        let result = inner.call(SYS_OPEN, address, path.len(), flags);
-        let _ = inner.release(address);
-        result
+    fn open(&self, path: &str, flags: usize, uid: u32, gid: u32) -> Result<usize> {
+        self.kopen(path, flags, CallerCtx { uid, gid, pid: context::context_id().into() }).and_then(handle_open_res)
     }
 
     fn rmdir(&self, path: &str, _uid: u32, _gid: u32) -> Result<usize> {
@@ -372,12 +420,8 @@ impl Scheme for UserScheme {
         result
     }
 
-    fn dup(&self, file: usize, buf: &[u8]) -> Result<usize> {
-        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
-        let address = inner.capture(buf)?;
-        let result = inner.call(SYS_DUP, file, address, buf.len());
-        let _ = inner.release(address);
-        result
+    fn dup(&self, old_id: usize, buf: &[u8]) -> Result<usize> {
+        self.kdup(old_id, buf, current_caller_ctx()?).and_then(handle_open_res)
     }
 
     fn read(&self, file: usize, buf: &mut [u8]) -> Result<usize> {
@@ -538,4 +582,27 @@ impl Scheme for UserScheme {
         inner.call(SYS_CLOSE, file, 0, 0)
     }
 }
-impl crate::scheme::KernelScheme for UserScheme {}
+impl KernelScheme for UserScheme {
+    fn kopen(&self, path: &str, flags: usize, ctx: CallerCtx) -> Result<OpenResult> {
+        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
+        let address = inner.capture(path.as_bytes())?;
+        let result = inner.call_extended(ctx, [SYS_OPEN, address, path.len(), flags]);
+        let _ = inner.release(address);
+
+        match result? {
+            Response::Regular(code) => Error::demux(code).map(OpenResult::SchemeLocal),
+            Response::Fd(desc) => Ok(OpenResult::External(desc)),
+        }
+    }
+    fn kdup(&self, file: usize, buf: &[u8], ctx: CallerCtx) -> Result<OpenResult> {
+        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
+        let address = inner.capture(buf)?;
+        let result = inner.call_extended(ctx, [SYS_DUP, file, address, buf.len()]);
+        let _ = inner.release(address);
+
+        match result? {
+            Response::Regular(code) => Error::demux(code).map(OpenResult::SchemeLocal),
+            Response::Fd(desc) => Ok(OpenResult::External(desc)),
+        }
+    }
+}
diff --git a/src/syscall/fs.rs b/src/syscall/fs.rs
index 100214a67d992a2cdc4942b8e2d110a39eccb217..792a4041c40effb5c6357c6ee03079ce9affa6de 100644
--- a/src/syscall/fs.rs
+++ b/src/syscall/fs.rs
@@ -1,12 +1,13 @@
 //! Filesystem syscalls
 use alloc::sync::Arc;
+use syscall::CallerCtx;
 use core::str;
 use spin::RwLock;
 
 use crate::context::file::{FileDescriptor, FileDescription};
 use crate::context;
 use crate::memory::PAGE_SIZE;
-use crate::scheme::{self, FileHandle};
+use crate::scheme::{self, FileHandle, OpenResult, current_caller_ctx};
 use crate::syscall::data::{Packet, Stat};
 use crate::syscall::error::*;
 use crate::syscall::flag::*;
@@ -53,11 +54,8 @@ pub fn file_op_mut_slice(a: usize, fd: FileHandle, slice: &mut [u8]) -> Result<u
 
 /// Open syscall
 pub fn open(path: &str, flags: usize) -> Result<FileHandle> {
-    let (uid, gid, scheme_ns, umask) = {
-        let contexts = context::contexts();
-        let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
-        let context = context_lock.read();
-        (context.euid, context.egid, context.ens, context.umask)
+    let (pid, uid, gid, scheme_ns, umask) = match context::current()?.read() {
+        ref context => (context.id.into(), context.euid, context.egid, context.ens, context.umask),
     };
 
     let flags = (flags & (!0o777)) | ((flags & 0o777) & (!(umask & 0o777)));
@@ -67,23 +65,26 @@ pub fn open(path: &str, flags: usize) -> Result<FileHandle> {
     let scheme_name = parts.next().ok_or(Error::new(EINVAL))?;
     let reference = parts.next().unwrap_or("");
 
-    let (scheme_id, file_id) = {
+    let description = {
         let (scheme_id, scheme) = {
             let schemes = scheme::schemes();
             let (scheme_id, scheme) = schemes.get_name(scheme_ns, scheme_name).ok_or(Error::new(ENODEV))?;
             (scheme_id, Arc::clone(scheme))
         };
 
-        (scheme_id, scheme.open(reference, flags, uid, gid)?)
+        match scheme.kopen(reference, flags, CallerCtx { uid, gid, pid })? {
+            OpenResult::SchemeLocal(number) => Arc::new(RwLock::new(FileDescription {
+                namespace: scheme_ns,
+                scheme: scheme_id,
+                number,
+                flags: flags & !O_CLOEXEC,
+            })),
+            OpenResult::External(desc) => desc,
+        }
     };
 
     context::current()?.read().add_file(FileDescriptor {
-        description: Arc::new(RwLock::new(FileDescription {
-            namespace: scheme_ns,
-            scheme: scheme_id,
-            number: file_id,
-            flags: flags & !O_CLOEXEC,
-        })),
+        description,
         cloexec: flags & O_CLOEXEC == O_CLOEXEC,
     }).ok_or(Error::new(EMFILE))
 }
@@ -196,22 +197,25 @@ fn duplicate_file(fd: FileHandle, buf: &[u8]) -> Result<FileDescriptor> {
     } else {
         let description = file.description.read();
 
-        let new_id = {
+        let new_description = {
             let scheme = {
                 let schemes = scheme::schemes();
                 let scheme = schemes.get(description.scheme).ok_or(Error::new(EBADF))?;
                 Arc::clone(scheme)
             };
-            scheme.dup(description.number, buf)?
+            match scheme.kdup(description.number, buf, current_caller_ctx()?)? {
+                OpenResult::SchemeLocal(number) => Arc::new(RwLock::new(FileDescription {
+                    namespace: description.namespace,
+                    scheme: description.scheme,
+                    number,
+                    flags: description.flags,
+                })),
+                OpenResult::External(desc) => desc,
+            }
         };
 
         Ok(FileDescriptor {
-            description: Arc::new(RwLock::new(FileDescription {
-                namespace: description.namespace,
-                scheme: description.scheme,
-                number: new_id,
-                flags: description.flags,
-            })),
+            description: new_description,
             cloexec: false,
         })
     }
diff --git a/syscall b/syscall
index abf7e59f5ad001a7a981d53751368c8fad189ffc..4d37b9fc1f6660057335d9b5ed7b0cef35ab78f9 160000
--- a/syscall
+++ b/syscall
@@ -1 +1 @@
-Subproject commit abf7e59f5ad001a7a981d53751368c8fad189ffc
+Subproject commit 4d37b9fc1f6660057335d9b5ed7b0cef35ab78f9