diff mbox series

[v4,2/5] userfaultfd: add /dev/userfaultfd for fine grained access control

Message ID 20220719195628.3415852-3-axelrasmussen@google.com (mailing list archive)
State New, archived
Headers show
Series userfaultfd: add /dev/userfaultfd for fine grained access control | expand

Commit Message

Axel Rasmussen July 19, 2022, 7:56 p.m. UTC
Historically, it has been shown that intercepting kernel faults with
userfaultfd (thereby forcing the kernel to wait for an arbitrary amount
of time) can be exploited, or at least can make some kinds of exploits
easier. So, in 37cd0575b8 "userfaultfd: add UFFD_USER_MODE_ONLY" we
changed things so, in order for kernel faults to be handled by
userfaultfd, either the process needs CAP_SYS_PTRACE, or this sysctl
must be configured so that any unprivileged user can do it.

In a typical implementation of a hypervisor with live migration (take
QEMU/KVM as one such example), we do indeed need to be able to handle
kernel faults. But, both options above are less than ideal:

- Toggling the sysctl increases attack surface by allowing any
  unprivileged user to do it.

- Granting the live migration process CAP_SYS_PTRACE gives it this
  ability, but *also* the ability to "observe and control the
  execution of another process [...], and examine and change [its]
  memory and registers" (from ptrace(2)). This isn't something we need
  or want to be able to do, so granting this permission violates the
  "principle of least privilege".

This is all a long winded way to say: we want a more fine-grained way to
grant access to userfaultfd, without granting other additional
permissions at the same time.

To achieve this, add a /dev/userfaultfd misc device. This device
provides an alternative to the userfaultfd(2) syscall for the creation
of new userfaultfds. The idea is, any userfaultfds created this way will
be able to handle kernel faults, without the caller having any special
capabilities. Access to this mechanism is instead restricted using e.g.
standard filesystem permissions.

Signed-off-by: Axel Rasmussen <axelrasmussen@google.com>
---
 fs/userfaultfd.c                 | 69 +++++++++++++++++++++++++-------
 include/uapi/linux/userfaultfd.h |  4 ++
 2 files changed, 59 insertions(+), 14 deletions(-)

Comments

Peter Xu July 19, 2022, 9:18 p.m. UTC | #1
On Tue, Jul 19, 2022 at 12:56:25PM -0700, Axel Rasmussen wrote:
> Historically, it has been shown that intercepting kernel faults with
> userfaultfd (thereby forcing the kernel to wait for an arbitrary amount
> of time) can be exploited, or at least can make some kinds of exploits
> easier. So, in 37cd0575b8 "userfaultfd: add UFFD_USER_MODE_ONLY" we
> changed things so, in order for kernel faults to be handled by
> userfaultfd, either the process needs CAP_SYS_PTRACE, or this sysctl
> must be configured so that any unprivileged user can do it.
> 
> In a typical implementation of a hypervisor with live migration (take
> QEMU/KVM as one such example), we do indeed need to be able to handle
> kernel faults. But, both options above are less than ideal:
> 
> - Toggling the sysctl increases attack surface by allowing any
>   unprivileged user to do it.
> 
> - Granting the live migration process CAP_SYS_PTRACE gives it this
>   ability, but *also* the ability to "observe and control the
>   execution of another process [...], and examine and change [its]
>   memory and registers" (from ptrace(2)). This isn't something we need
>   or want to be able to do, so granting this permission violates the
>   "principle of least privilege".
> 
> This is all a long winded way to say: we want a more fine-grained way to
> grant access to userfaultfd, without granting other additional
> permissions at the same time.
> 
> To achieve this, add a /dev/userfaultfd misc device. This device
> provides an alternative to the userfaultfd(2) syscall for the creation
> of new userfaultfds. The idea is, any userfaultfds created this way will
> be able to handle kernel faults, without the caller having any special
> capabilities. Access to this mechanism is instead restricted using e.g.
> standard filesystem permissions.
> 
> Signed-off-by: Axel Rasmussen <axelrasmussen@google.com>

Thanks, this looks much better.

Acked-by: Peter Xu <peterx@redhat.com>
Nadav Amit July 19, 2022, 10:32 p.m. UTC | #2
On Jul 19, 2022, at 12:56 PM, Axel Rasmussen <axelrasmussen@google.com> wrote:

> Historically, it has been shown that intercepting kernel faults with
> userfaultfd (thereby forcing the kernel to wait for an arbitrary amount
> of time) can be exploited, or at least can make some kinds of exploits
> easier. So, in 37cd0575b8 "userfaultfd: add UFFD_USER_MODE_ONLY" we
> changed things so, in order for kernel faults to be handled by
> userfaultfd, either the process needs CAP_SYS_PTRACE, or this sysctl
> must be configured so that any unprivileged user can do it.
> 
> In a typical implementation of a hypervisor with live migration (take
> QEMU/KVM as one such example), we do indeed need to be able to handle
> kernel faults. But, both options above are less than ideal:
> 
> - Toggling the sysctl increases attack surface by allowing any
>  unprivileged user to do it.
> 
> - Granting the live migration process CAP_SYS_PTRACE gives it this
>  ability, but *also* the ability to "observe and control the
>  execution of another process [...], and examine and change [its]
>  memory and registers" (from ptrace(2)). This isn't something we need
>  or want to be able to do, so granting this permission violates the
>  "principle of least privilege".
> 
> This is all a long winded way to say: we want a more fine-grained way to
> grant access to userfaultfd, without granting other additional
> permissions at the same time.
> 
> To achieve this, add a /dev/userfaultfd misc device. This device
> provides an alternative to the userfaultfd(2) syscall for the creation
> of new userfaultfds. The idea is, any userfaultfds created this way will
> be able to handle kernel faults, without the caller having any special
> capabilities. Access to this mechanism is instead restricted using e.g.
> standard filesystem permissions.

Are there any other “devices" that when opened by different processes
provide such isolated interfaces in each process? I.e., devices that if you
read from them in different processes you get completely unrelated data?
(putting aside namespaces).

It all sounds so wrong to me, that I am going to try again to pushback
(sorry).

From a semantic point of view - userfaultfd is process specific. It is
therefore similar to /proc/[pid]/mem (or /proc/[pid]/pagemap and so on).

So why can’t we put it there? I saw that you argued against it in your
cover-letter, and I think that your argument is you would need
CAP_SYS_PTRACE if you want to access userfaultfd of other processes. But
this is EXACTLY the way opening /proc/[pid]/mem is performed - see
proc_mem_open().

So instead of having some strange device that behaves differently in the
context of each process, you can just have /proc/[pid]/userfaultfd and then
use mm_access() to check if you have permissions to access userfaultfd (just
like proc_mem_open() does). This would be more intuitive for users as it is
similar to other /proc/[pid]/X, and would cover both local and remote
use-cases.
Axel Rasmussen July 19, 2022, 10:45 p.m. UTC | #3
On Tue, Jul 19, 2022 at 3:32 PM Nadav Amit <namit@vmware.com> wrote:
>
> On Jul 19, 2022, at 12:56 PM, Axel Rasmussen <axelrasmussen@google.com> wrote:
>
> > Historically, it has been shown that intercepting kernel faults with
> > userfaultfd (thereby forcing the kernel to wait for an arbitrary amount
> > of time) can be exploited, or at least can make some kinds of exploits
> > easier. So, in 37cd0575b8 "userfaultfd: add UFFD_USER_MODE_ONLY" we
> > changed things so, in order for kernel faults to be handled by
> > userfaultfd, either the process needs CAP_SYS_PTRACE, or this sysctl
> > must be configured so that any unprivileged user can do it.
> >
> > In a typical implementation of a hypervisor with live migration (take
> > QEMU/KVM as one such example), we do indeed need to be able to handle
> > kernel faults. But, both options above are less than ideal:
> >
> > - Toggling the sysctl increases attack surface by allowing any
> >  unprivileged user to do it.
> >
> > - Granting the live migration process CAP_SYS_PTRACE gives it this
> >  ability, but *also* the ability to "observe and control the
> >  execution of another process [...], and examine and change [its]
> >  memory and registers" (from ptrace(2)). This isn't something we need
> >  or want to be able to do, so granting this permission violates the
> >  "principle of least privilege".
> >
> > This is all a long winded way to say: we want a more fine-grained way to
> > grant access to userfaultfd, without granting other additional
> > permissions at the same time.
> >
> > To achieve this, add a /dev/userfaultfd misc device. This device
> > provides an alternative to the userfaultfd(2) syscall for the creation
> > of new userfaultfds. The idea is, any userfaultfds created this way will
> > be able to handle kernel faults, without the caller having any special
> > capabilities. Access to this mechanism is instead restricted using e.g.
> > standard filesystem permissions.
>
> Are there any other “devices" that when opened by different processes
> provide such isolated interfaces in each process? I.e., devices that if you
> read from them in different processes you get completely unrelated data?
> (putting aside namespaces).
>
> It all sounds so wrong to me, that I am going to try again to pushback
> (sorry).

No need to be sorry. :)

>
> From a semantic point of view - userfaultfd is process specific. It is
> therefore similar to /proc/[pid]/mem (or /proc/[pid]/pagemap and so on).
>
> So why can’t we put it there? I saw that you argued against it in your
> cover-letter, and I think that your argument is you would need
> CAP_SYS_PTRACE if you want to access userfaultfd of other processes. But
> this is EXACTLY the way opening /proc/[pid]/mem is performed - see
> proc_mem_open().
>
> So instead of having some strange device that behaves differently in the
> context of each process, you can just have /proc/[pid]/userfaultfd and then
> use mm_access() to check if you have permissions to access userfaultfd (just
> like proc_mem_open() does). This would be more intuitive for users as it is
> similar to other /proc/[pid]/X, and would cover both local and remote
> use-cases.

Ah, so actually I find this argument much more compelling.

I don't find it persuasive that we should put it in /proc for the
purpose of supporting cross-process memory manipulation, because I
think the syscall works better for that, and in that case we don't
mind depending on CAP_SYS_PTRACE.

But, what you've argued here I do find persuasive. :) You are right, I
can't think of any other example of a device node in /dev that works
like this, where it is completely independent on a per-process basis.
The closest I could come up with was /dev/zero or /dev/null or
similar. You won't affect any other process by touching these, but I
don't think these are good examples.

I'll send a v5 which does this. I do worry that cross-process support
is probably complex to get right, so I might leave that out and only
allow a process to open its own device for now.

>
Nadav Amit July 19, 2022, 11:55 p.m. UTC | #4
On Jul 19, 2022, at 3:45 PM, Axel Rasmussen <axelrasmussen@google.com> wrote:

> On Tue, Jul 19, 2022 at 3:32 PM Nadav Amit <namit@vmware.com> wrote:
>> On Jul 19, 2022, at 12:56 PM, Axel Rasmussen <axelrasmussen@google.com> wrote:
>> 
>>> Historically, it has been shown that intercepting kernel faults with
>>> userfaultfd (thereby forcing the kernel to wait for an arbitrary amount
>>> of time) can be exploited, or at least can make some kinds of exploits
>>> easier. So, in 37cd0575b8 "userfaultfd: add UFFD_USER_MODE_ONLY" we
>>> changed things so, in order for kernel faults to be handled by
>>> userfaultfd, either the process needs CAP_SYS_PTRACE, or this sysctl
>>> must be configured so that any unprivileged user can do it.
>>> 
>>> In a typical implementation of a hypervisor with live migration (take
>>> QEMU/KVM as one such example), we do indeed need to be able to handle
>>> kernel faults. But, both options above are less than ideal:
>>> 
>>> - Toggling the sysctl increases attack surface by allowing any
>>> unprivileged user to do it.
>>> 
>>> - Granting the live migration process CAP_SYS_PTRACE gives it this
>>> ability, but *also* the ability to "observe and control the
>>> execution of another process [...], and examine and change [its]
>>> memory and registers" (from ptrace(2)). This isn't something we need
>>> or want to be able to do, so granting this permission violates the
>>> "principle of least privilege".
>>> 
>>> This is all a long winded way to say: we want a more fine-grained way to
>>> grant access to userfaultfd, without granting other additional
>>> permissions at the same time.
>>> 
>>> To achieve this, add a /dev/userfaultfd misc device. This device
>>> provides an alternative to the userfaultfd(2) syscall for the creation
>>> of new userfaultfds. The idea is, any userfaultfds created this way will
>>> be able to handle kernel faults, without the caller having any special
>>> capabilities. Access to this mechanism is instead restricted using e.g.
>>> standard filesystem permissions.
>> 
>> Are there any other “devices" that when opened by different processes
>> provide such isolated interfaces in each process? I.e., devices that if you
>> read from them in different processes you get completely unrelated data?
>> (putting aside namespaces).
>> 
>> It all sounds so wrong to me, that I am going to try again to pushback
>> (sorry).
> 
> No need to be sorry. :)
> 
>> From a semantic point of view - userfaultfd is process specific. It is
>> therefore similar to /proc/[pid]/mem (or /proc/[pid]/pagemap and so on).
>> 
>> So why can’t we put it there? I saw that you argued against it in your
>> cover-letter, and I think that your argument is you would need
>> CAP_SYS_PTRACE if you want to access userfaultfd of other processes. But
>> this is EXACTLY the way opening /proc/[pid]/mem is performed - see
>> proc_mem_open().
>> 
>> So instead of having some strange device that behaves differently in the
>> context of each process, you can just have /proc/[pid]/userfaultfd and then
>> use mm_access() to check if you have permissions to access userfaultfd (just
>> like proc_mem_open() does). This would be more intuitive for users as it is
>> similar to other /proc/[pid]/X, and would cover both local and remote
>> use-cases.
> 
> Ah, so actually I find this argument much more compelling.
> 
> I don't find it persuasive that we should put it in /proc for the
> purpose of supporting cross-process memory manipulation, because I
> think the syscall works better for that, and in that case we don't
> mind depending on CAP_SYS_PTRACE.
> 
> But, what you've argued here I do find persuasive. :) You are right, I
> can't think of any other example of a device node in /dev that works
> like this, where it is completely independent on a per-process basis.
> The closest I could come up with was /dev/zero or /dev/null or
> similar. You won't affect any other process by touching these, but I
> don't think these are good examples.
> 
> I'll send a v5 which does this. I do worry that cross-process support
> is probably complex to get right, so I might leave that out and only
> allow a process to open its own device for now.

So I didn’t want to get into it, and I am fine that you leave it out,
since such an interface would still enable to support it later.

Anyhow, I do want to clarify a bit about the “cross-process support”
userfaultfd situation. Basically, you can already get cross-process support
today, by using calling userfaultfd() on the controlled process and calling
pidfd_open() from another process. It does work and I do not remember any
issues that it introduced (in contrast, for instance, to io-uring, that
would break if you use userfaultfd+iouring+fork today).

Thanks for your reconsideration.

Regards,
Nadav
Peter Xu July 20, 2022, 2:32 a.m. UTC | #5
On Tue, Jul 19, 2022 at 11:55:21PM +0000, Nadav Amit wrote:
> Anyhow, I do want to clarify a bit about the “cross-process support”
> userfaultfd situation. Basically, you can already get cross-process support
> today, by using calling userfaultfd() on the controlled process and calling
> pidfd_open() from another process. It does work and I do not remember any
> issues that it introduced (in contrast, for instance, to io-uring, that
> would break if you use userfaultfd+iouring+fork today).

Do you mean to base it on pidof_getfd()?

Just want to mention that this will still need collaboration of the target
process as userfaultfd needs to be created explicitly there.  From that POV
it's still more similar to general SCM_RIGHTS trick to pass over the fd but
just to pass it in a different way.

IMHO the core change about having /proc/pid/userfaultfd is skipping that
only last step to create the handle.
Nadav Amit July 20, 2022, 5:42 p.m. UTC | #6
On Jul 19, 2022, at 7:32 PM, Peter Xu <peterx@redhat.com> wrote:

> ⚠ External Email
> 
> On Tue, Jul 19, 2022 at 11:55:21PM +0000, Nadav Amit wrote:
>> Anyhow, I do want to clarify a bit about the “cross-process support”
>> userfaultfd situation. Basically, you can already get cross-process support
>> today, by using calling userfaultfd() on the controlled process and calling
>> pidfd_open() from another process. It does work and I do not remember any
>> issues that it introduced (in contrast, for instance, to io-uring, that
>> would break if you use userfaultfd+iouring+fork today).
> 
> Do you mean to base it on pidof_getfd()?

autocorrect? :)

I did refer to pidfd_getfd() as a syscall that can be used today by one
process to control the address space of another process. I did not intend to
use it for the actual implementation.

> Just want to mention that this will still need collaboration of the target
> process as userfaultfd needs to be created explicitly there.  From that POV
> it's still more similar to general SCM_RIGHTS trick to pass over the fd but
> just to pass it in a different way.

There are also some tricks you can do with ptrace in order not to need the
collaboration, but they are admittedly fragile.

> IMHO the core change about having /proc/pid/userfaultfd is skipping that
> only last step to create the handle.

Yes. The point that I was trying to make is that there are no open issues
with adding support for remote process control through
/proc/pid/userfaultfd. This is in contrast, for example, for using io-uring
with userfaultfd. For instance, if you try to use io-uring TODAY with
userfaultfd (without the async support that I need to add), and you try to
monitor the fork event, things would break (the new userfaultfd file
descriptor after fork would be installed on the io-worker thread).

This is all to say that it is really simple to add support for one process
monitoring userfaultfd of another process, since I understood that Axel had
concerned that this might be utterly broken…
Axel Rasmussen July 20, 2022, 8:10 p.m. UTC | #7
On Wed, Jul 20, 2022 at 10:42 AM Nadav Amit <namit@vmware.com> wrote:
>
> On Jul 19, 2022, at 7:32 PM, Peter Xu <peterx@redhat.com> wrote:
>
> > ⚠ External Email
> >
> > On Tue, Jul 19, 2022 at 11:55:21PM +0000, Nadav Amit wrote:
> >> Anyhow, I do want to clarify a bit about the “cross-process support”
> >> userfaultfd situation. Basically, you can already get cross-process support
> >> today, by using calling userfaultfd() on the controlled process and calling
> >> pidfd_open() from another process. It does work and I do not remember any
> >> issues that it introduced (in contrast, for instance, to io-uring, that
> >> would break if you use userfaultfd+iouring+fork today).
> >
> > Do you mean to base it on pidof_getfd()?
>
> autocorrect? :)
>
> I did refer to pidfd_getfd() as a syscall that can be used today by one
> process to control the address space of another process. I did not intend to
> use it for the actual implementation.
>
> > Just want to mention that this will still need collaboration of the target
> > process as userfaultfd needs to be created explicitly there.  From that POV
> > it's still more similar to general SCM_RIGHTS trick to pass over the fd but
> > just to pass it in a different way.
>
> There are also some tricks you can do with ptrace in order not to need the
> collaboration, but they are admittedly fragile.
>
> > IMHO the core change about having /proc/pid/userfaultfd is skipping that
> > only last step to create the handle.
>
> Yes. The point that I was trying to make is that there are no open issues
> with adding support for remote process control through
> /proc/pid/userfaultfd. This is in contrast, for example, for using io-uring
> with userfaultfd. For instance, if you try to use io-uring TODAY with
> userfaultfd (without the async support that I need to add), and you try to
> monitor the fork event, things would break (the new userfaultfd file
> descriptor after fork would be installed on the io-worker thread).
>
> This is all to say that it is really simple to add support for one process
> monitoring userfaultfd of another process, since I understood that Axel had
> concerned that this might be utterly broken…

Mostly I was worried it would be nontrivial to implement, and it isn't
a use case I plan to use so I was hoping to ignore it and defer it to
some future patches. ;)

But, if it "just works" I'm happy to include it in v5.
Nadav Amit July 20, 2022, 8:14 p.m. UTC | #8
On Jul 20, 2022, at 1:10 PM, Axel Rasmussen <axelrasmussen@google.com> wrote:

> On Wed, Jul 20, 2022 at 10:42 AM Nadav Amit <namit@vmware.com> wrote:
>> On Jul 19, 2022, at 7:32 PM, Peter Xu <peterx@redhat.com> wrote:
>> 
>>> ⚠ External Email
>>> 
>>> On Tue, Jul 19, 2022 at 11:55:21PM +0000, Nadav Amit wrote:
>>>> Anyhow, I do want to clarify a bit about the “cross-process support”
>>>> userfaultfd situation. Basically, you can already get cross-process support
>>>> today, by using calling userfaultfd() on the controlled process and calling
>>>> pidfd_open() from another process. It does work and I do not remember any
>>>> issues that it introduced (in contrast, for instance, to io-uring, that
>>>> would break if you use userfaultfd+iouring+fork today).
>>> 
>>> Do you mean to base it on pidof_getfd()?
>> 
>> autocorrect? :)
>> 
>> I did refer to pidfd_getfd() as a syscall that can be used today by one
>> process to control the address space of another process. I did not intend to
>> use it for the actual implementation.
>> 
>>> Just want to mention that this will still need collaboration of the target
>>> process as userfaultfd needs to be created explicitly there.  From that POV
>>> it's still more similar to general SCM_RIGHTS trick to pass over the fd but
>>> just to pass it in a different way.
>> 
>> There are also some tricks you can do with ptrace in order not to need the
>> collaboration, but they are admittedly fragile.
>> 
>>> IMHO the core change about having /proc/pid/userfaultfd is skipping that
>>> only last step to create the handle.
>> 
>> Yes. The point that I was trying to make is that there are no open issues
>> with adding support for remote process control through
>> /proc/pid/userfaultfd. This is in contrast, for example, for using io-uring
>> with userfaultfd. For instance, if you try to use io-uring TODAY with
>> userfaultfd (without the async support that I need to add), and you try to
>> monitor the fork event, things would break (the new userfaultfd file
>> descriptor after fork would be installed on the io-worker thread).
>> 
>> This is all to say that it is really simple to add support for one process
>> monitoring userfaultfd of another process, since I understood that Axel had
>> concerned that this might be utterly broken…
> 
> Mostly I was worried it would be nontrivial to implement, and it isn't
> a use case I plan to use so I was hoping to ignore it and defer it to
> some future patches. ;)
> 
> But, if it "just works" I'm happy to include it in v5.

There is a problem though, since for many use-cases you do need
process_madvisev(MADV_DONTNEED) which is unsupported, and you also need - in
some use-cases - to be able to skip pinned pages. These are patches that I
still need to send.

So I leave it to you to make up your mind whether it is reasonable to add it
now without this support.
Nadav Amit Aug. 2, 2022, 6:46 p.m. UTC | #9
On Jul 19, 2022, at 12:56 PM, Axel Rasmussen <axelrasmussen@google.com> wrote:

> 
> +static int new_userfaultfd(bool is_syscall, int flags)
> {
> 	struct userfaultfd_ctx *ctx;
> 	int fd;
> 
> -	if (!sysctl_unprivileged_userfaultfd &&
> -	    (flags & UFFD_USER_MODE_ONLY) == 0 &&
> -	    !capable(CAP_SYS_PTRACE)) {
> -		printk_once(KERN_WARNING "uffd: Set unprivileged_userfaultfd "
> -			"sysctl knob to 1 if kernel faults must be handled "
> -			"without obtaining CAP_SYS_PTRACE capability\n");
> +	if (is_syscall && !userfaultfd_syscall_allowed(flags))
> 		return -EPERM;
> -	}
> 
> 	BUG_ON(!current->mm);
> 
> @@ -2098,8 +2105,42 @@ SYSCALL_DEFINE1(userfaultfd, int, flags)
> 	return fd;
> }
> 
> +SYSCALL_DEFINE1(userfaultfd, int, flags)
> +{
> +	return new_userfaultfd(true, flags);
> +}

Not critical, but why not to put the userfaultfd_syscall_allowed() check
here? You would be able to lose the “is_syscall”.

I also had a small comment for patch 5.

But these are minor issues, so for the series:

Acked-by: Nadav Amit <namit@vmware.com>


[ Sorry again for misunderstanding the scheme you were using is similar to
KVM and therefore reasonable. ]
diff mbox series

Patch

diff --git a/fs/userfaultfd.c b/fs/userfaultfd.c
index e943370107d0..968f2517a281 100644
--- a/fs/userfaultfd.c
+++ b/fs/userfaultfd.c
@@ -30,6 +30,7 @@ 
 #include <linux/security.h>
 #include <linux/hugetlb.h>
 #include <linux/swapops.h>
+#include <linux/miscdevice.h>
 
 int sysctl_unprivileged_userfaultfd __read_mostly;
 
@@ -413,13 +414,8 @@  vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
 
 	if (ctx->features & UFFD_FEATURE_SIGBUS)
 		goto out;
-	if ((vmf->flags & FAULT_FLAG_USER) == 0 &&
-	    ctx->flags & UFFD_USER_MODE_ONLY) {
-		printk_once(KERN_WARNING "uffd: Set unprivileged_userfaultfd "
-			"sysctl knob to 1 if kernel faults must be handled "
-			"without obtaining CAP_SYS_PTRACE capability\n");
+	if (!(vmf->flags & FAULT_FLAG_USER) && (ctx->flags & UFFD_USER_MODE_ONLY))
 		goto out;
-	}
 
 	/*
 	 * If it's already released don't get it. This avoids to loop
@@ -2052,19 +2048,30 @@  static void init_once_userfaultfd_ctx(void *mem)
 	seqcount_spinlock_init(&ctx->refile_seq, &ctx->fault_pending_wqh.lock);
 }
 
-SYSCALL_DEFINE1(userfaultfd, int, flags)
+static inline bool userfaultfd_syscall_allowed(int flags)
+{
+	/* Userspace-only page faults are always allowed */
+	if (flags & UFFD_USER_MODE_ONLY)
+		return true;
+
+	/*
+	 * The user is requesting a userfaultfd which can handle kernel faults.
+	 * Privileged users are always allowed to do this.
+	 */
+	if (capable(CAP_SYS_PTRACE))
+		return true;
+
+	/* Otherwise, access to kernel fault handling is sysctl controlled. */
+	return sysctl_unprivileged_userfaultfd;
+}
+
+static int new_userfaultfd(bool is_syscall, int flags)
 {
 	struct userfaultfd_ctx *ctx;
 	int fd;
 
-	if (!sysctl_unprivileged_userfaultfd &&
-	    (flags & UFFD_USER_MODE_ONLY) == 0 &&
-	    !capable(CAP_SYS_PTRACE)) {
-		printk_once(KERN_WARNING "uffd: Set unprivileged_userfaultfd "
-			"sysctl knob to 1 if kernel faults must be handled "
-			"without obtaining CAP_SYS_PTRACE capability\n");
+	if (is_syscall && !userfaultfd_syscall_allowed(flags))
 		return -EPERM;
-	}
 
 	BUG_ON(!current->mm);
 
@@ -2098,8 +2105,42 @@  SYSCALL_DEFINE1(userfaultfd, int, flags)
 	return fd;
 }
 
+SYSCALL_DEFINE1(userfaultfd, int, flags)
+{
+	return new_userfaultfd(true, flags);
+}
+
+static int userfaultfd_dev_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static long userfaultfd_dev_ioctl(struct file *file, unsigned int cmd, unsigned long flags)
+{
+	if (cmd != USERFAULTFD_IOC_NEW)
+		return -EINVAL;
+
+	return new_userfaultfd(false, flags);
+}
+
+static const struct file_operations userfaultfd_dev_fops = {
+	.open = userfaultfd_dev_open,
+	.unlocked_ioctl = userfaultfd_dev_ioctl,
+	.compat_ioctl = userfaultfd_dev_ioctl,
+	.owner = THIS_MODULE,
+	.llseek = noop_llseek,
+};
+
+static struct miscdevice userfaultfd_misc = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "userfaultfd",
+	.fops = &userfaultfd_dev_fops
+};
+
 static int __init userfaultfd_init(void)
 {
+	WARN_ON(misc_register(&userfaultfd_misc));
+
 	userfaultfd_ctx_cachep = kmem_cache_create("userfaultfd_ctx_cache",
 						sizeof(struct userfaultfd_ctx),
 						0,
diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h
index 7d32b1e797fb..005e5e306266 100644
--- a/include/uapi/linux/userfaultfd.h
+++ b/include/uapi/linux/userfaultfd.h
@@ -12,6 +12,10 @@ 
 
 #include <linux/types.h>
 
+/* ioctls for /dev/userfaultfd */
+#define USERFAULTFD_IOC 0xAA
+#define USERFAULTFD_IOC_NEW _IO(USERFAULTFD_IOC, 0x00)
+
 /*
  * If the UFFDIO_API is upgraded someday, the UFFDIO_UNREGISTER and
  * UFFDIO_WAKE ioctls should be defined as _IOW and not as _IOR.  In