* Re: [PATCH 00/58] LSM: Module stacking for AppArmor
From: Casey Schaufler @ 2019-06-04 19:58 UTC (permalink / raw)
To: Stephen Smalley, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: keescook, john.johansen, penguin-kernel, paul, casey
In-Reply-To: <2a9049a7-6259-5ae0-2790-0aaf337c51a4@tycho.nsa.gov>
On 6/4/2019 10:11 AM, Stephen Smalley wrote:
> On 6/4/19 12:14 PM, Casey Schaufler wrote:
>> On 6/4/2019 5:29 AM, Stephen Smalley wrote:
>>> On 6/2/19 12:50 PM, Casey Schaufler wrote:
>>>> This patchset provides the changes required for
>>>> the AppArmor security module to stack safely with any other.
>>>
>>> Please explain the motivation
>>
>> I'll add some explanation for the next revision.
>> It won't be anything that I haven't posted many times
>> before, but you're right that it belongs in the log.
>>
>>> - why do we want to allow AppArmor to stack with other modules,
>>
>> First, is there a reason not to? Sure, you can confuse
>> administrators by implementing complex security policies,
>> but there are lots of ways to do that already.
>
> There are costs to doing so, e.g.
> - greater complexity in the security framework,
Taking blob management out of the modules and
into the framework makes simplifies the modules.
> - possibly greater memory and runtime overheads,
Possibly reduced memory and runtime overheads, as well.
> - potential user confusion (which security module(s) caused a given failure?)
That's not new. I've seen countless cases where users blame
SELinux or Smack when the problem is with mode bits and/or
capabilities. Not to mention that a good 50% of current Linux
users don't understand any of the security mechanisms to
begin with.
> - potential distro maintainer burden
Selection of security modules and how they are configured
has always been a burden for distro developers and maintainers.
Nothing new here.
And, they knew the job was dangerous when they took it.
> (similar to above, but performing triage when any given permission denial can have multiple causes beyond just DAC + one module, weird interactions among modules, etc)
Yama has been widely accepted by distros, and civilization
has yet to have officially been declared ended.
> It isn't free so there should be a cost/benefit analysis.
Some benchmarking is definitely in order, but most
of what's you're calling out as downside is hypothetical
or based on assumption.
>
>>
>> AppArmor provides a different security model than SELinux,
>> TOMOYO or Smack. Smack is better at system component
>> separation, while AppArmor is better at application isolation.
>> It's a win to use each to its strength rather than trying to
>> stretch either to the edge of what it can do.
>>
>>> who would use it,
>>
>> Can't name names, but there have been multiple requests.
>>
>>> how would it be used,
>>
>> As mentioned above, Smack for system separation, AppArmor for
>> application isolation.
>
> Can you provide a concrete example of how combining the two yields a smaller, simpler configuration overall than using them individually?
Smack + AppArmor is a simpler, smaller model than the Smack policy
used in Tizen 2.
>
>>
>>> what does it provide that isn't already possible in the absence of it.
>>
>> It's not necessary that something be impossible to do any
>> other way. The question should be whether this provides for
>> a better way to achieve the goals, and this does that.
>> If I tried the come up with something that's impossible I
>> would expect the usual "you can do that with SELinux policy"
>> argument. We know we can do things. We want to have the tools
>> to do them better.
>>
>>> Also, Ubuntu fully upstreamed all of their changes to AppArmor, would this still suffice to enable stacking of AppArmor or do they rely on hooks that are not handled here?
>>
>> Some amount of merging will likely be required. But that's
>> always going to be true with parallel development tracks.
>> That's why we have git!
>>
>>> Please explain the cost of the change - what do we pay in terms of memory, runtime, or other overheads in order to support this change?
>>
>> Do you have particular benchmarks you want to see?
>> When I've supplied numbers in the past they have not
>> been remarked on.
>
> A combination of micro and macro benchmarks exercising multiple kernel subsystems would be good. Kernel build time isn't sufficient.
Do you have preferences, or better yet, facilities
for running them? I am, alas, running on finite resources
and benchmark contributions, especially in areas where you
have specific concerns, would be most welcome.
>
>>
>>>
>>>>
>>>> A new process attribute identifies which security module
>>>> information should be reported by SO_PEERSEC and the
>>>> /proc/.../attr/current interface. This is provided by
>>>> /proc/.../attr/display. Writing the name of the security
>>>> module desired to this interface will set which LSM hooks
>>>> will be called for this information. The first security
>>>> module providing the hooks will be used by default.
>>>
>>> Doesn't this effectively undo making the hooks read-only after init, at least for the subset involved? What are the security implications thereof?
>>
>> Any mechanism, be it a separate set of hooks, a name used to
>> do list look ups, or an sophisticated hash scheme will have that
>> impact for the processes that use it. This scheme has the best
>> performance profile of the mechanisms I experimented with and
>> avoids all sorts of special cases.
>>
>>>
>>>> The use of integer based security tokens (secids) is
>>>> generally (but not completely) replaced by a structure
>>>> lsm_export. The lsm_export structure can contain information
>>>> for each of the security modules that export information
>>>> outside the LSM layer.
>>>>
>>>> The LSM interfaces that provide "secctx" text strings
>>>> have been changed to use a structure "lsm_context"
>>>> instead of a pointer/length pair. In some cases the
>>>> interfaces used a "char *" pointer and in others a
>>>> "void *". This was necessary to ensure that the correct
>>>> release mechanism for the text is used. It also makes
>>>> many of the interfaces cleaner.
>>>>
>>>> https://github.com/cschaufler/lsm-stacking.git#stack-5.2-v1-apparmor
>>>>
>>>> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
>>>> ---
>>>> drivers/android/binder.c | 25 ++-
>>>> fs/kernfs/dir.c | 6 +-
>>>> fs/kernfs/inode.c | 31 ++-
>>>> fs/kernfs/kernfs-internal.h | 3 +-
>>>> fs/nfs/inode.c | 13 +-
>>>> fs/nfs/internal.h | 8 +-
>>>> fs/nfs/nfs4proc.c | 17 +-
>>>> fs/nfs/nfs4xdr.c | 16 +-
>>>> fs/nfsd/nfs4proc.c | 8 +-
>>>> fs/nfsd/nfs4xdr.c | 14 +-
>>>> fs/nfsd/vfs.c | 7 +-
>>>> fs/proc/base.c | 1 +
>>>> include/linux/cred.h | 3 +-
>>>> include/linux/lsm_hooks.h | 91 +++++----
>>>> include/linux/nfs4.h | 8 +-
>>>> include/linux/security.h | 133 +++++++++----
>>>> include/net/af_unix.h | 2 +-
>>>> include/net/netlabel.h | 10 +-
>>>> include/net/scm.h | 14 +-
>>>> kernel/audit.c | 43 ++--
>>>> kernel/audit.h | 9 +-
>>>> kernel/auditfilter.c | 6 +-
>>>> kernel/auditsc.c | 77 ++++----
>>>> kernel/cred.c | 15 +-
>>>> net/ipv4/cipso_ipv4.c | 13 +-
>>>> net/ipv4/ip_sockglue.c | 12 +-
>>>> net/netfilter/nf_conntrack_netlink.c | 29 ++-
>>>> net/netfilter/nf_conntrack_standalone.c | 16 +-
>>>> net/netfilter/nfnetlink_queue.c | 38 ++--
>>>> net/netfilter/nft_meta.c | 13 +-
>>>> net/netfilter/xt_SECMARK.c | 14 +-
>>>> net/netlabel/netlabel_kapi.c | 5 +-
>>>> net/netlabel/netlabel_unlabeled.c | 101 +++++-----
>>>> net/netlabel/netlabel_unlabeled.h | 2 +-
>>>> net/netlabel/netlabel_user.c | 13 +-
>>>> net/netlabel/netlabel_user.h | 2 +-
>>>> net/unix/af_unix.c | 6 +-
>>>> security/apparmor/audit.c | 4 +-
>>>> security/apparmor/include/audit.h | 2 +-
>>>> security/apparmor/include/net.h | 6 +-
>>>> security/apparmor/include/secid.h | 9 +-
>>>> security/apparmor/lsm.c | 64 +++---
>>>> security/apparmor/secid.c | 42 ++--
>>>> security/integrity/ima/ima.h | 14 +-
>>>> security/integrity/ima/ima_api.c | 9 +-
>>>> security/integrity/ima/ima_appraise.c | 6 +-
>>>> security/integrity/ima/ima_main.c | 34 ++--
>>>> security/integrity/ima/ima_policy.c | 19 +-
>>>> security/security.c | 338 +++++++++++++++++++++++++++-----
>>>> security/selinux/hooks.c | 259 ++++++++++++------------
>>>> security/selinux/include/audit.h | 5 +-
>>>> security/selinux/include/objsec.h | 42 +++-
>>>> security/selinux/netlabel.c | 25 +--
>>>> security/selinux/ss/services.c | 18 +-
>>>> security/smack/smack.h | 18 ++
>>>> security/smack/smack_lsm.c | 238 +++++++++++-----------
>>>> security/smack/smack_netfilter.c | 8 +-
>>>> security/smack/smackfs.c | 12 +-
>>>> 58 files changed, 1217 insertions(+), 779 deletions(-)
>>>>
>>>
>
^ permalink raw reply
* Re: [PATCH 1/8] security: Override creds in __fput() with last fputter's creds [ver #2]
From: Andy Lutomirski @ 2019-06-04 18:15 UTC (permalink / raw)
To: David Howells, Jann Horn
Cc: Al Viro, Casey Schaufler, raven, Linux FS Devel, Linux API,
linux-block, keyrings, LSM List, LKML
In-Reply-To: <155966611030.17449.1411028213562548153.stgit@warthog.procyon.org.uk>
On Tue, Jun 4, 2019 at 9:35 AM David Howells <dhowells@redhat.com> wrote:
>
> So that the LSM can see the credentials of the last process to do an fput()
> on a file object when the file object is being dismantled, do the following
> steps:
>
> (1) Cache the current credentials in file->f_fput_cred at the point the
> file object's reference count reaches zero.
I don't think it's valid to capture credentials in close(). This
sounds very easy to spoof, especially when you consider that you can
stick an fd in unix socket and aim it at a service that's just going
to ignore it and close it.
IOW I think this is at least as invalid as looking at current_cred()
in write(), which is a classic bug that gets repeated regularly.
--Andy
^ permalink raw reply
* Re: [RFC][PATCH 0/8] Mount, FS, Block and Keyrings notifications [ver #2]
From: Andy Lutomirski @ 2019-06-04 17:43 UTC (permalink / raw)
To: David Howells
Cc: Al Viro, Casey Schaufler, raven, Linux FS Devel, Linux API,
linux-block, keyrings, LSM List, LKML
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
On Tue, Jun 4, 2019 at 9:35 AM David Howells <dhowells@redhat.com> wrote:
>
>
> Hi Al,
>
> Here's a set of patches to add a general variable-length notification queue
> concept and to add sources of events for:
I asked before and didn't see a response, so I'll ask again. Why are
you paying any attention at all to the creds that generate an event?
It seems like the resulting security model will be vary hard to
understand and probably buggy. Can't you define a sensible model in
which only the listener creds matter?
> LSM support is included:
>
> (1) The creds of the process that did the fput() that reduced the refcount
> to zero are cached in the file struct.
>
> (2) __fput() overrides the current creds with the creds from (1) whilst
> doing the cleanup, thereby making sure that the creds seen by the
> destruction notification generated by mntput() appears to come from
> the last fputter.
That looks like duct tape that is, at best, likely to be very buggy.
>
> (3) security_post_notification() is called for each queue that we might
> want to post a notification into, thereby allowing the LSM to prevent
> covert communications.
This seems like the wrong approach. If an LSM wants to prevent covert
communication from, say, mount actions, then it shouldn't allow the
watch to be set up in the first place.
^ permalink raw reply
* Re: [PATCH 00/58] LSM: Module stacking for AppArmor
From: Stephen Smalley @ 2019-06-04 17:11 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: keescook, john.johansen, penguin-kernel, paul
In-Reply-To: <66a87b0b-b6f4-74ff-2e51-afc8e2d30de1@schaufler-ca.com>
On 6/4/19 12:14 PM, Casey Schaufler wrote:
> On 6/4/2019 5:29 AM, Stephen Smalley wrote:
>> On 6/2/19 12:50 PM, Casey Schaufler wrote:
>>> This patchset provides the changes required for
>>> the AppArmor security module to stack safely with any other.
>>
>> Please explain the motivation
>
> I'll add some explanation for the next revision.
> It won't be anything that I haven't posted many times
> before, but you're right that it belongs in the log.
>
>> - why do we want to allow AppArmor to stack with other modules,
>
> First, is there a reason not to? Sure, you can confuse
> administrators by implementing complex security policies,
> but there are lots of ways to do that already.
There are costs to doing so, e.g.
- greater complexity in the security framework,
- possibly greater memory and runtime overheads,
- potential user confusion (which security module(s) caused a given
failure?)
- potential distro maintainer burden (similar to above, but performing
triage when any given permission denial can have multiple causes beyond
just DAC + one module, weird interactions among modules, etc)
It isn't free so there should be a cost/benefit analysis.
>
> AppArmor provides a different security model than SELinux,
> TOMOYO or Smack. Smack is better at system component
> separation, while AppArmor is better at application isolation.
> It's a win to use each to its strength rather than trying to
> stretch either to the edge of what it can do.
>
>> who would use it,
>
> Can't name names, but there have been multiple requests.
>
>> how would it be used,
>
> As mentioned above, Smack for system separation, AppArmor for
> application isolation.
Can you provide a concrete example of how combining the two yields a
smaller, simpler configuration overall than using them individually?
>
>> what does it provide that isn't already possible in the absence of it.
>
> It's not necessary that something be impossible to do any
> other way. The question should be whether this provides for
> a better way to achieve the goals, and this does that.
> If I tried the come up with something that's impossible I
> would expect the usual "you can do that with SELinux policy"
> argument. We know we can do things. We want to have the tools
> to do them better.
>
>> Also, Ubuntu fully upstreamed all of their changes to AppArmor, would this still suffice to enable stacking of AppArmor or do they rely on hooks that are not handled here?
>
> Some amount of merging will likely be required. But that's
> always going to be true with parallel development tracks.
> That's why we have git!
>
>> Please explain the cost of the change - what do we pay in terms of memory, runtime, or other overheads in order to support this change?
>
> Do you have particular benchmarks you want to see?
> When I've supplied numbers in the past they have not
> been remarked on.
A combination of micro and macro benchmarks exercising multiple kernel
subsystems would be good. Kernel build time isn't sufficient.
>
>>
>>>
>>> A new process attribute identifies which security module
>>> information should be reported by SO_PEERSEC and the
>>> /proc/.../attr/current interface. This is provided by
>>> /proc/.../attr/display. Writing the name of the security
>>> module desired to this interface will set which LSM hooks
>>> will be called for this information. The first security
>>> module providing the hooks will be used by default.
>>
>> Doesn't this effectively undo making the hooks read-only after init, at least for the subset involved? What are the security implications thereof?
>
> Any mechanism, be it a separate set of hooks, a name used to
> do list look ups, or an sophisticated hash scheme will have that
> impact for the processes that use it. This scheme has the best
> performance profile of the mechanisms I experimented with and
> avoids all sorts of special cases.
>
>>
>>> The use of integer based security tokens (secids) is
>>> generally (but not completely) replaced by a structure
>>> lsm_export. The lsm_export structure can contain information
>>> for each of the security modules that export information
>>> outside the LSM layer.
>>>
>>> The LSM interfaces that provide "secctx" text strings
>>> have been changed to use a structure "lsm_context"
>>> instead of a pointer/length pair. In some cases the
>>> interfaces used a "char *" pointer and in others a
>>> "void *". This was necessary to ensure that the correct
>>> release mechanism for the text is used. It also makes
>>> many of the interfaces cleaner.
>>>
>>> https://github.com/cschaufler/lsm-stacking.git#stack-5.2-v1-apparmor
>>>
>>> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
>>> ---
>>> drivers/android/binder.c | 25 ++-
>>> fs/kernfs/dir.c | 6 +-
>>> fs/kernfs/inode.c | 31 ++-
>>> fs/kernfs/kernfs-internal.h | 3 +-
>>> fs/nfs/inode.c | 13 +-
>>> fs/nfs/internal.h | 8 +-
>>> fs/nfs/nfs4proc.c | 17 +-
>>> fs/nfs/nfs4xdr.c | 16 +-
>>> fs/nfsd/nfs4proc.c | 8 +-
>>> fs/nfsd/nfs4xdr.c | 14 +-
>>> fs/nfsd/vfs.c | 7 +-
>>> fs/proc/base.c | 1 +
>>> include/linux/cred.h | 3 +-
>>> include/linux/lsm_hooks.h | 91 +++++----
>>> include/linux/nfs4.h | 8 +-
>>> include/linux/security.h | 133 +++++++++----
>>> include/net/af_unix.h | 2 +-
>>> include/net/netlabel.h | 10 +-
>>> include/net/scm.h | 14 +-
>>> kernel/audit.c | 43 ++--
>>> kernel/audit.h | 9 +-
>>> kernel/auditfilter.c | 6 +-
>>> kernel/auditsc.c | 77 ++++----
>>> kernel/cred.c | 15 +-
>>> net/ipv4/cipso_ipv4.c | 13 +-
>>> net/ipv4/ip_sockglue.c | 12 +-
>>> net/netfilter/nf_conntrack_netlink.c | 29 ++-
>>> net/netfilter/nf_conntrack_standalone.c | 16 +-
>>> net/netfilter/nfnetlink_queue.c | 38 ++--
>>> net/netfilter/nft_meta.c | 13 +-
>>> net/netfilter/xt_SECMARK.c | 14 +-
>>> net/netlabel/netlabel_kapi.c | 5 +-
>>> net/netlabel/netlabel_unlabeled.c | 101 +++++-----
>>> net/netlabel/netlabel_unlabeled.h | 2 +-
>>> net/netlabel/netlabel_user.c | 13 +-
>>> net/netlabel/netlabel_user.h | 2 +-
>>> net/unix/af_unix.c | 6 +-
>>> security/apparmor/audit.c | 4 +-
>>> security/apparmor/include/audit.h | 2 +-
>>> security/apparmor/include/net.h | 6 +-
>>> security/apparmor/include/secid.h | 9 +-
>>> security/apparmor/lsm.c | 64 +++---
>>> security/apparmor/secid.c | 42 ++--
>>> security/integrity/ima/ima.h | 14 +-
>>> security/integrity/ima/ima_api.c | 9 +-
>>> security/integrity/ima/ima_appraise.c | 6 +-
>>> security/integrity/ima/ima_main.c | 34 ++--
>>> security/integrity/ima/ima_policy.c | 19 +-
>>> security/security.c | 338 +++++++++++++++++++++++++++-----
>>> security/selinux/hooks.c | 259 ++++++++++++------------
>>> security/selinux/include/audit.h | 5 +-
>>> security/selinux/include/objsec.h | 42 +++-
>>> security/selinux/netlabel.c | 25 +--
>>> security/selinux/ss/services.c | 18 +-
>>> security/smack/smack.h | 18 ++
>>> security/smack/smack_lsm.c | 238 +++++++++++-----------
>>> security/smack/smack_netfilter.c | 8 +-
>>> security/smack/smackfs.c | 12 +-
>>> 58 files changed, 1217 insertions(+), 779 deletions(-)
>>>
>>
^ permalink raw reply
* Re: security/loadpin: Allow to exclude specific file types
From: Ke Wu @ 2019-06-04 17:01 UTC (permalink / raw)
To: Kees Cook
Cc: Colin Ian King, James Morris, Serge E. Hallyn,
linux-security-module, linux-kernel@vger.kernel.org
In-Reply-To: <201905311330.EA6B6E5F@keescook>
I sent out a new patch according to your last suggestion. Please take
a look. Thanks!
On Fri, May 31, 2019 at 1:33 PM Kees Cook <keescook@chromium.org> wrote:
>
> On Fri, May 31, 2019 at 11:03:17AM -0700, Ke Wu wrote:
> > I think Coverity is correct. Note that it's the size of
> > kernel_read_file_str (rather than exclude_read_files) doesn't equal to
> > ignore_read_file_id.
> >
> > This is because READING_MAX_ID is also an element in
> > kernel_read_file_str, which makes the size of kernel_read_file_str to
> > be READING_MAX_ID+1. I will send a new patch to fix the issue. Thanks
> > for the analysis!
>
> Ah! Yes, I see now. I was looking at the wrong things. It should be
> possible to just do:
>
> > > >> 209 for (j = 0; j < ARRAY_SIZE(kernel_read_file_str); j++) {
>
> for (j = 0; j < ARRAY_SIZE(ignore_read_file_id); j++)
>
> and add a
>
> BUILD_BUG_ON(ARRAY_SIZE(kernel_read_file_str) < ARRAY_SIZE(ignore_read_file_id))
>
> for future robustness checking.
>
> Thanks for looking at this more closely!
>
> -Kees
>
> > > >> 210 if (strcmp(cur, kernel_read_file_str[j]) == 0) {
> > > >> 211 pr_info("excluding: %s\n",
> > > >> 212 kernel_read_file_str[j]);
> > > >>
> > > >> CID 81977 (#1 of 1): Out-of-bounds write
> > > >> overrun-local: Overrunning array ignore_read_file_id of 8 4-byte
> > > >> elements at element index 8 (byte offset 35) using index j (which
> > > >> evaluates to 8).
> > > >>
> > > >> 213 ignore_read_file_id[j] = 1;
> > > >>
> > > >> According to Coverity ignore_read_file_id is an array of 8 integers.
> > > >> However, ARRAY_SIZE(kernel_read_file_str) is 9, so we have an out of
> > > >> bounds write on ignore_read_file[j] when j is 8.
> > > >
> > > > What am I missing? This doesn't fail the build:
> > > >
> > > > + BUILD_BUG_ON(ARRAY_SIZE(exclude_read_files) !=
> > > > + ARRAY_SIZE(ignore_read_file_id));
> > > >
> > > > They have the same number of elements.
> > > >
> > >
> > > Yep, that's very true. I'll discuss this with Coverity as this seems
> > > like a weird false positive.
> > >
> > > Apologies for the noise.
> > >
> > > Colin
> >
> >
> >
> > --
> > Ke Wu | Software Engineer | mikewu@google.com | Google Inc.
>
> --
> Kees Cook
--
Ke Wu | Software Engineer | mikewu@google.com | Google Inc.
^ permalink raw reply
* Re: [RFC PATCH 6/9] x86/sgx: Require userspace to provide allowed prots to ADD_PAGES
From: Sean Christopherson @ 2019-06-04 16:45 UTC (permalink / raw)
To: Jarkko Sakkinen
Cc: Andy Lutomirski, Cedric Xing, Stephen Smalley, James Morris,
Serge E . Hallyn, LSM List, Paul Moore, Eric Paris, selinux,
Jethro Beekman, Dave Hansen, Thomas Gleixner, Linus Torvalds,
LKML, X86 ML, linux-sgx, Andrew Morton, nhorman, npmccallum,
Serge Ayoun, Shay Katz-zamir, Haitao Huang, Andy Shevchenko,
Kai Svahn, Borislav Petkov, Josh Triplett, Kai Huang,
David Rientjes, William Roberts, Philip Tricca
In-Reply-To: <20190604162306.GB3811@linux.intel.com>
On Tue, Jun 04, 2019 at 07:23:06PM +0300, Jarkko Sakkinen wrote:
> On Fri, May 31, 2019 at 04:31:56PM -0700, Sean Christopherson wrote:
> > ...to support (the equivalent) of existing Linux Security Module
> > functionality.
>
> Long and short descriptions should be separate. Also this does not
> make any sense. LSM is a framework with a set of hook to make access
> decisions and there various implementations of it.
>
> How this replicates LSMs and why that even would be a goal?
>
> My guess is that you are trying to do something else. I'm just saying
> that the idea to do equivalent of LSMs to another subsystems would be
> insane if it was done.
Heh, yeah, it's not duplicating LSM functionality. What I was trying to
say is that this patch allows LSMs to implement policies that are
equivalent to their existing functionality, e.g. paves the way to add
security_enclave_load() as an equivalent to security_file_mprotect().
> > always be MAP_SHARED. Lastly, all real world enclaves will need read,
> > write and execute permissions to EPC pages. As a result, SGX does not
> > play nice with existing LSM behavior as it is impossible to apply
> > policies to enclaves with any reasonable granularity, e.g. an LSM can
> > deny access to EPC altogether, but can't deny potentially dangerous
> > behavior such as mapping pages RW->RW or RWX.
>
> The mapping must be shared given that it is iomem but why enclave pages
> would need RWX for all pages? The information that is missing from this
> paragraph is the explanation why an LSM could not deny dangerous
> behavior in PTE level.
I'll add that.
> > To give LSMs enough information to implement their policies without
> > having to resort to ugly things, e.g. holding a reference to the vm_file
> > of each enclave page, require userspace to explicitly state the allowed
> > protections for each page (region), i.e. take ALLOW_{READ,WRITE,EXEC}
> > in the ADD_PAGES ioctl.
>
> I would keep descriptions such as "ugly things" away from commit
> messages as it is easy way to be not clear and explicit what you are
> trying to say.
>
> > The ALLOW_* flags will be passed to LSMs so that they can make informed
> > decisions when the enclave is being built, i.e. when the source vm_file
> > is available. For example, SELinux's EXECMOD permission can be
> > required if an enclave is requesting both ALLOW_WRITE and ALLOW_EXEC.
>
> There should be some explanation what ALLOW_* flag are. It is now like
> as it was in common knowledge. SECINFO already has protection flags to
> name an example and without any explanation all of this is just very
> confusing.
Noted.
> This should address SECINFO and ALLOW_* relationship and differences.
>
> > Update the mmap()/mprotect() hooks to enforce the ALLOW_* protections,
> > a la the standard VM_MAY{READ,WRITE,EXEC} flags.
> >
> > The ALLOW_EXEC flag also has a second (important) use in that it can
> > be used to prevent loading an enclave from a noexec file system, on
> > SGX2 hardware (regardless of kernel support for SGX2), userspace could
> > EADD from a noexec path using read-only permissions and later mprotect()
> > and ENCLU[EMODPE] the page to gain execute permissions. By requiring
> > ALLOW_EXEC up front, SGX will be able to enforce noexec paths when
> > building the enclave.
> >
> > Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
> > ---
> > arch/x86/include/uapi/asm/sgx.h | 9 ++++++++-
> > arch/x86/kernel/cpu/sgx/driver/ioctl.c | 23 +++++++++++++++++------
> > arch/x86/kernel/cpu/sgx/encl.c | 2 +-
> > arch/x86/kernel/cpu/sgx/encl.h | 1 +
> > 4 files changed, 27 insertions(+), 8 deletions(-)
> >
> > diff --git a/arch/x86/include/uapi/asm/sgx.h b/arch/x86/include/uapi/asm/sgx.h
> > index 4a12d6abbcb7..4489e92fa0dc 100644
> > --- a/arch/x86/include/uapi/asm/sgx.h
> > +++ b/arch/x86/include/uapi/asm/sgx.h
> > @@ -31,6 +31,11 @@ struct sgx_enclave_create {
> > __u64 src;
> > };
> >
> > +/* Supported flags for struct sgx_enclave_add_pages. */
> > +#define SGX_ALLOW_READ VM_READ
> > +#define SGX_ALLOW_WRITE VM_WRITE
> > +#define SGX_ALLOW_EXEC VM_EXEC
>
> Why these flags are even defined if they are the same as VM_* flags?
Brain fart. Flags can just take PROT_{READ,WRITE,EXEC}.
> > +
> > /**
> > * struct sgx_enclave_add_pages - parameter structure for the
> > * %SGX_IOC_ENCLAVE_ADD_PAGES ioctl
> > @@ -39,6 +44,7 @@ struct sgx_enclave_create {
> > * @secinfo: address for the SECINFO data (common to all pages)
> > * @nr_pages: number of pages (must be virtually contiguous)
> > * @mrmask: bitmask for the measured 256 byte chunks (common to all pages)
> > + * @flags: flags, e.g. SGX_ALLOW_{READ,WRITE,EXEC} (common to all pages)
> > */
> > struct sgx_enclave_add_pages {
> > __u64 addr;
> > @@ -46,7 +52,8 @@ struct sgx_enclave_add_pages {
> > __u64 secinfo;
> > __u32 nr_pages;
> > __u16 mrmask;
> > -} __attribute__((__packed__));
> > + __u16 flags;
> > +};
> >
...
> > @@ -576,12 +578,20 @@ static int __sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> >
> > static int sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> > unsigned long src, struct sgx_secinfo *secinfo,
> > - unsigned int mrmask)
> > + unsigned int mrmask, unsigned int flags)
> > {
> > + unsigned long prot = secinfo->flags & (VM_READ | VM_WRITE | VM_EXEC);
>
> Even if the secinfo flags have the exactly the same values you should
> not do this as they are kind of from different type. This is confusing
> to read.
I can add a dummy helper to translate flags and encapsulate the below
assert.
> > + unsigned long allowed_prot = flags & (VM_READ | VM_WRITE | VM_EXEC);
>
> Why you take the trouble defining those macros and do not then use them
> even yourself?
The original thought was to define them for userspace, but that's broken
because VM_* aren't defined for userspace.
> > struct page *data_page;
> > void *data;
> > int ret;
> >
> > + BUILD_BUG_ON(SGX_SECINFO_R != VM_READ || SGX_SECINFO_W != VM_WRITE ||
> > + SGX_SECINFO_X != VM_EXEC);
>
> Why this check?
To assert that the hardware defined SECINFO flags are interchangeable with
Linux's software defined flags, i.e. don't need to be translated.
>
> > +
> > + if (prot & ~allowed_prot)
> > + return -EACCES;
> > +
> > data_page = alloc_page(GFP_HIGHUSER);
> > if (!data_page)
> > return -ENOMEM;
> > @@ -593,7 +603,8 @@ static int sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> > goto out;
> > }
> >
> > - ret = __sgx_encl_add_page(encl, addr, data, secinfo, mrmask);
> > + ret = __sgx_encl_add_page(encl, addr, data, secinfo, mrmask,
> > + allowed_prot);
> > out:
> > kunmap(data_page);
> > __free_page(data_page);
> > @@ -645,7 +656,7 @@ static long sgx_ioc_enclave_add_pages(struct file *filep, unsigned int cmd,
> >
> > ret = sgx_encl_add_page(encl, addp->addr + i*PAGE_SIZE,
> > addp->src + i*PAGE_SIZE,
> > - &secinfo, addp->mrmask);
> > + &secinfo, addp->mrmask, addp->flags);
> > }
> > return ret;
> > }
> > diff --git a/arch/x86/kernel/cpu/sgx/encl.c b/arch/x86/kernel/cpu/sgx/encl.c
> > index 955d4f430adc..e5847571a265 100644
> > --- a/arch/x86/kernel/cpu/sgx/encl.c
> > +++ b/arch/x86/kernel/cpu/sgx/encl.c
> > @@ -249,7 +249,7 @@ int sgx_map_allowed(struct sgx_encl *encl, unsigned long start,
> >
> > for (addr = start; addr < end; addr += PAGE_SIZE) {
> > page = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT);
> > - if (!page)
> > + if (!page || (prot & ~page->allowed_prot))
> > return -EACCES;
> > }
>
> However this goes it would be good idea to have only ony patch in the
> patch set that fully defines this function. Impossible to review
> properly with this split.
Sorry, I don't understand what you're suggesting.
>
> >
> >
> > diff --git a/arch/x86/kernel/cpu/sgx/encl.h b/arch/x86/kernel/cpu/sgx/encl.h
> > index 6e310e3b3fff..7cca076a4987 100644
> > --- a/arch/x86/kernel/cpu/sgx/encl.h
> > +++ b/arch/x86/kernel/cpu/sgx/encl.h
> > @@ -41,6 +41,7 @@ enum sgx_encl_page_desc {
> >
> > struct sgx_encl_page {
> > unsigned long desc;
> > + unsigned long allowed_prot;
> > struct sgx_epc_page *epc_page;
> > struct sgx_va_page *va_page;
> > struct sgx_encl *encl;
> > --
> > 2.21.0
> >
>
> This patch left me very confused. I don't get it.
>
> /Jarkko
^ permalink raw reply
* [PATCH 8/8] Add sample notification program [ver #2]
From: David Howells @ 2019-06-04 16:36 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
This needs to be linked with -lkeyutils.
It is run like:
./watch_test
and watches "/" for mount changes and the current session keyring for key
changes:
# keyctl add user a a @s
1035096409
# keyctl unlink 1035096409 @s
# mount -t tmpfs none /mnt/nfsv3tcp/
# umount /mnt/nfsv3tcp
producing:
# ./watch_test
ptrs h=4 t=2 m=20003
NOTIFY[00000004-00000002] ty=0003 sy=0002 i=01000010
KEY 2ffc2e5d change=2[linked] aux=1035096409
ptrs h=6 t=4 m=20003
NOTIFY[00000006-00000004] ty=0003 sy=0003 i=01000010
KEY 2ffc2e5d change=3[unlinked] aux=1035096409
ptrs h=8 t=6 m=20003
NOTIFY[00000008-00000006] ty=0001 sy=0000 i=02000010
MOUNT 00000013 change=0[new_mount] aux=168
ptrs h=a t=8 m=20003
NOTIFY[0000000a-00000008] ty=0001 sy=0001 i=02000010
MOUNT 00000013 change=1[unmount] aux=168
Other events may be produced, such as with a failing disk:
ptrs h=5 t=2 m=6000004
NOTIFY[00000005-00000002] ty=0004 sy=0006 i=04000018
BLOCK 00800050 e=6[critical medium] s=5be8
This corresponds to:
print_req_error: critical medium error, dev sdf, sector 23528 flags 0
in dmesg.
Signed-off-by: David Howells <dhowells@redhat.com>
---
samples/Kconfig | 6 +
samples/Makefile | 1
samples/watch_queue/Makefile | 9 +
samples/watch_queue/watch_test.c | 284 ++++++++++++++++++++++++++++++++++++++
4 files changed, 300 insertions(+)
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
diff --git a/samples/Kconfig b/samples/Kconfig
index 0561a94f6fdb..a2b7a7babee5 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -160,4 +160,10 @@ config SAMPLE_VFS
as mount API and statx(). Note that this is restricted to the x86
arch whilst it accesses system calls that aren't yet in all arches.
+config SAMPLE_WATCH_QUEUE
+ bool "Build example /dev/watch_queue notification consumer"
+ help
+ Build example userspace program to use the new mount_notify(),
+ sb_notify() syscalls and the KEYCTL_WATCH_KEY keyctl() function.
+
endif # SAMPLES
diff --git a/samples/Makefile b/samples/Makefile
index debf8925f06f..ed3b8bab6e9b 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_SAMPLE_TRACE_PRINTK) += trace_printk/
obj-$(CONFIG_VIDEO_PCI_SKELETON) += v4l/
obj-y += vfio-mdev/
subdir-$(CONFIG_SAMPLE_VFS) += vfs
+subdir-$(CONFIG_SAMPLE_WATCH_QUEUE) += watch_queue
diff --git a/samples/watch_queue/Makefile b/samples/watch_queue/Makefile
new file mode 100644
index 000000000000..42b694430d0f
--- /dev/null
+++ b/samples/watch_queue/Makefile
@@ -0,0 +1,9 @@
+# List of programs to build
+hostprogs-y := watch_test
+
+# Tell kbuild to always build the programs
+always := $(hostprogs-y)
+
+HOSTCFLAGS_watch_test.o += -I$(objtree)/usr/include
+
+HOSTLOADLIBES_watch_test += -lkeyutils
diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c
new file mode 100644
index 000000000000..0bbab492e237
--- /dev/null
+++ b/samples/watch_queue/watch_test.c
@@ -0,0 +1,284 @@
+/* Use /dev/watch_queue to watch for keyring and mount topology changes.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <limits.h>
+#include <linux/watch_queue.h>
+#include <linux/unistd.h>
+#include <linux/keyctl.h>
+
+#ifndef __NR_mount_notify
+#define __NR_mount_notify -1
+#endif
+#ifndef __NR_sb_notify
+#define __NR_sb_notify -1
+#endif
+#ifndef __NR_block_notify
+#define __NR_block_notify -1
+#endif
+#ifndef KEYCTL_WATCH_KEY
+#define KEYCTL_WATCH_KEY -1
+#endif
+
+#define BUF_SIZE 4
+
+static const char *key_subtypes[256] = {
+ [NOTIFY_KEY_INSTANTIATED] = "instantiated",
+ [NOTIFY_KEY_UPDATED] = "updated",
+ [NOTIFY_KEY_LINKED] = "linked",
+ [NOTIFY_KEY_UNLINKED] = "unlinked",
+ [NOTIFY_KEY_CLEARED] = "cleared",
+ [NOTIFY_KEY_REVOKED] = "revoked",
+ [NOTIFY_KEY_INVALIDATED] = "invalidated",
+ [NOTIFY_KEY_SETATTR] = "setattr",
+};
+
+static void saw_key_change(struct watch_notification *n)
+{
+ struct key_notification *k = (struct key_notification *)n;
+ unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+ if (len != sizeof(struct key_notification))
+ return;
+
+ printf("KEY %08x change=%u[%s] aux=%u\n",
+ k->key_id, n->subtype, key_subtypes[n->subtype], k->aux);
+}
+
+static const char *mount_subtypes[256] = {
+ [NOTIFY_MOUNT_NEW_MOUNT] = "new_mount",
+ [NOTIFY_MOUNT_UNMOUNT] = "unmount",
+ [NOTIFY_MOUNT_EXPIRY] = "expiry",
+ [NOTIFY_MOUNT_READONLY] = "readonly",
+ [NOTIFY_MOUNT_SETATTR] = "setattr",
+ [NOTIFY_MOUNT_MOVE_FROM] = "move_from",
+ [NOTIFY_MOUNT_MOVE_TO] = "move_to",
+};
+
+static long keyctl_watch_key(int key, int watch_fd, int watch_id)
+{
+ return syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, watch_fd, watch_id);
+}
+
+static void saw_mount_change(struct watch_notification *n)
+{
+ struct mount_notification *m = (struct mount_notification *)n;
+ unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+ if (len != sizeof(struct mount_notification))
+ return;
+
+ printf("MOUNT %08x change=%u[%s] aux=%u\n",
+ m->triggered_on, n->subtype, mount_subtypes[n->subtype], m->changed_mount);
+}
+
+static const char *super_subtypes[256] = {
+ [NOTIFY_SUPERBLOCK_READONLY] = "readonly",
+ [NOTIFY_SUPERBLOCK_ERROR] = "error",
+ [NOTIFY_SUPERBLOCK_EDQUOT] = "edquot",
+ [NOTIFY_SUPERBLOCK_NETWORK] = "network",
+};
+
+static void saw_super_change(struct watch_notification *n)
+{
+ struct superblock_notification *s = (struct superblock_notification *)n;
+ unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+ if (len < sizeof(struct superblock_notification))
+ return;
+
+ printf("SUPER %08llx change=%u[%s]\n",
+ s->sb_id, n->subtype, super_subtypes[n->subtype]);
+}
+
+static const char *block_subtypes[256] = {
+ [NOTIFY_BLOCK_ERROR_TIMEOUT] = "timeout",
+ [NOTIFY_BLOCK_ERROR_NO_SPACE] = "critical space allocation",
+ [NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT] = "recoverable transport",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_TARGET] = "critical target",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS] = "critical nexus",
+ [NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM] = "critical medium",
+ [NOTIFY_BLOCK_ERROR_PROTECTION] = "protection",
+ [NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE] = "kernel resource",
+ [NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE] = "device resource",
+ [NOTIFY_BLOCK_ERROR_IO] = "I/O",
+};
+
+static void saw_block_change(struct watch_notification *n)
+{
+ struct block_notification *b = (struct block_notification *)n;
+ unsigned int len = n->info & WATCH_INFO_LENGTH;
+
+ if (len < sizeof(struct block_notification))
+ return;
+
+ printf("BLOCK %08llx e=%u[%s] s=%llx\n",
+ (unsigned long long)b->dev,
+ n->subtype, block_subtypes[n->subtype],
+ (unsigned long long)b->sector);
+}
+
+/*
+ * Consume and display events.
+ */
+static int consumer(int fd, struct watch_queue_buffer *buf)
+{
+ struct watch_notification *n;
+ struct pollfd p[1];
+ unsigned int head, tail, mask = buf->meta.mask;
+
+ for (;;) {
+ p[0].fd = fd;
+ p[0].events = POLLIN | POLLERR;
+ p[0].revents = 0;
+
+ if (poll(p, 1, -1) == -1) {
+ perror("poll");
+ break;
+ }
+
+ printf("ptrs h=%x t=%x m=%x\n",
+ buf->meta.head, buf->meta.tail, buf->meta.mask);
+
+ while (head = buf->meta.head,
+ tail = buf->meta.tail,
+ tail != head
+ ) {
+ asm ("lfence" : : : "memory" );
+ n = &buf->slots[tail & mask];
+ printf("NOTIFY[%08x-%08x] ty=%04x sy=%04x i=%08x\n",
+ head, tail, n->type, n->subtype, n->info);
+ if ((n->info & WATCH_INFO_LENGTH) == 0)
+ goto out;
+
+ switch (n->type) {
+ case WATCH_TYPE_META:
+ if (n->subtype == WATCH_META_REMOVAL_NOTIFICATION)
+ printf("REMOVAL of watchpoint %08x\n",
+ n->info & WATCH_INFO_ID);
+ break;
+ case WATCH_TYPE_MOUNT_NOTIFY:
+ saw_mount_change(n);
+ break;
+ case WATCH_TYPE_SB_NOTIFY:
+ saw_super_change(n);
+ break;
+ case WATCH_TYPE_KEY_NOTIFY:
+ saw_key_change(n);
+ break;
+ case WATCH_TYPE_BLOCK_NOTIFY:
+ saw_block_change(n);
+ break;
+ }
+
+ tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ asm("mfence" ::: "memory");
+ buf->meta.tail = tail;
+ }
+ }
+
+out:
+ return 0;
+}
+
+static struct watch_notification_filter filter = {
+ .nr_filters = 4,
+ .__reserved = 0,
+ .filters = {
+ [0] = {
+ .type = WATCH_TYPE_MOUNT_NOTIFY,
+ // Reject move-from notifications
+ .subtype_filter[0] = UINT_MAX & ~(1 << NOTIFY_MOUNT_MOVE_FROM),
+ },
+ [1] = {
+ .type = WATCH_TYPE_SB_NOTIFY,
+ // Only accept notification of changes to R/O state
+ .subtype_filter[0] = (1 << NOTIFY_SUPERBLOCK_READONLY),
+ // Only accept notifications of change-to-R/O
+ .info_mask = WATCH_INFO_FLAG_0,
+ .info_filter = WATCH_INFO_FLAG_0,
+ },
+ [2] = {
+ .type = WATCH_TYPE_KEY_NOTIFY,
+ .subtype_filter[0] = UINT_MAX,
+ },
+ [3] = {
+ .type = WATCH_TYPE_BLOCK_NOTIFY,
+ .subtype_filter[0] = UINT_MAX,
+ },
+ },
+};
+
+int main(int argc, char **argv)
+{
+ struct watch_queue_buffer *buf;
+ size_t page_size;
+ int fd;
+
+ fd = open("/dev/watch_queue", O_RDWR);
+ if (fd == -1) {
+ perror("/dev/watch_queue");
+ exit(1);
+ }
+
+ if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) {
+ perror("/dev/watch_queue(size)");
+ exit(1);
+ }
+
+ if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) {
+ perror("/dev/watch_queue(filter)");
+ exit(1);
+ }
+
+ page_size = sysconf(_SC_PAGESIZE);
+ buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (buf == MAP_FAILED) {
+ perror("mmap");
+ exit(1);
+ }
+
+ if (keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01) == -1) {
+ perror("keyctl");
+ exit(1);
+ }
+
+ if (syscall(__NR_mount_notify, AT_FDCWD, "/", 0, fd, 0x02) == -1) {
+ perror("mount_notify");
+ exit(1);
+ }
+
+ if (syscall(__NR_sb_notify, AT_FDCWD, "/mnt", 0, fd, 0x03) == -1) {
+ perror("sb_notify");
+ exit(1);
+ }
+
+ if (syscall(__NR_block_notify, fd, 0x04) == -1) {
+ perror("block_notify");
+ exit(1);
+ }
+
+ return consumer(fd, buf);
+}
^ permalink raw reply related
* [PATCH 7/8] block: Add block layer notifications [ver #2]
From: David Howells @ 2019-06-04 16:36 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
Add a block layer notification mechanism whereby notifications about
block-layer events such as I/O errors, can be reported to a monitoring
process asynchronously.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report block notifications via that
queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_BLOCK_NOTIFY,
.subtype_filter[0] = UINT_MAX;
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
block_notify(fd, 12);
After that, records will be placed into the queue when, for example, errors
occur on a block device. Records are of the following format:
struct block_notification {
struct watch_notification watch;
__u64 dev;
__u64 sector;
} *n;
Where:
n->watch.type will be WATCH_TYPE_BLOCK_NOTIFY
n->watch.subtype will be the type of notification, such as
NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
block_notify(), shifted.
n->dev will be the device numbers munged together.
n->sector will indicate the affected sector (if appropriate for the
event).
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
block/Kconfig | 9 +++
block/Makefile | 1
block/blk-core.c | 29 +++++++++++
block/blk-notify.c | 83 ++++++++++++++++++++++++++++++++
include/linux/blkdev.h | 10 ++++
include/linux/syscalls.h | 1
include/uapi/linux/watch_queue.h | 28 +++++++++++
kernel/sys_ni.c | 1
10 files changed, 164 insertions(+)
create mode 100644 block/blk-notify.c
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 429416ce60e1..22793f77c5f1 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -441,3 +441,4 @@
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
435 i386 mount_notify sys_mount_notify __ia32_sys_mount_notify
436 i386 sb_notify sys_sb_notify __ia32_sys_sb_notify
+437 i386 block_notify sys_block_notify __ia32_sys_block_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 4ae146e472db..3f0b82272a9f 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -358,6 +358,7 @@
434 common fsinfo __x64_sys_fsinfo
435 common mount_notify __x64_sys_mount_notify
436 common sb_notify __x64_sys_sb_notify
+437 common block_notify __x64_sys_block_notify
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/block/Kconfig b/block/Kconfig
index 1b220101a9cb..3b0a0ddb83ef 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -163,6 +163,15 @@ config BLK_SED_OPAL
Enabling this option enables users to setup/unlock/lock
Locking ranges for SED devices using the Opal protocol.
+config BLK_NOTIFICATIONS
+ bool "Block layer event notifications"
+ select WATCH_QUEUE
+ help
+ This option provides support for getting block layer event
+ notifications. This makes use of the /dev/watch_queue misc device to
+ handle the notification buffer and provides the block_notify() system
+ call to enable/disable watches.
+
menu "Partition Types"
source "block/partitions/Kconfig"
diff --git a/block/Makefile b/block/Makefile
index eee1b4ceecf9..2dca6273f8f3 100644
--- a/block/Makefile
+++ b/block/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_BLK_DEBUG_FS) += blk-mq-debugfs.o
obj-$(CONFIG_BLK_DEBUG_FS_ZONED)+= blk-mq-debugfs-zoned.o
obj-$(CONFIG_BLK_SED_OPAL) += sed-opal.o
obj-$(CONFIG_BLK_PM) += blk-pm.o
+obj-$(CONFIG_BLK_NOTIFICATIONS) += blk-notify.o
diff --git a/block/blk-core.c b/block/blk-core.c
index 419d600e6637..edad86172d47 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -144,6 +144,22 @@ static const struct {
[BLK_STS_IOERR] = { -EIO, "I/O" },
};
+#ifdef CONFIG_BLK_NOTIFICATIONS
+static const
+enum block_notification_type blk_notifications[ARRAY_SIZE(blk_errors)] = {
+ [BLK_STS_TIMEOUT] = NOTIFY_BLOCK_ERROR_TIMEOUT,
+ [BLK_STS_NOSPC] = NOTIFY_BLOCK_ERROR_NO_SPACE,
+ [BLK_STS_TRANSPORT] = NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT,
+ [BLK_STS_TARGET] = NOTIFY_BLOCK_ERROR_CRITICAL_TARGET,
+ [BLK_STS_NEXUS] = NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS,
+ [BLK_STS_MEDIUM] = NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM,
+ [BLK_STS_PROTECTION] = NOTIFY_BLOCK_ERROR_PROTECTION,
+ [BLK_STS_RESOURCE] = NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE,
+ [BLK_STS_DEV_RESOURCE] = NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE,
+ [BLK_STS_IOERR] = NOTIFY_BLOCK_ERROR_IO,
+};
+#endif
+
blk_status_t errno_to_blk_status(int errno)
{
int i;
@@ -179,6 +195,19 @@ static void print_req_error(struct request *req, blk_status_t status)
req->rq_disk ? req->rq_disk->disk_name : "?",
(unsigned long long)blk_rq_pos(req),
req->cmd_flags);
+
+#ifdef CONFIG_BLK_NOTIFICATIONS
+ if (blk_notifications[idx]) {
+ struct block_notification n = {
+ .watch.type = WATCH_TYPE_BLOCK_NOTIFY,
+ .watch.subtype = blk_notifications[idx],
+ .watch.info = sizeof(n),
+ .dev = req->rq_disk ? disk_devt(req->rq_disk) : 0,
+ .sector = blk_rq_pos(req),
+ };
+ post_block_notification(&n);
+ }
+#endif
}
static void req_bio_endio(struct request *rq, struct bio *bio,
diff --git a/block/blk-notify.c b/block/blk-notify.c
new file mode 100644
index 000000000000..b310aaf37e7c
--- /dev/null
+++ b/block/blk-notify.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Block layer event notifications.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/blkdev.h>
+#include <linux/watch_queue.h>
+#include <linux/syscalls.h>
+#include <linux/init_task.h>
+
+/*
+ * Global queue for watching for block layer events.
+ */
+static struct watch_list blk_watchers = {
+ .watchers = HLIST_HEAD_INIT,
+ .lock = __SPIN_LOCK_UNLOCKED(&blk_watchers.lock),
+};
+
+static DEFINE_SPINLOCK(blk_watchers_lock);
+
+/*
+ * Post superblock notifications.
+ *
+ * Note that there's only a global queue to which all events are posted. Might
+ * want to provide per-dev queues also.
+ */
+void post_block_notification(struct block_notification *n)
+{
+ u64 id = 0; /* Might want to allow dev# here. */
+
+ post_watch_notification(&blk_watchers, &n->watch, &init_cred, id);
+}
+
+/**
+ * sys_block_notify - Watch for superblock events.
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE2(block_notify, int, watch_fd, int, watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = &blk_watchers;
+ struct watch *watch;
+ long ret = -ENOMEM;
+ u64 id = 0; /* Might want to allow dev# here. */
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err;
+ }
+
+ if (watch_id >= 0) {
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wqueue;
+
+ init_watch(watch, wqueue);
+ watch->id = id;
+ watch->info_id = (u32)watch_id << WATCH_INFO_ID__SHIFT;
+
+ spin_lock(&blk_watchers_lock);
+ ret = add_watch_to_object(watch, wlist);
+ spin_unlock(&blk_watchers_lock);
+ if (ret < 0)
+ kfree(watch);
+ } else {
+ spin_lock(&blk_watchers_lock);
+ ret = remove_watch_from_object(wlist, wqueue, id, false);
+ spin_unlock(&blk_watchers_lock);
+ }
+
+err_wqueue:
+ put_watch_queue(wqueue);
+err:
+ return ret;
+}
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 1aafeb923e7b..c28f8647a76d 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -43,6 +43,7 @@ struct pr_ops;
struct rq_qos;
struct blk_queue_stats;
struct blk_stat_callback;
+struct block_notification;
#define BLKDEV_MIN_RQ 4
#define BLKDEV_MAX_RQ 128 /* Default maximum */
@@ -1744,6 +1745,15 @@ static inline bool blk_req_can_dispatch_to_zone(struct request *rq)
}
#endif /* CONFIG_BLK_DEV_ZONED */
+#ifdef CONFIG_BLK_NOTIFICATIONS
+extern void post_block_notification(struct block_notification *n);
+#else
+static inline void post_block_notification(struct block_notification *n)
+{
+}
+#endif
+
+
#else /* CONFIG_BLOCK */
struct block_device;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 204a6dbcc34a..77a9d84f1fbd 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1005,6 +1005,7 @@ asmlinkage long sys_mount_notify(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
asmlinkage long sys_sb_notify(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
+asmlinkage long sys_block_notify(int watch_fd, int watch_id);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 02c330462af8..231eafa3df99 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -44,6 +44,7 @@ struct watch_notification {
#define WATCH_INFO_FLAG_6 0x00400000
#define WATCH_INFO_FLAG_7 0x00800000
#define WATCH_INFO_ID 0xff000000 /* ID of watchpoint */
+#define WATCH_INFO_ID__SHIFT 24
};
#define WATCH_LENGTH_SHIFT 3
@@ -154,4 +155,31 @@ struct superblock_error_notification {
__u32 error_cookie;
};
+/*
+ * Type of block layer notification.
+ */
+enum block_notification_type {
+ NOTIFY_BLOCK_ERROR_TIMEOUT = 1, /* Timeout error */
+ NOTIFY_BLOCK_ERROR_NO_SPACE = 2, /* Critical space allocation error */
+ NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT = 3, /* Recoverable transport error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_TARGET = 4, /* Critical target error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS = 5, /* Critical nexus error */
+ NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM = 6, /* Critical medium error */
+ NOTIFY_BLOCK_ERROR_PROTECTION = 7, /* Protection error */
+ NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE = 8, /* Kernel resource error */
+ NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE = 9, /* Device resource error */
+ NOTIFY_BLOCK_ERROR_IO = 10, /* Other I/O error */
+};
+
+/*
+ * Block notification record.
+ * - watch.type = WATCH_TYPE_BLOCK_NOTIFY
+ * - watch.subtype = enum block_notification_type
+ */
+struct block_notification {
+ struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
+ __u64 dev; /* Device number */
+ __u64 sector; /* Affected sector */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 565d1e3d1bed..6178455ac568 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents);
COND_SYSCALL(io_uring_setup);
COND_SYSCALL(io_uring_enter);
COND_SYSCALL(io_uring_register);
+COND_SYSCALL(block_notify);
/* fs/xattr.c */
^ permalink raw reply related
* [PATCH 6/8] fsinfo: Export superblock notification counter [ver #2]
From: David Howells @ 2019-06-04 16:36 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
Provide an fsinfo attribute to export the superblock notification counter
so that it can be polled in the case of a notification buffer overrun.
This is accessed with:
struct fsinfo_params params = {
.request = FSINFO_ATTR_SB_NOTIFICATIONS,
};
and returns a structure that looks like:
struct fsinfo_sb_notifications {
__u64 watch_id;
__u32 notify_counter;
__u32 __reserved[1];
};
Where watch_id is a number uniquely identifying the superblock in
notification records and notify_counter is incremented for each
superblock notification posted.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 12 ++++++++++++
fs/super.c | 1 +
include/linux/fs.h | 1 +
include/uapi/linux/fsinfo.h | 10 ++++++++++
include/uapi/linux/watch_queue.h | 2 +-
samples/vfs/test-fsinfo.c | 13 +++++++++++++
6 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 3ec64d3cba08..1456e26d2f7c 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -284,6 +284,16 @@ static int fsinfo_generic_param_enum(struct file_system_type *f,
return sizeof(*p);
}
+static int fsinfo_generic_sb_notifications(struct path *path,
+ struct fsinfo_sb_notifications *p)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ p->watch_id = sb->s_unique_id;
+ p->notify_counter = atomic_read(&sb->s_notify_counter);
+ return sizeof(*p);
+}
+
static void fsinfo_insert_sb_flag_parameters(struct path *path,
struct fsinfo_kparams *params)
{
@@ -331,6 +341,7 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
case _genp(MOUNT_DEVNAME, mount_devname);
case _genp(MOUNT_CHILDREN, mount_children);
case _genp(MOUNT_SUBMOUNT, mount_submount);
+ case _gen(SB_NOTIFICATIONS, sb_notifications);
default:
return -EOPNOTSUPP;
}
@@ -606,6 +617,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING_N (SERVER_NAME, server_name),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (CELL_NAME, cell_name),
+ FSINFO_STRUCT (SB_NOTIFICATIONS, sb_notifications),
};
/**
diff --git a/fs/super.c b/fs/super.c
index ae44745e6e2c..832d1a1865c9 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1823,6 +1823,7 @@ EXPORT_SYMBOL(thaw_super);
*/
void post_sb_notification(struct super_block *s, struct superblock_notification *n)
{
+ atomic_inc(&s->s_notify_counter);
post_watch_notification(s->s_watchers, &n->watch, current_cred(),
s->s_unique_id);
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 02ba4bfb9cc3..06e272a25ed7 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1536,6 +1536,7 @@ struct super_block {
#ifdef CONFIG_SB_NOTIFICATIONS
struct watch_list *s_watchers;
#endif
+ atomic_t s_notify_counter;
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 7247088332c2..b4c9446305bb 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -39,6 +39,7 @@ enum fsinfo_attribute {
FSINFO_ATTR_SERVER_NAME = 21, /* Name of the Nth server (string) */
FSINFO_ATTR_SERVER_ADDRESS = 22, /* Mth address of the Nth server */
FSINFO_ATTR_CELL_NAME = 23, /* Cell name (string) */
+ FSINFO_ATTR_SB_NOTIFICATIONS = 24, /* sb_notify() information */
FSINFO_ATTR__NR
};
@@ -308,4 +309,13 @@ struct fsinfo_server_address {
struct __kernel_sockaddr_storage address;
};
+/*
+ * Information struct for fsinfo(FSINFO_ATTR_SB_NOTIFICATIONS).
+ */
+struct fsinfo_sb_notifications {
+ __u64 watch_id; /* Watch ID for superblock. */
+ __u32 notify_counter; /* Number of notifications. */
+ __u32 __reserved[1];
+};
+
#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 1f09247e49f3..02c330462af8 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -145,7 +145,7 @@ enum superblock_notification_type {
*/
struct superblock_notification {
struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
- __u64 sb_id; /* 64-bit superblock ID [fsinfo_ids::f_sb_id] */
+ __u64 sb_id; /* 64-bit superblock ID [fsinfo_sb_notifications::watch_id] */
};
struct superblock_error_notification {
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index af29da74559e..0f8f9ded0925 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -90,6 +90,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING_N (SERVER_NAME, server_name),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (CELL_NAME, cell_name),
+ FSINFO_STRUCT (SB_NOTIFICATIONS, sb_notifications),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -118,6 +119,7 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (SERVER_NAME, server_name),
FSINFO_NAME (SERVER_ADDRESS, server_address),
FSINFO_NAME (CELL_NAME, cell_name),
+ FSINFO_NAME (SB_NOTIFICATIONS, sb_notifications),
};
union reply {
@@ -133,6 +135,7 @@ union reply {
struct fsinfo_mount_info mount_info;
struct fsinfo_mount_child mount_children[1];
struct fsinfo_server_address srv_addr;
+ struct fsinfo_sb_notifications sb_notifications;
};
static void dump_hex(unsigned int *data, int from, int to)
@@ -377,6 +380,15 @@ static void dump_attr_MOUNT_CHILDREN(union reply *r, int size)
printf("\t[%u] %8x %8x\n", i++, f->mnt_id, f->notify_counter);
}
+static void dump_attr_SB_NOTIFICATIONS(union reply *r, int size)
+{
+ struct fsinfo_sb_notifications *f = &r->sb_notifications;
+
+ printf("\n");
+ printf("\twatch_id: %llx\n", (unsigned long long)f->watch_id);
+ printf("\tnotifs : %llx\n", (unsigned long long)f->notify_counter);
+}
+
/*
*
*/
@@ -395,6 +407,7 @@ static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
FSINFO_DUMPER(MOUNT_INFO),
FSINFO_DUMPER(MOUNT_CHILDREN),
FSINFO_DUMPER(SERVER_ADDRESS),
+ FSINFO_DUMPER(SB_NOTIFICATIONS),
};
static void dump_fsinfo(enum fsinfo_attribute attr,
^ permalink raw reply related
* [PATCH 5/8] vfs: Add superblock notifications [ver #2]
From: David Howells @ 2019-06-04 16:35 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously. Note that this does not cover vfsmount topology
changes. mount_notify() is used for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_SB_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
sb_notify(AT_FDCWD, "/home/dhowells", 0, fd, 0x03);
In this case, it would let me monitor my own homedir for events. After
setting the watch, records will be placed into the queue when, for example,
as superblock switches between read-write and read-only. Records are of
the following format:
struct superblock_notification {
struct watch_notification watch;
__u64 sb_id;
} *n;
Where:
n->watch.type will be WATCH_TYPE_SB_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_SUPERBLOCK_READONLY.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
sb_notify(), shifted.
n->watch.info & WATCH_INFO_FLAG_0 will be used for
NOTIFY_SUPERBLOCK_READONLY, being set if the superblock becomes
R/O, and being cleared otherwise.
n->sb_id will be the ID of the superblock, as can be retrieved with
the fsinfo() syscall, as part of the fsinfo_sb_notifications
attribute in the the watch_id field.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
[*] QUESTION: Does this want to be per-sb, per-mount_namespace,
per-some-new-notify-ns or per-system? Or do multiple options make
sense?
[*] QUESTION: I've done it this way so that anyone could theoretically
monitor the superblock of any filesystem they can pathwalk to, but do
we need other security controls?
[*] QUESTION: Should the LSM be able to filter the events a queue can
receive? For instance the opener of the queue would grant that queue
subject creds (by ->f_cred) that could be used to govern what events
could be seen, assuming the target superblock to have some object
creds, based on, say, the mounter.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 12 +++
fs/super.c | 116 ++++++++++++++++++++++++++++++++
include/linux/fs.h | 77 +++++++++++++++++++++
include/linux/syscalls.h | 2 +
include/uapi/linux/watch_queue.h | 26 +++++++
kernel/sys_ni.c | 3 +
8 files changed, 238 insertions(+)
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index a8416a9a0ccb..429416ce60e1 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -440,3 +440,4 @@
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
435 i386 mount_notify sys_mount_notify __ia32_sys_mount_notify
+436 i386 sb_notify sys_sb_notify __ia32_sys_sb_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index ea052a94eb97..4ae146e472db 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -357,6 +357,7 @@
433 common fspick __x64_sys_fspick
434 common fsinfo __x64_sys_fsinfo
435 common mount_notify __x64_sys_mount_notify
+436 common sb_notify __x64_sys_sb_notify
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index a26bbe27a791..fc0fa4b35f3c 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -130,6 +130,18 @@ config MOUNT_NOTIFICATIONS
device to handle the notification buffer and provides the
mount_notify() system call to enable/disable watchpoints.
+config SB_NOTIFICATIONS
+ bool "Superblock event notifications"
+ select WATCH_QUEUE
+ help
+ This option provides support for receiving superblock event
+ notifications. This makes use of the /dev/watch_queue misc device to
+ handle the notification buffer and provides the sb_notify() system
+ call to enable/disable watches.
+
+ Events can include things like changing between R/W and R/O, EIO
+ generation, ENOSPC generation and EDQUOT generation.
+
source "fs/quota/Kconfig"
source "fs/autofs/Kconfig"
diff --git a/fs/super.c b/fs/super.c
index 61819e8e5469..ae44745e6e2c 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -36,6 +36,8 @@
#include <linux/lockdep.h>
#include <linux/user_namespace.h>
#include <linux/fs_context.h>
+#include <linux/syscalls.h>
+#include <linux/namei.h>
#include <uapi/linux/mount.h>
#include "internal.h"
@@ -350,6 +352,10 @@ void deactivate_locked_super(struct super_block *s)
{
struct file_system_type *fs = s->s_type;
if (atomic_dec_and_test(&s->s_active)) {
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (s->s_watchers)
+ remove_watch_list(s->s_watchers);
+#endif
cleancache_invalidate_fs(s);
unregister_shrinker(&s->s_shrink);
fs->kill_sb(s);
@@ -990,6 +996,8 @@ int reconfigure_super(struct fs_context *fc)
/* Needs to be ordered wrt mnt_is_readonly() */
smp_wmb();
sb->s_readonly_remount = 0;
+ notify_sb(sb, NOTIFY_SUPERBLOCK_READONLY,
+ remount_ro ? WATCH_INFO_FLAG_0 : 0);
/*
* Some filesystems modify their metadata via some other path than the
@@ -1808,3 +1816,111 @@ int thaw_super(struct super_block *sb)
return thaw_super_locked(sb);
}
EXPORT_SYMBOL(thaw_super);
+
+#ifdef CONFIG_SB_NOTIFICATIONS
+/*
+ * Post superblock notifications.
+ */
+void post_sb_notification(struct super_block *s, struct superblock_notification *n)
+{
+ post_watch_notification(s->s_watchers, &n->watch, current_cred(),
+ s->s_unique_id);
+}
+
+/**
+ * sys_sb_notify - Watch for superblock events.
+ * @dfd: Base directory to pathwalk from or fd referring to superblock.
+ * @filename: Path to superblock to place the watch upon
+ * @at_flags: Pathwalk control flags
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE5(sb_notify,
+ int, dfd,
+ const char __user *, filename,
+ unsigned int, at_flags,
+ int, watch_fd,
+ int, watch_id)
+{
+ struct watch_queue *wqueue;
+ struct super_block *s;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct path path;
+ unsigned int lookup_flags =
+ LOOKUP_DIRECTORY | LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ int ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+ if ((at_flags & ~(AT_NO_AUTOMOUNT | AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+ if (at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (at_flags & AT_EMPTY_PATH)
+ lookup_flags |= LOOKUP_EMPTY;
+
+ ret = user_path_at(dfd, filename, at_flags, &path);
+ if (ret)
+ return ret;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue))
+ goto err_path;
+
+ s = path.dentry->d_sb;
+ if (watch_id >= 0) {
+ if (!s->s_watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = s->s_unique_id;
+ watch->private = s;
+ watch->info_id = (u32)watch_id << 24;
+
+ down_write(&s->s_umount);
+ ret = -EIO;
+ if (atomic_read(&s->s_active)) {
+ if (!s->s_watchers) {
+ s->s_watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, s->s_watchers);
+ if (ret == 0) {
+ spin_lock(&sb_lock);
+ s->s_count++;
+ spin_unlock(&sb_lock);
+ }
+ }
+ up_write(&s->s_umount);
+ if (ret < 0)
+ kfree(watch);
+ } else {
+ ret = -EBADSLT;
+ if (READ_ONCE(s->s_watchers)) {
+ down_write(&s->s_umount);
+ ret = remove_watch_from_object(s->s_watchers, wqueue,
+ s->s_unique_id, false);
+ up_write(&s->s_umount);
+ }
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_path:
+ path_put(&path);
+ return ret;
+}
+#endif
diff --git a/include/linux/fs.h b/include/linux/fs.h
index db05738b1951..02ba4bfb9cc3 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -40,6 +40,7 @@
#include <linux/fs_types.h>
#include <linux/build_bug.h>
#include <linux/stddef.h>
+#include <linux/watch_queue.h>
#include <asm/byteorder.h>
#include <uapi/linux/fs.h>
@@ -1531,6 +1532,10 @@ struct super_block {
/* Superblock event notifications */
u64 s_unique_id;
+
+#ifdef CONFIG_SB_NOTIFICATIONS
+ struct watch_list *s_watchers;
+#endif
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
@@ -3531,4 +3536,76 @@ static inline struct sock *io_uring_get_socket(struct file *file)
}
#endif
+extern void post_sb_notification(struct super_block *, struct superblock_notification *);
+
+/**
+ * notify_sb: Post simple superblock notification.
+ * @s: The superblock the notification is about.
+ * @subtype: The type of notification.
+ * @info: WATCH_INFO_FLAG_* flags to be set in the record.
+ */
+static inline void notify_sb(struct super_block *s,
+ enum superblock_notification_type subtype,
+ u32 info)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
+ struct superblock_notification n = {
+ .watch.type = WATCH_TYPE_SB_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = sizeof(n) | info,
+ .sb_id = s->s_unique_id,
+ };
+
+ post_sb_notification(s, &n);
+ }
+
+#endif
+}
+
+/**
+ * notify_sb_error: Post superblock error notification.
+ * @s: The superblock the notification is about.
+ * @error: The error number to be recorded.
+ */
+static inline int notify_sb_error(struct super_block *s, int error)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
+ struct superblock_error_notification n = {
+ .s.watch.type = WATCH_TYPE_SB_NOTIFY,
+ .s.watch.subtype = NOTIFY_SUPERBLOCK_ERROR,
+ .s.watch.info = sizeof(n),
+ .s.sb_id = s->s_unique_id,
+ .error_number = error,
+ .error_cookie = 0,
+ };
+
+ post_sb_notification(s, &n.s);
+ }
+#endif
+ return error;
+}
+
+/**
+ * notify_sb_EDQUOT: Post superblock quota overrun notification.
+ * @s: The superblock the notification is about.
+ */
+static inline int notify_sb_EQDUOT(struct super_block *s)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
+ struct superblock_notification n = {
+ .watch.type = WATCH_TYPE_SB_NOTIFY,
+ .watch.subtype = NOTIFY_SUPERBLOCK_EDQUOT,
+ .watch.info = sizeof(n),
+ .sb_id = s->s_unique_id,
+ };
+
+ post_sb_notification(s, &n);
+ }
+#endif
+ return -EDQUOT;
+}
+
#endif /* _LINUX_FS_H */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 7c2b66175f3c..204a6dbcc34a 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1003,6 +1003,8 @@ asmlinkage long sys_fsinfo(int dfd, const char __user *path,
void __user *buffer, size_t buf_size);
asmlinkage long sys_mount_notify(int dfd, const char __user *path,
unsigned int at_flags, int watch_fd, int watch_id);
+asmlinkage long sys_sb_notify(int dfd, const char __user *path,
+ unsigned int at_flags, int watch_fd, int watch_id);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 6cb923f3454e..1f09247e49f3 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -128,4 +128,30 @@ struct mount_notification {
__u32 changed_mount; /* The mount that got changed */
};
+/*
+ * Type of superblock notification.
+ */
+enum superblock_notification_type {
+ NOTIFY_SUPERBLOCK_READONLY = 0, /* Filesystem toggled between R/O and R/W */
+ NOTIFY_SUPERBLOCK_ERROR = 1, /* Error in filesystem or blockdev */
+ NOTIFY_SUPERBLOCK_EDQUOT = 2, /* EDQUOT notification */
+ NOTIFY_SUPERBLOCK_NETWORK = 3, /* Network status change */
+};
+
+/*
+ * Superblock notification record.
+ * - watch.type = WATCH_TYPE_MOUNT_NOTIFY
+ * - watch.subtype = enum superblock_notification_subtype
+ */
+struct superblock_notification {
+ struct watch_notification watch; /* WATCH_TYPE_SB_NOTIFY */
+ __u64 sb_id; /* 64-bit superblock ID [fsinfo_ids::f_sb_id] */
+};
+
+struct superblock_error_notification {
+ struct superblock_notification s; /* subtype = notify_superblock_error */
+ __u32 error_number;
+ __u32 error_cookie;
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 97b025e7863c..565d1e3d1bed 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -108,6 +108,9 @@ COND_SYSCALL(quotactl);
/* fs/read_write.c */
+/* fs/sb_notify.c */
+COND_SYSCALL(sb_notify);
+
/* fs/sendfile.c */
/* fs/select.c */
^ permalink raw reply related
* [PATCH 4/8] vfs: Add a mount-notification facility [ver #2]
From: David Howells @ 2019-06-04 16:35 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
Add a mount notification facility whereby notifications about changes in
mount topology and configuration can be received. Note that this only
covers vfsmount topology changes and not superblock events. A separate
facility will be added for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_MOUNT_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
In this case, it would let me monitor the mount topology subtree rooted at
"/" for events. Mount notifications propagate up the tree towards the
root, so a watch will catch all of the events happening in the subtree
rooted at the watch.
After setting the watch, records will be placed into the queue when, for
example, as superblock switches between read-write and read-only. Records
are of the following format:
struct mount_notification {
struct watch_notification watch;
__u32 triggered_on;
__u32 changed_mount;
} *n;
Where:
n->watch.type will be WATCH_TYPE_MOUNT_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_MOUNT_NEW_MOUNT.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
mount_notify(), shifted.
n->watch.info & WATCH_INFO_FLAG_0 will be used for
NOTIFY_MOUNT_READONLY, being set if the superblock becomes R/O, and
being cleared otherwise, and for NOTIFY_MOUNT_NEW_MOUNT, being set
if the new mount is a submount (e.g. an automount).
n->triggered_on indicates the ID of the mount on which the watch
was installed.
n->changed_mount indicates the ID of the mount that was affected.
The mount IDs can be retrieved with the fsinfo() syscall, using the
fsinfo_mount_info and fsinfo_mount_child attributes. There are
notification counters there too for when a buffer overrun occurs, thereby
allowing the mount tree to be quickly rescanned.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 9 ++
fs/Makefile | 1
fs/mount.h | 33 ++++--
fs/mount_notify.c | 186 ++++++++++++++++++++++++++++++++
fs/namespace.c | 9 +-
include/linux/dcache.h | 1
include/linux/syscalls.h | 2
include/uapi/linux/watch_queue.h | 24 ++++
kernel/sys_ni.c | 3 +
11 files changed, 256 insertions(+), 14 deletions(-)
create mode 100644 fs/mount_notify.c
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 03decae51513..a8416a9a0ccb 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -439,3 +439,4 @@
432 i386 fsmount sys_fsmount __ia32_sys_fsmount
433 i386 fspick sys_fspick __ia32_sys_fspick
434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
+435 i386 mount_notify sys_mount_notify __ia32_sys_mount_notify
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index ea63df9a1020..ea052a94eb97 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -356,6 +356,7 @@
432 common fsmount __x64_sys_fsmount
433 common fspick __x64_sys_fspick
434 common fsinfo __x64_sys_fsinfo
+435 common mount_notify __x64_sys_mount_notify
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index 9e7d2f2c0111..a26bbe27a791 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -121,6 +121,15 @@ source "fs/crypto/Kconfig"
source "fs/notify/Kconfig"
+config MOUNT_NOTIFICATIONS
+ bool "Mount topology change notifications"
+ select WATCH_QUEUE
+ help
+ This option provides support for getting change notifications on the
+ mount tree topology. This makes use of the /dev/watch_queue misc
+ device to handle the notification buffer and provides the
+ mount_notify() system call to enable/disable watchpoints.
+
source "fs/quota/Kconfig"
source "fs/autofs/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index 26eaeae4b9a1..c6a71daf2464 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -131,3 +131,4 @@ obj-$(CONFIG_F2FS_FS) += f2fs/
obj-$(CONFIG_CEPH_FS) += ceph/
obj-$(CONFIG_PSTORE) += pstore/
obj-$(CONFIG_EFIVAR_FS) += efivarfs/
+obj-$(CONFIG_MOUNT_NOTIFICATIONS) += mount_notify.o
diff --git a/fs/mount.h b/fs/mount.h
index 47795802f78e..a95b805d00d8 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -4,6 +4,7 @@
#include <linux/poll.h>
#include <linux/ns_common.h>
#include <linux/fs_pin.h>
+#include <linux/watch_queue.h>
struct mnt_namespace {
atomic_t count;
@@ -67,9 +68,13 @@ struct mount {
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
+ int mnt_nr_watchers; /* The number of subtree watches tracking this */
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ struct watch_list *mnt_watchers; /* Watches on dentries within this mount */
+#endif
atomic_t mnt_notify_counter; /* Number of notifications generated */
} __randomize_layout;
@@ -153,18 +158,8 @@ static inline bool is_anon_ns(struct mnt_namespace *ns)
return ns->seq == 0;
}
-/*
- * Type of mount topology change notification.
- */
-enum mount_notification_subtype {
- NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */
- NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */
- NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */
- NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */
- NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */
- NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */
- NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */
-};
+extern void post_mount_notification(struct mount *changed,
+ struct mount_notification *notify);
static inline void notify_mount(struct mount *changed,
struct mount *aux,
@@ -172,4 +167,18 @@ static inline void notify_mount(struct mount *changed,
u32 info_flags)
{
atomic_inc(&changed->mnt_notify_counter);
+
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ {
+ struct mount_notification n = {
+ .watch.type = WATCH_TYPE_MOUNT_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = info_flags | sizeof(n),
+ .triggered_on = changed->mnt_id,
+ .changed_mount = aux ? aux->mnt_id : 0,
+ };
+
+ post_mount_notification(changed, &n);
+ }
+#endif
}
diff --git a/fs/mount_notify.c b/fs/mount_notify.c
new file mode 100644
index 000000000000..46806c2764d2
--- /dev/null
+++ b/fs/mount_notify.c
@@ -0,0 +1,186 @@
+/* Provide mount topology/attribute change notifications.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/syscalls.h>
+#include <linux/slab.h>
+#include "mount.h"
+
+/*
+ * Post mount notifications to all watches going rootwards along the tree.
+ *
+ * Must be called with the mount_lock held.
+ */
+void post_mount_notification(struct mount *changed,
+ struct mount_notification *notify)
+{
+ const struct cred *cred = current_cred();
+ struct path cursor;
+ struct mount *mnt;
+ unsigned seq;
+
+ seq = 0;
+ rcu_read_lock();
+restart:
+ cursor.mnt = &changed->mnt;
+ cursor.dentry = changed->mnt.mnt_root;
+ mnt = real_mount(cursor.mnt);
+ notify->watch.info &= ~WATCH_INFO_IN_SUBTREE;
+
+ read_seqbegin_or_lock(&rename_lock, &seq);
+ for (;;) {
+ if (mnt->mnt_watchers &&
+ !hlist_empty(&mnt->mnt_watchers->watchers)) {
+ if (cursor.dentry->d_flags & DCACHE_MOUNT_WATCH)
+ post_watch_notification(mnt->mnt_watchers,
+ ¬ify->watch, cred,
+ (unsigned long)cursor.dentry);
+ } else {
+ cursor.dentry = mnt->mnt.mnt_root;
+ }
+ notify->watch.info |= WATCH_INFO_IN_SUBTREE;
+
+ if (cursor.dentry == cursor.mnt->mnt_root ||
+ IS_ROOT(cursor.dentry)) {
+ struct mount *parent = READ_ONCE(mnt->mnt_parent);
+
+ /* Escaped? */
+ if (cursor.dentry != cursor.mnt->mnt_root)
+ break;
+
+ /* Global root? */
+ if (mnt == parent)
+ break;
+
+ cursor.dentry = READ_ONCE(mnt->mnt_mountpoint);
+ mnt = parent;
+ cursor.mnt = &mnt->mnt;
+ } else {
+ cursor.dentry = cursor.dentry->d_parent;
+ }
+ }
+
+ if (need_seqretry(&rename_lock, seq)) {
+ seq = 1;
+ goto restart;
+ }
+
+ done_seqretry(&rename_lock, seq);
+ rcu_read_unlock();
+}
+
+static void release_mount_watch(struct watch *watch)
+{
+ struct vfsmount *mnt = watch->private;
+ struct dentry *dentry = (struct dentry *)(unsigned long)watch->id;
+
+ dput(dentry);
+ mntput(mnt);
+}
+
+/**
+ * sys_mount_notify - Watch for mount topology/attribute changes
+ * @dfd: Base directory to pathwalk from or fd referring to mount.
+ * @filename: Path to mount to place the watch upon
+ * @at_flags: Pathwalk control flags
+ * @watch_fd: The watch queue to send notifications to.
+ * @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
+ */
+SYSCALL_DEFINE5(mount_notify,
+ int, dfd,
+ const char __user *, filename,
+ unsigned int, at_flags,
+ int, watch_fd,
+ int, watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct mount *m;
+ struct path path;
+ unsigned int lookup_flags =
+ LOOKUP_DIRECTORY | LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ int ret;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+ if ((at_flags & ~(AT_NO_AUTOMOUNT | AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+ if (at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (at_flags & AT_EMPTY_PATH)
+ lookup_flags |= LOOKUP_EMPTY;
+
+ ret = user_path_at(dfd, filename, lookup_flags, &path);
+ if (ret)
+ return ret;
+
+ wqueue = get_watch_queue(watch_fd);
+ if (IS_ERR(wqueue))
+ goto err_path;
+
+ m = real_mount(path.mnt);
+
+ if (watch_id >= 0) {
+ if (!m->mnt_watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ wlist->release_watch = release_mount_watch;
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = (unsigned long)path.dentry;
+ watch->private = path.mnt;
+ watch->info_id = (u32)watch_id << 24;
+
+ down_write(&m->mnt.mnt_sb->s_umount);
+ if (!m->mnt_watchers) {
+ m->mnt_watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, m->mnt_watchers);
+ if (ret == 0) {
+ spin_lock(&path.dentry->d_lock);
+ path.dentry->d_flags |= DCACHE_MOUNT_WATCH;
+ spin_unlock(&path.dentry->d_lock);
+ path_get(&path);
+ }
+ up_write(&m->mnt.mnt_sb->s_umount);
+ if (ret < 0)
+ kfree(watch);
+ } else {
+ ret = -EBADSLT;
+ if (m->mnt_watchers) {
+ down_write(&m->mnt.mnt_sb->s_umount);
+ ret = remove_watch_from_object(m->mnt_watchers, wqueue,
+ (unsigned long)path.dentry,
+ false);
+ up_write(&m->mnt.mnt_sb->s_umount);
+ }
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_path:
+ path_put(&path);
+ return ret;
+}
diff --git a/fs/namespace.c b/fs/namespace.c
index ae03066b2d9b..de778b2e8ec4 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -515,7 +515,8 @@ static int mnt_make_readonly(struct mount *mnt)
mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD;
unlock_mount_hash();
if (ret == 0)
- notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0x10000);
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY,
+ WATCH_INFO_FLAG_0);
return ret;
}
@@ -1478,6 +1479,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
list_del_init(&p->mnt_expire);
list_del_init(&p->mnt_list);
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ if (p->mnt_watchers)
+ remove_watch_list(p->mnt_watchers);
+#endif
ns = p->mnt_ns;
if (ns) {
ns->mounts--;
@@ -2115,7 +2120,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
notify_mount(dest_mnt, source_mnt, NOTIFY_MOUNT_NEW_MOUNT,
source_mnt->mnt.mnt_sb->s_flags & SB_SUBMOUNT ?
- 0x10000 : 0);
+ WATCH_INFO_FLAG_0 : 0);
commit_tree(source_mnt);
}
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 361305ddd75e..5db8e244d9a0 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -217,6 +217,7 @@ struct dentry_operations {
#define DCACHE_PAR_LOOKUP 0x10000000 /* being looked up (with parent locked shared) */
#define DCACHE_DENTRY_CURSOR 0x20000000
#define DCACHE_NORCU 0x40000000 /* No RCU delay for freeing */
+#define DCACHE_MOUNT_WATCH 0x80000000 /* There's a mount watch here */
extern seqlock_t rename_lock;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 217d25b62b4f..7c2b66175f3c 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1001,6 +1001,8 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
asmlinkage long sys_fsinfo(int dfd, const char __user *path,
struct fsinfo_params __user *params,
void __user *buffer, size_t buf_size);
+asmlinkage long sys_mount_notify(int dfd, const char __user *path,
+ unsigned int at_flags, int watch_fd, int watch_id);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 652fbe27a876..6cb923f3454e 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -104,4 +104,28 @@ struct key_notification {
__u32 aux; /* Per-type auxiliary data */
};
+/*
+ * Type of mount topology change notification.
+ */
+enum mount_notification_subtype {
+ NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */
+ NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */
+ NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */
+ NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */
+ NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */
+ NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */
+ NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */
+};
+
+/*
+ * Mount topology/configuration change notification record.
+ * - watch.type = WATCH_TYPE_MOUNT_NOTIFY
+ * - watch.subtype = enum mount_notification_subtype
+ */
+struct mount_notification {
+ struct watch_notification watch; /* WATCH_TYPE_MOUNT_NOTIFY */
+ __u32 triggered_on; /* The mount that the notify was on */
+ __u32 changed_mount; /* The mount that got changed */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index d1d9d76cae1e..97b025e7863c 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -88,6 +88,9 @@ COND_SYSCALL(ioprio_get);
/* fs/locks.c */
COND_SYSCALL(flock);
+/* fs/mount_notify.c */
+COND_SYSCALL(mount_notify);
+
/* fs/namei.c */
/* fs/namespace.c */
^ permalink raw reply related
* [PATCH 3/8] keys: Add a notification facility [ver #2]
From: David Howells @ 2019-06-04 16:35 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
Add a key/keyring change notification facility whereby notifications about
changes in key and keyring content and attributes can be received.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_KEY_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
After that, records will be placed into the queue when events occur in
which keys are changed in some way. Records are of the following format:
struct key_notification {
struct watch_notification watch;
__u32 key_id;
__u32 aux;
} *n;
Where:
n->watch.type will be WATCH_TYPE_KEY_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_KEY_REVOKED.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
keyctl_watch_key(), shifted.
n->key will be the ID of the affected key.
n->aux will hold subtype-dependent information, such as the key
being linked into the keyring specified by n->key in the case of
NOTIFY_KEY_LINKED.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 58 ++++++++++++++++++++++
include/linux/key.h | 4 ++
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 25 ++++++++++
security/keys/Kconfig | 10 ++++
security/keys/compat.c | 2 +
security/keys/gc.c | 5 ++
security/keys/internal.h | 30 +++++++++++
security/keys/key.c | 37 +++++++++-----
security/keys/keyctl.c | 89 +++++++++++++++++++++++++++++++++-
security/keys/keyring.c | 17 +++++-
security/keys/request_key.c | 4 +-
12 files changed, 258 insertions(+), 24 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 9521c4207f01..05ef58c753f3 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -808,6 +808,7 @@ The keyctl syscall functions are:
A process must have search permission on the key for this function to be
successful.
+
* Compute a Diffie-Hellman shared secret or public key::
long keyctl(KEYCTL_DH_COMPUTE, struct keyctl_dh_params *params,
@@ -1001,6 +1002,63 @@ The keyctl syscall functions are:
written into the output buffer. Verification returns 0 on success.
+ * Watch a key or keyring for changes::
+
+ long keyctl(KEYCTL_WATCH_KEY, key_serial_t key, int queue_fd,
+ const struct watch_notification_filter *filter);
+
+ This will set or remove a watch for changes on the specified key or
+ keyring.
+
+ "key" is the ID of the key to be watched.
+
+ "queue_fd" is a file descriptor referring to an open "/dev/watch_queue"
+ which manages the buffer into which notifications will be delivered.
+
+ "filter" is either NULL to remove a watch or a filter specification to
+ indicate what events are required from the key.
+
+ See Documentation/watch_queue.rst for more information.
+
+ Note that only one watch may be emplaced for any particular { key,
+ queue_fd } combination.
+
+ Notification records look like::
+
+ struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id;
+ __u32 aux;
+ };
+
+ In this, watch::type will be "WATCH_TYPE_KEY_NOTIFY" and subtype will be
+ one of::
+
+ NOTIFY_KEY_INSTANTIATED
+ NOTIFY_KEY_UPDATED
+ NOTIFY_KEY_LINKED
+ NOTIFY_KEY_UNLINKED
+ NOTIFY_KEY_CLEARED
+ NOTIFY_KEY_REVOKED
+ NOTIFY_KEY_INVALIDATED
+ NOTIFY_KEY_SETATTR
+
+ Where these indicate a key being instantiated/rejected, updated, a link
+ being made in a keyring, a link being removed from a keyring, a keyring
+ being cleared, a key being revoked, a key being invalidated or a key
+ having one of its attributes changed (user, group, perm, timeout,
+ restriction).
+
+ If a watched key is deleted, a basic watch_notification will be issued
+ with "type" set to WATCH_TYPE_META and "subtype" set to
+ watch_meta_removal_notification. The watchpoint ID will be set in the
+ "info" field.
+
+ This needs to be configured by enabling:
+
+ "Provide key/keyring change notifications" (KEY_NOTIFICATIONS)
+
+
Kernel Services
===============
diff --git a/include/linux/key.h b/include/linux/key.h
index 7099985e35a9..f1c43852c0c6 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -159,6 +159,9 @@ struct key {
struct list_head graveyard_link;
struct rb_node serial_node;
};
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct watch_list *watchers; /* Entities watching this key for changes */
+#endif
struct rw_semaphore sem; /* change vs change sem */
struct key_user *user; /* owner of this key */
void *security; /* security data for this key */
@@ -193,6 +196,7 @@ struct key {
#define KEY_FLAG_ROOT_CAN_INVAL 7 /* set if key can be invalidated by root without permission */
#define KEY_FLAG_KEEP 8 /* set if key should not be removed */
#define KEY_FLAG_UID_KEYRING 9 /* set if key is a user or user session keyring */
+#define KEY_FLAG_SET_WATCH_PROXY 10 /* Set if watch_proxy should be set on added keys */
/* the key type and key description string
* - the desc is used to match a key against search criteria
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index f45ee0f69c0c..e9e7da849619 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -67,6 +67,7 @@
#define KEYCTL_PKEY_SIGN 27 /* Create a public key signature */
#define KEYCTL_PKEY_VERIFY 28 /* Verify a public key signature */
#define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */
+#define KEYCTL_WATCH_KEY 30 /* Watch a key or ring of keys for changes */
/* keyctl structures */
struct keyctl_dh_params {
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 4a7e0f735f4f..652fbe27a876 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -79,4 +79,29 @@ struct watch_notification_filter {
struct watch_notification_type_filter filters[];
};
+/*
+ * Type of key/keyring change notification.
+ */
+enum key_notification_subtype {
+ NOTIFY_KEY_INSTANTIATED = 0, /* Key was instantiated (aux is error code) */
+ NOTIFY_KEY_UPDATED = 1, /* Key was updated */
+ NOTIFY_KEY_LINKED = 2, /* Key (aux) was added to watched keyring */
+ NOTIFY_KEY_UNLINKED = 3, /* Key (aux) was removed from watched keyring */
+ NOTIFY_KEY_CLEARED = 4, /* Keyring was cleared */
+ NOTIFY_KEY_REVOKED = 5, /* Key was revoked */
+ NOTIFY_KEY_INVALIDATED = 6, /* Key was invalidated */
+ NOTIFY_KEY_SETATTR = 7, /* Key's attributes got changed */
+};
+
+/*
+ * Key/keyring notification record.
+ * - watch.type = WATCH_TYPE_KEY_NOTIFY
+ * - watch.subtype = enum key_notification_type
+ */
+struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id; /* The key/keyring affected */
+ __u32 aux; /* Per-type auxiliary data */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 6462e6654ccf..fbe064fa0a17 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -101,3 +101,13 @@ config KEY_DH_OPERATIONS
in the kernel.
If you are unsure as to whether this is required, answer N.
+
+config KEY_NOTIFICATIONS
+ bool "Provide key/keyring change notifications"
+ depends on KEYS
+ select WATCH_QUEUE
+ help
+ This option provides support for getting change notifications on keys
+ and keyrings on which the caller has View permission. This makes use
+ of the /dev/watch_queue misc device to handle the notification
+ buffer and provides KEYCTL_WATCH_KEY to enable/disable watches.
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 9482df601dc3..021d8e1c9233 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -158,6 +158,8 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_PKEY_VERIFY:
return keyctl_pkey_verify(compat_ptr(arg2), compat_ptr(arg3),
compat_ptr(arg4), compat_ptr(arg5));
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key(arg2, arg3, arg4);
default:
return -EOPNOTSUPP;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 634e96b380e8..b685b9a85a9e 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -135,6 +135,11 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
kdebug("- %u", key->serial);
key_check(key);
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ remove_watch_list(key->watchers);
+ key->watchers = NULL;
+#endif
+
/* Throw away the key data if the key is instantiated */
if (state == KEY_IS_POSITIVE && key->type->destroy)
key->type->destroy(key);
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 8f533c81aa8d..a7ac0f823ade 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -19,6 +19,7 @@
#include <linux/task_work.h>
#include <linux/keyctl.h>
#include <linux/refcount.h>
+#include <linux/watch_queue.h>
#include <linux/compat.h>
struct iovec;
@@ -97,7 +98,8 @@ extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit **_edit);
extern int __key_link_check_live_key(struct key *keyring, struct key *key);
-extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
+extern void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit);
extern void __key_link_end(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit *edit);
@@ -178,6 +180,23 @@ extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
key_perm_t perm);
+static inline void notify_key(struct key *key,
+ enum key_notification_subtype subtype, u32 aux)
+{
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct key_notification n = {
+ .watch.type = WATCH_TYPE_KEY_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = sizeof(n),
+ .key_id = key_serial(key),
+ .aux = aux,
+ };
+
+ post_watch_notification(key->watchers, &n.watch, current_cred(),
+ n.key_id);
+#endif
+}
+
/*
* Check to see whether permission is granted to use a key in the desired way.
*/
@@ -324,6 +343,15 @@ static inline long keyctl_pkey_e_d_s(int op,
}
#endif
+#ifdef CONFIG_KEY_NOTIFICATIONS
+extern long keyctl_watch_key(key_serial_t, int, int);
+#else
+static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch_id)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
/*
* Debugging key validation
*/
diff --git a/security/keys/key.c b/security/keys/key.c
index 696f1c092c50..9d9f94992470 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -412,6 +412,7 @@ static void mark_key_instantiated(struct key *key, int reject_error)
*/
smp_store_release(&key->state,
(reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+ notify_key(key, NOTIFY_KEY_INSTANTIATED, reject_error);
}
/*
@@ -454,7 +455,7 @@ static int __key_instantiate_and_link(struct key *key,
if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
set_bit(KEY_FLAG_KEEP, &key->flags);
- __key_link(key, _edit);
+ __key_link(keyring, key, _edit);
}
/* disable the authorisation key */
@@ -603,7 +604,7 @@ int key_reject_and_link(struct key *key,
/* and link it into the destination keyring */
if (keyring && link_ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
/* disable the authorisation key */
if (authkey)
@@ -756,9 +757,11 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
down_write(&key->sem);
ret = key->type->update(key, prep);
- if (ret == 0)
+ if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
+ notify_key(key, NOTIFY_KEY_UPDATED, 0);
+ }
up_write(&key->sem);
@@ -999,9 +1002,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
down_write(&key->sem);
ret = key->type->update(key, &prep);
- if (ret == 0)
+ if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
+ notify_key(key, NOTIFY_KEY_UPDATED, 0);
+ }
up_write(&key->sem);
@@ -1033,15 +1038,17 @@ void key_revoke(struct key *key)
* instantiated
*/
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
- key->type->revoke)
- key->type->revoke(key);
-
- /* set the death time to no more than the expiry time */
- time = ktime_get_real_seconds();
- if (key->revoked_at == 0 || key->revoked_at > time) {
- key->revoked_at = time;
- key_schedule_gc(key->revoked_at + key_gc_delay);
+ if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_REVOKED, 0);
+ if (key->type->revoke)
+ key->type->revoke(key);
+
+ /* set the death time to no more than the expiry time */
+ time = ktime_get_real_seconds();
+ if (key->revoked_at == 0 || key->revoked_at > time) {
+ key->revoked_at = time;
+ key_schedule_gc(key->revoked_at + key_gc_delay);
+ }
}
up_write(&key->sem);
@@ -1063,8 +1070,10 @@ void key_invalidate(struct key *key)
if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
+ if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_INVALIDATED, 0);
key_schedule_gc_links();
+ }
up_write(&key->sem);
}
}
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 3e4053a217c3..c644bf23ed14 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -914,6 +914,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
if (group != (gid_t) -1)
key->gid = gid;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
error_put:
@@ -964,6 +965,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
/* if we're not the sysadmin, we can only change a key that we own */
if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
key->perm = perm;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
}
@@ -1355,10 +1357,12 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
okay:
key = key_ref_to_ptr(key_ref);
ret = 0;
- if (test_bit(KEY_FLAG_KEEP, &key->flags))
+ if (test_bit(KEY_FLAG_KEEP, &key->flags)) {
ret = -EPERM;
- else
+ } else {
key_set_timeout(key, timeout);
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
+ }
key_put(key);
error:
@@ -1631,6 +1635,84 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
return ret;
}
+#ifdef CONFIG_KEY_NOTIFICATIONS
+/*
+ * Watch for changes to a key.
+ *
+ * The caller must have View permission to watch a key or keyring.
+ */
+long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct key *key;
+ key_ref_t key_ref;
+ long ret = -ENOMEM;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_VIEW);
+ if (IS_ERR(key_ref))
+ return PTR_ERR(key_ref);
+ key = key_ref_to_ptr(key_ref);
+
+ wqueue = get_watch_queue(watch_queue_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err_key;
+ }
+
+ if (watch_id >= 0) {
+ if (!key->watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = key->serial;
+ watch->info_id = (u32)watch_id << 24;
+
+ down_write(&key->sem);
+ if (!key->watchers) {
+ key->watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, key->watchers);
+ up_write(&key->sem);
+
+ if (ret < 0)
+ kfree(watch);
+ } else {
+ ret = -EBADSLT;
+ if (key->watchers) {
+ down_write(&key->sem);
+ ret = remove_watch_from_object(key->watchers,
+ wqueue, key_serial(key),
+ false);
+ up_write(&key->sem);
+ }
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_key:
+ key_put(key);
+ return ret;
+}
+#endif /* CONFIG_KEY_NOTIFICATIONS */
+
/*
* The key control system call
*/
@@ -1771,6 +1853,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(const void __user *)arg4,
(const void __user *)arg5);
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index e14f09e3a4b0..f0f9ab3c5587 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1018,12 +1018,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type,
down_write(&keyring->sem);
down_write(&keyring_serialise_restrict_sem);
- if (keyring->restrict_link)
+ if (keyring->restrict_link) {
ret = -EEXIST;
- else if (keyring_detect_restriction_cycle(keyring, restrict_link))
+ } else if (keyring_detect_restriction_cycle(keyring, restrict_link)) {
ret = -EDEADLK;
- else
+ } else {
keyring->restrict_link = restrict_link;
+ notify_key(keyring, NOTIFY_KEY_SETATTR, 0);
+ }
up_write(&keyring_serialise_restrict_sem);
up_write(&keyring->sem);
@@ -1286,12 +1288,14 @@ int __key_link_check_live_key(struct key *keyring, struct key *key)
* holds at most one link to any given key of a particular type+description
* combination.
*/
-void __key_link(struct key *key, struct assoc_array_edit **_edit)
+void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit)
{
__key_get(key);
assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
assoc_array_apply_edit(*_edit);
*_edit = NULL;
+ notify_key(keyring, NOTIFY_KEY_LINKED, key_serial(key));
}
/*
@@ -1369,7 +1373,7 @@ int key_link(struct key *keyring, struct key *key)
if (ret == 0)
ret = __key_link_check_live_key(keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
__key_link_end(keyring, &key->index_key, edit);
}
@@ -1398,6 +1402,7 @@ EXPORT_SYMBOL(key_link);
int key_unlink(struct key *keyring, struct key *key)
{
struct assoc_array_edit *edit;
+ key_serial_t target = key_serial(key);
int ret;
key_check(keyring);
@@ -1419,6 +1424,7 @@ int key_unlink(struct key *keyring, struct key *key)
goto error;
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_UNLINKED, target);
key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
ret = 0;
@@ -1452,6 +1458,7 @@ int keyring_clear(struct key *keyring)
} else {
if (edit)
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_CLEARED, 0);
key_payload_reserve(keyring, 0);
ret = 0;
}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 75d87f9e0f49..5f474d0e8620 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -387,7 +387,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
goto key_already_present;
if (dest_keyring)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
mutex_unlock(&key_construction_mutex);
if (dest_keyring)
@@ -406,7 +406,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
if (dest_keyring) {
ret = __key_link_check_live_key(dest_keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
__key_link_end(dest_keyring, &ctx->index_key, edit);
if (ret < 0)
goto link_check_failed;
^ permalink raw reply related
* [PATCH 1/8] security: Override creds in __fput() with last fputter's creds [ver #2]
From: David Howells @ 2019-06-04 16:35 UTC (permalink / raw)
To: viro
Cc: Casey Schaufler, Casey Schaufler, dhowells, raven, linux-fsdevel,
linux-api, linux-block, keyrings, linux-security-module,
linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
So that the LSM can see the credentials of the last process to do an fput()
on a file object when the file object is being dismantled, do the following
steps:
(1) Cache the current credentials in file->f_fput_cred at the point the
file object's reference count reaches zero.
(2) In __fput(), use override_creds() to apply those credentials to the
dismantling process. This is necessary so that if we're dismantling a
unix socket that has semi-passed fds still in it, their fputs will
pick up the same credentials if they're reduced to zero at that point.
Note that it's probably not strictly necessary to take an extra ref on
the creds here (which override_creds() does).
(3) Destroy the fput creds in file_free_rcu().
This additionally makes the creds available to:
fsnotify
eventpoll
file locking
->fasync, ->release file ops
superblock destruction
mountpoint destruction
This allows various notifications about object cleanups/destructions to
carry appropriate credentials for the LSM to approve/disapprove them based
on the process that caused them, even if indirectly.
Note that this means that someone looking at /proc/<pid>/fd/<n> may end up
being inadvertently noted as the subject of a cleanup message if the
process they're looking at croaks whilst they're looking at it.
Further, kernel services like nfsd and cachefiles may be seen as the
fputter and may not have a system credential. In cachefiles's case, it may
appear that cachefilesd caused the notification.
Suggested-by: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
---
fs/file_table.c | 12 ++++++++++++
include/linux/fs.h | 1 +
2 files changed, 13 insertions(+)
diff --git a/fs/file_table.c b/fs/file_table.c
index 3f9c1b452c1d..9bf2be45b7f9 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -46,6 +46,7 @@ static void file_free_rcu(struct rcu_head *head)
struct file *f = container_of(head, struct file, f_u.fu_rcuhead);
put_cred(f->f_cred);
+ put_cred(f->f_fput_cred);
kmem_cache_free(filp_cachep, f);
}
@@ -252,6 +253,7 @@ struct file *alloc_file_clone(struct file *base, int flags,
*/
static void __fput(struct file *file)
{
+ const struct cred *saved_cred;
struct dentry *dentry = file->f_path.dentry;
struct vfsmount *mnt = file->f_path.mnt;
struct inode *inode = file->f_inode;
@@ -262,6 +264,12 @@ static void __fput(struct file *file)
might_sleep();
+ /* Set the creds of whoever triggered the last fput for the LSM. Note
+ * that this has to be made available to further fputs, say on fds
+ * trapped in a unix socket.
+ */
+ saved_cred = override_creds(file->f_fput_cred);
+
fsnotify_close(file);
/*
* The function eventpoll_release() should be the first called
@@ -293,6 +301,8 @@ static void __fput(struct file *file)
if (unlikely(mode & FMODE_NEED_UNMOUNT))
dissolve_on_fput(mnt);
mntput(mnt);
+
+ revert_creds(saved_cred);
out:
file_free(file);
}
@@ -334,6 +344,7 @@ void fput_many(struct file *file, unsigned int refs)
if (atomic_long_sub_and_test(refs, &file->f_count)) {
struct task_struct *task = current;
+ file->f_fput_cred = get_current_cred();
if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
init_task_work(&file->f_u.fu_rcuhead, ____fput);
if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
@@ -368,6 +379,7 @@ void __fput_sync(struct file *file)
if (atomic_long_dec_and_test(&file->f_count)) {
struct task_struct *task = current;
BUG_ON(!(task->flags & PF_KTHREAD));
+ file->f_fput_cred = get_current_cred();
__fput(file);
}
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f1c74596cd77..db05738b1951 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -943,6 +943,7 @@ struct file {
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
+ const struct cred *f_fput_cred; /* Who did the last fput() (for LSM) */
struct file_ra_state f_ra;
u64 f_version;
^ permalink raw reply related
* [PATCH 2/8] General notification queue with user mmap()'able ring buffer [ver #2]
From: David Howells @ 2019-06-04 16:35 UTC (permalink / raw)
To: viro
Cc: dhowells, raven, linux-fsdevel, linux-api, linux-block, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <155966609977.17449.5624614375035334363.stgit@warthog.procyon.org.uk>
Implement a misc device that implements a general notification queue as a
ring buffer that can be mmap()'d from userspace.
The way this is done is:
(1) An application opens the device and indicates the size of the ring
buffer that it wants to reserve in pages (this can only be set once):
fd = open("/dev/watch_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_NR_PAGES, nr_of_pages);
(2) The application should then map the pages that the device has
reserved. Each instance of the device created by open() allocates
separate pages so that maps of different fds don't interfere with one
another. Multiple mmap() calls on the same fd, however, will all work
together.
page_size = sysconf(_SC_PAGESIZE);
mapping_size = nr_of_pages * page_size;
char *buf = mmap(NULL, mapping_size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
The ring is divided into 8-byte slots. Entries written into the ring are
variable size and can use between 1 and 63 slots. A special entry is
maintained in the first two slots of the ring that contains the head and
tail pointers. This is skipped when the ring wraps round. Note that
multislot entries, therefore, aren't allowed to be broken over the end of
the ring, but instead "skip" entries are inserted to pad out the buffer.
Each entry has a 1-slot header that describes it:
struct watch_notification {
__u32 type:24;
__u32 subtype:8;
__u32 info;
};
The type indicates the source (eg. mount tree changes, superblock events,
keyring changes, block layer events) and the subtype indicates the event
type (eg. mount, unmount; EIO, EDQUOT; link, unlink). The info field
indicates a number of things, including the entry length, an ID assigned to
a watchpoint contributing to this buffer, type-specific flags and meta
flags, such as an overrun indicator.
Supplementary data, such as the key ID that generated an event, are
attached in additional slots.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 328 ++++++++++++++
drivers/misc/Kconfig | 13 +
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 895 ++++++++++++++++++++++++++++++++++++++
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/watch_queue.h | 87 ++++
include/uapi/linux/watch_queue.h | 82 +++
mm/interval_tree.c | 2
mm/memory.c | 1
security/security.c | 9
11 files changed, 1447 insertions(+)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
new file mode 100644
index 000000000000..7c7d299d1dee
--- /dev/null
+++ b/Documentation/watch_queue.rst
@@ -0,0 +1,328 @@
+============================
+Mappable notifications queue
+============================
+
+This is a misc device that acts as a mapped ring buffer by which userspace can
+receive notifications from the kernel. This can be used in conjunction with::
+
+ * Key/keyring notifications
+
+ * Mount topology change notifications
+
+ * Superblock event notifications
+
+ * Block layer event notifications
+
+
+The notifications buffers can be enabled by:
+
+ "Device Drivers"/"Misc devices"/"Mappable notification queue"
+ (CONFIG_WATCH_QUEUE)
+
+This document has the following sections:
+
+.. contents:: :local:
+
+
+Overview
+========
+
+This facility appears as a misc device file that is opened and then mapped and
+polled. Each time it is opened, it creates a new buffer specific to the
+returned file descriptor. Then, when the opening process sets watches, it
+indicates the particular buffer it wants notifications from that watch to be
+written into. Note that there are no read() and write() methods (except for
+debugging). The user is expected to access the ring directly and to use poll
+to wait for new data.
+
+If a watch is in place, notifications are only written into the buffer if the
+filter criteria are passed and if there's sufficient space available in the
+ring. If neither of those is so, a notification will be discarded. In the
+latter case, an overrun indicator will also be set.
+
+Note that when producing a notification, the kernel does not wait for the
+consumers to collect it, but rather just continues on. This means that
+notifications can be generated whilst spinlocks are held and also protects the
+kernel from being held up indefinitely by a userspace malfunction.
+
+As far as the ring goes, the head index belongs to the kernel and the tail
+index belongs to userspace. The kernel will refuse to write anything if the
+tail index becomes invalid. Userspace *must* use appropriate memory barriers
+between reading or updating the tail index and reading the ring.
+
+
+Record Structure
+================
+
+Notification records in the ring may occupy a variable number of slots within
+the buffer, beginning with a 1-slot header::
+
+ struct watch_notification {
+ __u32 type:24;
+ __u32 subtype:8;
+ __u32 info;
+ };
+
+"type" indicates the source of the notification record and "subtype" indicates
+the type of record from that source (see the Watch Sources section below). The
+type may also be "WATCH_TYPE_META". This is a special record type generated
+internally by the watch queue driver itself. There are two subtypes, one of
+which indicates records that should be just skipped (padding or metadata):
+
+ * WATCH_META_SKIP_NOTIFICATION
+ * WATCH_META_REMOVAL_NOTIFICATION
+
+The former indicates a record that should just be skipped and the latter
+indicates that an object on which a watchpoint was installed was removed or
+destroyed.
+
+"info" indicates a bunch of things, including:
+
+ * The length of the record (mask with WATCH_INFO_LENGTH). This indicates the
+ size of the record, which may be between 1 and 63 slots. Note that this is
+ placed appropriately within the info value so that no shifting is required
+ to convert number of occupied slots to byte length.
+
+ * The watchpoint ID (mask with WATCH_INFO_ID). This indicates that caller's
+ ID of the watchpoint, which may be between 0 and 255. Multiple watchpoints
+ may share a queue, and this provides a means to distinguish them.
+
+ * A buffer overrun flag (WATCH_INFO_OVERRUN flag). If this is set in a
+ notification record, some of the preceding records were discarded.
+
+ * An ENOMEM-loss flag (WATCH_INFO_ENOMEM flag). This is set to indicate that
+ an event was lost to ENOMEM.
+
+ * A recursive-change flag (WATCH_INFO_RECURSIVE flag). This is set to
+ indicate that the change that happened was recursive - for instance
+ changing the attributes on an entire mount subtree.
+
+ * An exact-match flag (WATCH_INFO_IN_SUBTREE flag). This is set if the event
+ didn't happen exactly at the watchpoint, but rather somewhere in the
+ subtree thereunder.
+
+ * Some type-specific flags (WATCH_INFO_TYPE_FLAGS). These are set by the
+ notification producer to indicate some meaning specific to the type and
+ subtype.
+
+Everything in info apart from the length can be used for filtering.
+
+
+Ring Structure
+==============
+
+The ring is divided into 8-byte slots. The caller uses an ioctl() to set the
+size of the ring after opening and this must be a power-of-2 multiple of the
+system page size (so that the mask can be used with AND).
+
+The head and tail indices are stored in the first two slots in the ring, which
+are marked out as a skippable entry::
+
+ struct watch_queue_buffer {
+ union {
+ struct {
+ struct watch_notification watch;
+ volatile __u32 head;
+ volatile __u32 tail;
+ __u32 mask;
+ } meta;
+ struct watch_notification slots[0];
+ };
+ };
+
+In "meta.watch", type will be set to WATCH_TYPE_META and subtype to
+WATCH_META_SKIP_NOTIFICATION so that anyone processing the buffer will just
+skip this record. Also, because this record is here, records cannot wrap round
+the end of the buffer, so a skippable padding element will be inserted at the
+end of the buffer if needed. Thus the contents of a notification record in the
+buffer are always contiguous.
+
+"meta.mask" is an AND'able mask to turn the index counters into slots array
+indices.
+
+The buffer is empty if "meta.head" == "meta.tail".
+
+[!] NOTE that the ring indices "meta.head" and "meta.tail" are indices into
+"slots[]" not byte offsets into the buffer.
+
+[!] NOTE that userspace must never change the head pointer. This belongs to
+the kernel and will be updated by that. The kernel will never change the tail
+pointer.
+
+[!] NOTE that userspace must never AND-off the tail pointer before updating it,
+but should just keep adding to it and letting it wrap naturally. The value
+*should* be masked off when used as an index into slots[].
+
+[!] NOTE that if the distance between head and tail becomes too great, the
+kernel will assume the buffer is full and write no more until the issue is
+resolved.
+
+
+Watch Sources
+=============
+
+Any particular buffer can be fed from multiple sources. Sources include:
+
+ * WATCH_TYPE_MOUNT_NOTIFY
+
+ Notifications of this type indicate mount tree topology changes and mount
+ attribute changes. A watchpoint can be set on a particular file or
+ directory and notifications from the path subtree rooted at that point will
+ be intercepted.
+
+ * WATCH_TYPE_SB_NOTIFY
+
+ Notifications of this type indicate superblock events, such as quota limits
+ being hit, I/O errors being produced or network server loss/reconnection.
+ Watchpoints of this type are set directly on superblocks.
+
+ * WATCH_TYPE_KEY_NOTIFY
+
+ Notifications of this type indicate changes to keys and keyrings, including
+ the changes of keyring contents or the attributes of keys.
+
+ See Documentation/security/keys/core.rst for more information.
+
+ * WATCH_TYPE_BLOCK_NOTIFY
+
+ Notifications of this type indicate block layer events, such as I/O errors
+ or temporary link loss. Watchpoints of this type are set on a global
+ queue.
+
+
+Event Filtering
+===============
+
+Once a watch queue has been created, a set of filters can be applied to limit
+the events that are received using::
+
+ struct watch_notification_filter filter = {
+ ...
+ };
+ ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
+
+The filter description is a variable of type::
+
+ struct watch_notification_filter {
+ __u32 nr_filters;
+ __u32 __reserved;
+ struct watch_notification_type_filter filters[];
+ };
+
+Where "nr_filters" is the number of filters in filters[] and "__reserved"
+should be 0. The "filters" array has elements of the following type::
+
+ struct watch_notification_type_filter {
+ __u32 type;
+ __u32 info_filter;
+ __u32 info_mask;
+ __u32 subtype_filter[8];
+ };
+
+Where:
+
+ * ``type`` is the event type to filter for and should be something like
+ "WATCH_TYPE_MOUNT_NOTIFY"
+
+ * ``info_filter`` and ``info_mask`` act as a filter on the info field of the
+ notification record. The notification is only written into the buffer if::
+
+ (watch.info & info_mask) == info_filter
+
+ This can be used, for example, to ignore events that are not exactly on the
+ watched point in a mount tree by specifying WATCH_INFO_IN_SUBTREE must
+ be 0.
+
+ * ``subtype_filter`` is a bitmask indicating the subtypes that are of
+ interest. Bit 0 of subtype_filter[0] corresponds to subtype 0, bit 1 to
+ subtype 1, and so on.
+
+If the argument to the ioctl() is NULL, then the filters will be removed and
+all events from the watched sources will come through.
+
+
+Polling
+=======
+
+The file descriptor that holds the buffer may be used with poll() and similar.
+POLLIN and POLLRDNORM are set if the buffer indices differ. POLLERR is set if
+the buffer indices are further apart than the size of the buffer. Wake-up
+events are only generated if the buffer is transitioned from an empty state.
+
+
+Example
+=======
+
+A buffer is created with something like the following::
+
+ fd = open("/dev/watch_queue", O_RDWR);
+
+ #define BUF_SIZE 4
+ ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
+
+ page_size = sysconf(_SC_PAGESIZE);
+ buf = mmap(NULL, BUF_SIZE * page_size,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+It can then be set to receive mount topology change notifications, keyring
+change notifications and superblock notifications::
+
+ memset(&filter, 0, sizeof(filter));
+ filter.subtype_filter[0] = ~0ULL;
+ filter.info_mask = WATCH_INFO_IN_SUBTREE;
+ filter.info_filter = 0;
+ filter.info_id = 0x01000000;
+
+ keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fd, &filter);
+
+ mount_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+ sb_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+The notifications can then be consumed by something like the following::
+
+ extern void saw_mount_change(struct watch_notification *n);
+ extern void saw_key_change(struct watch_notification *n);
+
+ static int consumer(int fd, struct watch_queue_buffer *buf)
+ {
+ struct watch_notification *n;
+ struct pollfd p[1];
+ unsigned int head, tail, mask = buf->meta.mask;
+
+ for (;;) {
+ p[0].fd = fd;
+ p[0].events = POLLIN | POLLERR;
+ p[0].revents = 0;
+
+ if (poll(p, 1, -1) == -1 || p[0].revents & POLLERR)
+ goto went_wrong;
+
+ while (head = _atomic_load_acquire(buf->meta.head),
+ tail = buf->meta.tail,
+ tail != head
+ ) {
+ n = &buf->slots[tail & mask];
+ if ((n->info & WATCH_INFO_LENGTH) == 0)
+ goto went_wrong;
+
+ switch (n->type) {
+ case WATCH_TYPE_MOUNT_NOTIFY:
+ saw_mount_change(n);
+ break;
+ case WATCH_TYPE_KEY_NOTIFY:
+ saw_key_change(n);
+ break;
+ }
+
+ tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ _atomic_store_release(buf->meta.tail, tail);
+ }
+ }
+
+ went_wrong:
+ return 0;
+ }
+
+Note the memory barriers when loading the head pointer and storing the tail
+pointer!
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6a0365b2332c..19668c0ebe03 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -4,6 +4,19 @@
menu "Misc devices"
+config WATCH_QUEUE
+ bool "Mappable notification queue"
+ default n
+ depends on MMU
+ help
+ This is a general notification queue for the kernel to pass events to
+ userspace through a mmap()'able ring buffer. It can be used in
+ conjunction with watches for mount topology change notifications,
+ superblock change notifications and key/keyring change notifications.
+
+ Note that in theory this should work fine with NOMMU, but I'm not
+ sure how to make that work.
+
config SENSORS_LIS3LV02D
tristate
depends on INPUT
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b9affcdaa3d6..bf16acd9f8cc 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -3,6 +3,7 @@
# Makefile for misc devices that really don't fit anywhere else.
#
+obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_IBMVMC) += ibmvmc.o
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
diff --git a/drivers/misc/watch_queue.c b/drivers/misc/watch_queue.c
new file mode 100644
index 000000000000..c8242eab4c71
--- /dev/null
+++ b/drivers/misc/watch_queue.c
@@ -0,0 +1,895 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#define pr_fmt(fmt) "watchq: " fmt
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/file.h>
+#include <linux/security.h>
+#include <linux/cred.h>
+#include <linux/watch_queue.h>
+
+#undef DEBUG_WITH_WRITE /* Allow use of write() to record notifications */
+
+MODULE_DESCRIPTION("Watch queue");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+struct watch_type_filter {
+ enum watch_notification_type type;
+ __u32 subtype_filter[1]; /* Bitmask of subtypes to filter on */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+};
+
+struct watch_filter {
+ union {
+ struct rcu_head rcu;
+ unsigned long type_filter[2]; /* Bitmask of accepted types */
+ };
+ u32 nr_filters; /* Number of filters */
+ struct watch_type_filter filters[];
+};
+
+struct watch_queue {
+ struct rcu_head rcu;
+ struct address_space mapping;
+ const struct cred *cred; /* Creds of the owner of the queue */
+ struct watch_filter __rcu *filter;
+ wait_queue_head_t waiters;
+ struct hlist_head watches; /* Contributory watches */
+ struct kref usage; /* Object usage count */
+ spinlock_t lock;
+ bool defunct; /* T when queues closed */
+ u8 nr_pages; /* Size of pages[] */
+ u8 flag_next; /* Flag to apply to next item */
+#ifdef DEBUG_WITH_WRITE
+ u8 debug;
+#endif
+ u32 size;
+ struct watch_queue_buffer *buffer; /* Pointer to first record */
+
+ /* The mappable pages. The zeroth page holds the ring pointers. */
+ struct page **pages;
+};
+
+/**
+ * post_one_notification - Post an event notification to one queue
+ * @wqueue: The watch queue to add the event to.
+ * @n: The notification record to post.
+ * @cred: The credentials to use in security checks.
+ *
+ * Post a notification of an event into an mmap'd queue and let the user know.
+ * Returns true if successful and false on failure (eg. buffer overrun or
+ * userspace mucked up the ring indices).
+ *
+ *
+ * The size of the notification should be set in n->flags & WATCH_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+static bool post_one_notification(struct watch_queue *wqueue,
+ struct watch_notification *n,
+ const struct cred *cred)
+{
+ struct watch_queue_buffer *buf = wqueue->buffer;
+ unsigned int metalen = sizeof(buf->meta) / sizeof(buf->slots[0]);
+ unsigned int size = wqueue->size, mask = size - 1;
+ unsigned int len;
+ unsigned int ring_tail, tail, head, used, segment, h;
+
+ if (!buf)
+ return false;
+
+ len = (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ if (len == 0)
+ return false;
+
+ spin_lock_bh(&wqueue->lock); /* Protect head pointer */
+
+ if (wqueue->defunct ||
+ security_post_notification(wqueue->cred, cred, n) < 0)
+ goto out;
+
+ ring_tail = READ_ONCE(buf->meta.tail);
+ head = READ_ONCE(buf->meta.head);
+ used = head - ring_tail;
+
+ /* Check to see if userspace mucked up the pointers */
+ if (used >= size)
+ goto overrun;
+ tail = ring_tail & mask;
+ if (tail > 0 && tail < metalen)
+ goto overrun;
+
+ h = head & mask;
+ if (h >= tail) {
+ /* Head is at or after tail in the buffer. There may then be
+ * two segments: one to the end of buffer and one at the
+ * beginning of the buffer between the metadata block and the
+ * tail pointer.
+ */
+ segment = size - h;
+ if (len > segment) {
+ /* Not enough space in the post-head segment; we need
+ * to wrap. When wrapping, we will have to skip the
+ * metadata at the beginning of the buffer.
+ */
+ if (len > tail - metalen)
+ goto overrun;
+
+ /* Fill the space at the end of the page */
+ buf->slots[h].type = WATCH_TYPE_META;
+ buf->slots[h].subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->slots[h].info = segment << WATCH_LENGTH_SHIFT;
+ head += segment;
+ h = 0;
+ if (h >= tail)
+ goto overrun;
+ }
+ }
+
+ if (h == 0) {
+ /* Reset and skip the header metadata */
+ buf->meta.watch.type = WATCH_TYPE_META;
+ buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->meta.watch.info = metalen << WATCH_LENGTH_SHIFT;
+ head += metalen;
+ h = metalen;
+ if (h >= tail)
+ goto overrun;
+ }
+
+ if (h < tail) {
+ /* Head is before tail in the buffer. There may be one segment
+ * between the two, but we may need to skip the metadata block.
+ */
+ segment = tail - h;
+ if (len > segment)
+ goto overrun;
+ }
+
+ n->info |= wqueue->flag_next;
+ wqueue->flag_next = 0;
+ memcpy(buf->slots + h, n, len * sizeof(buf->slots[0]));
+ head += len;
+
+ smp_store_release(&buf->meta.head, head);
+ spin_unlock_bh(&wqueue->lock);
+ if (used == 0)
+ wake_up(&wqueue->waiters);
+ return true;
+
+overrun:
+ wqueue->flag_next = WATCH_INFO_OVERRUN;
+out:
+ spin_unlock_bh(&wqueue->lock);
+ return false;
+}
+
+/*
+ * Apply filter rules to a notification.
+ */
+static bool filter_watch_notification(const struct watch_filter *wf,
+ const struct watch_notification *n)
+{
+ const struct watch_type_filter *wt;
+ int i;
+
+ if (!test_bit(n->type, wf->type_filter))
+ return false;
+
+ for (i = 0; i < wf->nr_filters; i++) {
+ wt = &wf->filters[i];
+ if (n->type == wt->type &&
+ ((1U << n->subtype) & wt->subtype_filter[0]) &&
+ (n->info & wt->info_mask) == wt->info_filter)
+ return true;
+ }
+
+ return false; /* If there is a filter, the default is to reject. */
+}
+
+/**
+ * __post_watch_notification - Post an event notification
+ * @wlist: The watch list to post the event to.
+ * @n: The notification record to post.
+ * @cred: The creds of the process that triggered the notification.
+ * @id: The ID to match on the watch.
+ *
+ * Post a notification of an event into a set of watch queues and let the users
+ * know.
+ *
+ * If @n is NULL then WATCH_INFO_LENGTH will be set on the next event posted.
+ *
+ * The size of the notification should be set in n->info & WATCH_INFO_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+void __post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ const struct watch_filter *wf;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+
+ rcu_read_lock();
+
+ hlist_for_each_entry_rcu(watch, &wlist->watchers, list_node) {
+ if (watch->id != id)
+ continue;
+ n->info &= ~(WATCH_INFO_ID | WATCH_INFO_OVERRUN);
+ n->info |= watch->info_id;
+
+ wqueue = rcu_dereference(watch->queue);
+ wf = rcu_dereference(wqueue->filter);
+ if (wf && !filter_watch_notification(wf, n))
+ continue;
+
+ post_one_notification(wqueue, n, cred);
+ }
+
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL(__post_watch_notification);
+
+/*
+ * Allow the queue to be polled.
+ */
+static __poll_t watch_queue_poll(struct file *file, poll_table *wait)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct watch_queue_buffer *buf = wqueue->buffer;
+ unsigned int head, tail;
+ __poll_t mask = 0;
+
+ poll_wait(file, &wqueue->waiters, wait);
+
+ head = READ_ONCE(buf->meta.head);
+ tail = READ_ONCE(buf->meta.tail);
+ if (head != tail)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (head - tail > wqueue->size)
+ mask |= EPOLLERR;
+ return mask;
+}
+
+static int watch_queue_set_page_dirty(struct page *page)
+{
+ SetPageDirty(page);
+ return 0;
+}
+
+static const struct address_space_operations watch_queue_aops = {
+ .set_page_dirty = watch_queue_set_page_dirty,
+};
+
+static vm_fault_t watch_queue_fault(struct vm_fault *vmf)
+{
+ struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+ struct page *page;
+
+ page = wqueue->pages[vmf->pgoff];
+ get_page(page);
+ if (!lock_page_or_retry(page, vmf->vma->vm_mm, vmf->flags)) {
+ put_page(page);
+ return VM_FAULT_RETRY;
+ }
+ vmf->page = page;
+ return VM_FAULT_LOCKED;
+}
+
+static void watch_queue_map_pages(struct vm_fault *vmf,
+ pgoff_t start_pgoff, pgoff_t end_pgoff)
+{
+ struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+ struct page *page;
+
+ rcu_read_lock();
+
+ do {
+ page = wqueue->pages[start_pgoff];
+ if (trylock_page(page)) {
+ vm_fault_t ret;
+ get_page(page);
+ ret = alloc_set_pte(vmf, NULL, page);
+ if (ret != 0)
+ put_page(page);
+
+ unlock_page(page);
+ }
+ } while (++start_pgoff < end_pgoff);
+
+ rcu_read_unlock();
+}
+
+static const struct vm_operations_struct watch_queue_vm_ops = {
+ .fault = watch_queue_fault,
+ .map_pages = watch_queue_map_pages,
+};
+
+/*
+ * Map the buffer.
+ */
+static int watch_queue_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct inode *inode = file_inode(file);
+ u8 nr_pages;
+
+ inode_lock(inode);
+ nr_pages = wqueue->nr_pages;
+ inode_unlock(inode);
+
+ if (nr_pages == 0 ||
+ vma->vm_pgoff != 0 ||
+ vma->vm_end - vma->vm_start > nr_pages * PAGE_SIZE ||
+ !(pgprot_val(vma->vm_page_prot) & pgprot_val(PAGE_SHARED)))
+ return -EINVAL;
+
+ vma->vm_flags |= VM_DONTEXPAND;
+ vma->vm_ops = &watch_queue_vm_ops;
+
+ vma_interval_tree_insert(vma, &wqueue->mapping.i_mmap);
+ return 0;
+}
+
+/*
+ * Allocate the required number of pages.
+ */
+static long watch_queue_set_size(struct watch_queue *wqueue, unsigned long nr_pages)
+{
+ struct watch_queue_buffer *buf;
+ u32 len;
+ int i;
+
+ if (wqueue->buffer)
+ return -EBUSY;
+
+ if (nr_pages == 0 ||
+ nr_pages > 16 || /* TODO: choose a better hard limit */
+ !is_power_of_2(nr_pages))
+ return -EINVAL;
+
+ wqueue->pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!wqueue->pages)
+ goto err;
+
+ for (i = 0; i < nr_pages; i++) {
+ wqueue->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!wqueue->pages[i])
+ goto err_some_pages;
+ wqueue->pages[i]->mapping = &wqueue->mapping;
+ SetPageUptodate(wqueue->pages[i]);
+ }
+
+ buf = vmap(wqueue->pages, nr_pages, VM_MAP, PAGE_SHARED);
+ if (!buf)
+ goto err_some_pages;
+
+ wqueue->buffer = buf;
+ wqueue->nr_pages = nr_pages;
+ wqueue->size = ((nr_pages * PAGE_SIZE) / sizeof(struct watch_notification));
+
+ /* The first four slots in the buffer contain metadata about the ring,
+ * including the head and tail indices and mask.
+ */
+ len = sizeof(buf->meta) / sizeof(buf->slots[0]);
+ buf->meta.watch.info = len << WATCH_LENGTH_SHIFT;
+ buf->meta.watch.type = WATCH_TYPE_META;
+ buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->meta.mask = wqueue->size - 1;
+ buf->meta.head = len;
+ buf->meta.tail = len;
+ return 0;
+
+err_some_pages:
+ for (i--; i >= 0; i--) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ put_page(wqueue->pages[i]);
+ }
+
+ kfree(wqueue->pages);
+ wqueue->pages = NULL;
+err:
+ return -ENOMEM;
+}
+
+/*
+ * Set the filter on a watch queue.
+ */
+static long watch_queue_set_filter(struct inode *inode,
+ struct watch_queue *wqueue,
+ struct watch_notification_filter __user *_filter)
+{
+ struct watch_notification_type_filter *tf;
+ struct watch_notification_filter filter;
+ struct watch_type_filter *q;
+ struct watch_filter *wfilter;
+ int ret, nr_filter = 0, i;
+
+ if (!_filter) {
+ /* Remove the old filter */
+ wfilter = NULL;
+ goto set;
+ }
+
+ /* Grab the user's filter specification */
+ if (copy_from_user(&filter, _filter, sizeof(filter)) != 0)
+ return -EFAULT;
+ if (filter.nr_filters == 0 ||
+ filter.nr_filters > 16 ||
+ filter.__reserved != 0)
+ return -EINVAL;
+
+ tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
+ if (IS_ERR(tf))
+ return PTR_ERR(tf);
+
+ ret = -EINVAL;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if ((tf[i].info_filter & ~tf[i].info_mask) ||
+ tf[i].info_mask & WATCH_INFO_LENGTH)
+ goto err_filter;
+ /* Ignore any unknown types */
+ if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+ continue;
+ nr_filter++;
+ }
+
+ /* Now we need to build the internal filter from only the relevant
+ * user-specified filters.
+ */
+ ret = -ENOMEM;
+ wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
+ if (!wfilter)
+ goto err_filter;
+ wfilter->nr_filters = nr_filter;
+
+ q = wfilter->filters;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+ continue;
+
+ q->type = tf[i].type;
+ q->info_filter = tf[i].info_filter;
+ q->info_mask = tf[i].info_mask;
+ q->subtype_filter[0] = tf[i].subtype_filter[0];
+ __set_bit(q->type, wfilter->type_filter);
+ q++;
+ }
+
+ kfree(tf);
+set:
+ inode_lock(inode);
+ rcu_swap_protected(wqueue->filter, wfilter,
+ lockdep_is_held(&inode->i_rwsem));
+ inode_unlock(inode);
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ return 0;
+
+err_filter:
+ kfree(tf);
+ return ret;
+}
+
+/*
+ * Set parameters.
+ */
+static long watch_queue_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct inode *inode = file_inode(file);
+ long ret;
+
+ switch (cmd) {
+ case IOC_WATCH_QUEUE_SET_SIZE:
+ inode_lock(inode);
+ ret = watch_queue_set_size(wqueue, arg);
+ inode_unlock(inode);
+ return ret;
+
+ case IOC_WATCH_QUEUE_SET_FILTER:
+ ret = watch_queue_set_filter(
+ inode, wqueue,
+ (struct watch_notification_filter __user *)arg);
+ return ret;
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * Open the file.
+ */
+static int watch_queue_open(struct inode *inode, struct file *file)
+{
+ struct watch_queue *wqueue;
+
+ wqueue = kzalloc(sizeof(*wqueue), GFP_KERNEL);
+ if (!wqueue)
+ return -ENOMEM;
+
+ wqueue->mapping.a_ops = &watch_queue_aops;
+ wqueue->mapping.i_mmap = RB_ROOT_CACHED;
+ init_rwsem(&wqueue->mapping.i_mmap_rwsem);
+ spin_lock_init(&wqueue->mapping.private_lock);
+
+ kref_init(&wqueue->usage);
+ spin_lock_init(&wqueue->lock);
+ init_waitqueue_head(&wqueue->waiters);
+ wqueue->cred = get_cred(file->f_cred);
+
+ file->private_data = wqueue;
+ return 0;
+}
+
+static void __put_watch_queue(struct kref *kref)
+{
+ struct watch_queue *wqueue =
+ container_of(kref, struct watch_queue, usage);
+
+ kfree_rcu(wqueue, rcu);
+}
+
+/**
+ * put_watch_queue - Dispose of a ref on a watchqueue.
+ * @wqueue: The watch queue to unref.
+ */
+void put_watch_queue(struct watch_queue *wqueue)
+{
+ kref_put(&wqueue->usage, __put_watch_queue);
+}
+EXPORT_SYMBOL(put_watch_queue);
+
+static void free_watch(struct rcu_head *rcu)
+{
+ struct watch *watch = container_of(rcu, struct watch, rcu);
+
+ put_watch_queue(rcu_access_pointer(watch->queue));
+}
+
+static void __put_watch(struct kref *kref)
+{
+ struct watch *watch = container_of(kref, struct watch, usage);
+
+ call_rcu(&watch->rcu, free_watch);
+}
+
+/*
+ * Discard a watch.
+ */
+static void put_watch(struct watch *watch)
+{
+ kref_put(&watch->usage, __put_watch);
+}
+
+/**
+ * init_watch_queue - Initialise a watch
+ * @watch: The watch to initialise.
+ * @wqueue: The queue to assign.
+ *
+ * Initialise a watch and set the watch queue.
+ */
+void init_watch(struct watch *watch, struct watch_queue *wqueue)
+{
+ kref_init(&watch->usage);
+ INIT_HLIST_NODE(&watch->list_node);
+ INIT_HLIST_NODE(&watch->queue_node);
+ rcu_assign_pointer(watch->queue, wqueue);
+}
+
+/**
+ * add_watch_to_object - Add a watch on an object to a watch list
+ * @watch: The watch to add
+ * @wlist: The watch list to add to
+ *
+ * @watch->queue must have been set to point to the queue to post notifications
+ * to and the watch list of the object to be watched.
+ *
+ * The caller must pin the queue and the list both and must hold the list
+ * locked against racing watch additions/removals.
+ */
+int add_watch_to_object(struct watch *watch, struct watch_list *wlist)
+{
+ struct watch_queue *wqueue = rcu_access_pointer(watch->queue);
+ struct watch *w;
+
+ hlist_for_each_entry(w, &wlist->watchers, list_node) {
+ if (watch->id == w->id)
+ return -EBUSY;
+ }
+
+ rcu_assign_pointer(watch->watch_list, wlist);
+
+ spin_lock_bh(&wqueue->lock);
+ kref_get(&wqueue->usage);
+ hlist_add_head(&watch->queue_node, &wqueue->watches);
+ spin_unlock_bh(&wqueue->lock);
+
+ hlist_add_head(&watch->list_node, &wlist->watchers);
+ return 0;
+}
+EXPORT_SYMBOL(add_watch_to_object);
+
+/**
+ * remove_watch_from_object - Remove a watch or all watches from an object.
+ * @wlist: The watch list to remove from
+ * @wq: The watch queue of interest (ignored if @all is true)
+ * @id: The ID of the watch to remove (ignored if @all is true)
+ * @all: True to remove all objects
+ *
+ * Remove a specific watch or all watches from an object. A notification is
+ * sent to the watcher to tell them that this happened.
+ */
+int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq,
+ u64 id, bool all)
+{
+ struct watch_notification n;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+ int ret = -EBADSLT;
+
+ rcu_read_lock();
+
+again:
+ spin_lock(&wlist->lock);
+ hlist_for_each_entry(watch, &wlist->watchers, list_node) {
+ if (all ||
+ (watch->id == id && rcu_access_pointer(watch->queue) == wq))
+ goto found;
+ }
+ spin_unlock(&wlist->lock);
+ goto out;
+
+found:
+ ret = 0;
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+ spin_unlock(&wlist->lock);
+
+ /* We now own the reference on watch that used to belong to wlist. */
+
+ n.type = WATCH_TYPE_META;
+ n.subtype = WATCH_META_REMOVAL_NOTIFICATION;
+ n.info = watch->info_id | sizeof(n);
+
+ wqueue = rcu_dereference(watch->queue);
+
+ /* We don't need the watch list lock for the next bit as RCU is
+ * protecting *wqueue from deallocation.
+ */
+ if (wqueue) {
+ post_one_notification(wqueue, &n, wq ? wq->cred : NULL);
+
+ spin_lock_bh(&wqueue->lock);
+
+ if (!hlist_unhashed(&watch->queue_node)) {
+ hlist_del_init_rcu(&watch->queue_node);
+ put_watch(watch);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ }
+
+ if (wlist->release_watch) {
+ void (*release_watch)(struct watch *);
+
+ release_watch = wlist->release_watch;
+ rcu_read_unlock();
+ (*release_watch)(watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+
+ if (all && !hlist_empty(&wlist->watchers))
+ goto again;
+out:
+ rcu_read_unlock();
+ return ret;
+}
+EXPORT_SYMBOL(remove_watch_from_object);
+
+/*
+ * Remove all the watches that are contributory to a queue. This has the
+ * potential to race with removal of the watches by the destruction of the
+ * objects being watched or with the distribution of notifications.
+ */
+static void watch_queue_clear(struct watch_queue *wqueue)
+{
+ struct watch_list *wlist;
+ struct watch *watch;
+ bool release;
+
+ rcu_read_lock();
+ spin_lock_bh(&wqueue->lock);
+
+ /* Prevent new additions and prevent notifications from happening */
+ wqueue->defunct = true;
+
+ while (!hlist_empty(&wqueue->watches)) {
+ watch = hlist_entry(wqueue->watches.first, struct watch, queue_node);
+ hlist_del_init_rcu(&watch->queue_node);
+ /* We now own a ref on the watch. */
+ spin_unlock_bh(&wqueue->lock);
+
+ /* We can't do the next bit under the queue lock as we need to
+ * get the list lock - which would cause a deadlock if someone
+ * was removing from the opposite direction at the same time or
+ * posting a notification.
+ */
+ wlist = rcu_dereference(watch->watch_list);
+ if (wlist) {
+ void (*release_watch)(struct watch *);
+
+ spin_lock(&wlist->lock);
+
+ release = !hlist_unhashed(&watch->list_node);
+ if (release) {
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+
+ /* We now own a second ref on the watch. */
+ }
+
+ release_watch = wlist->release_watch;
+ spin_unlock(&wlist->lock);
+
+ if (release) {
+ if (release_watch) {
+ rcu_read_unlock();
+ /* This might need to call dput(), so
+ * we have to drop all the locks.
+ */
+ (*release_watch)(watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+ }
+ }
+
+ put_watch(watch);
+ spin_lock_bh(&wqueue->lock);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ rcu_read_unlock();
+}
+
+/*
+ * Release the file.
+ */
+static int watch_queue_release(struct inode *inode, struct file *file)
+{
+ struct watch_filter *wfilter;
+ struct watch_queue *wqueue = file->private_data;
+ int i, pgref;
+
+ watch_queue_clear(wqueue);
+
+ if (wqueue->pages && wqueue->pages[0])
+ WARN_ON(page_ref_count(wqueue->pages[0]) != 1);
+
+ if (wqueue->buffer)
+ vfree(wqueue->buffer);
+ for (i = 0; i < wqueue->nr_pages; i++) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ pgref = page_ref_count(wqueue->pages[i]);
+ WARN(pgref != 1,
+ "FREE PAGE[%d] refcount %d\n", i, page_ref_count(wqueue->pages[i]));
+ __free_page(wqueue->pages[i]);
+ }
+
+ wfilter = rcu_dereference_protected(wqueue->filter, true);
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ kfree(wqueue->pages);
+ put_cred(wqueue->cred);
+ put_watch_queue(wqueue);
+ return 0;
+}
+
+#ifdef DEBUG_WITH_WRITE
+static ssize_t watch_queue_write(struct file *file,
+ const char __user *_buf, size_t len, loff_t *pos)
+{
+ struct watch_notification *n;
+ struct watch_queue *wqueue = file->private_data;
+ ssize_t ret;
+
+ if (!wqueue->buffer)
+ return -ENOBUFS;
+
+ if (len & ~WATCH_INFO_LENGTH || len == 0 || !_buf)
+ return -EINVAL;
+
+ n = memdup_user(_buf, len);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+
+ ret = -EINVAL;
+ if ((n->info & WATCH_INFO_LENGTH) != len)
+ goto error;
+ n->info &= (WATCH_INFO_LENGTH | WATCH_INFO_TYPE_FLAGS | WATCH_INFO_ID);
+
+ if (post_one_notification(wqueue, n, file->f_cred))
+ wqueue->debug = 0;
+ else
+ wqueue->debug++;
+ ret = len;
+ if (wqueue->debug > 20)
+ ret = -EIO;
+
+error:
+ kfree(n);
+ return ret;
+}
+#endif
+
+static const struct file_operations watch_queue_fops = {
+ .owner = THIS_MODULE,
+ .open = watch_queue_open,
+ .release = watch_queue_release,
+ .unlocked_ioctl = watch_queue_ioctl,
+ .poll = watch_queue_poll,
+ .mmap = watch_queue_mmap,
+#ifdef DEBUG_WITH_WRITE
+ .write = watch_queue_write,
+#endif
+ .llseek = no_llseek,
+};
+
+/**
+ * get_watch_queue - Get a watch queue from its file descriptor.
+ * @fd: The fd to query.
+ */
+struct watch_queue *get_watch_queue(int fd)
+{
+ struct watch_queue *wqueue = ERR_PTR(-EBADF);
+ struct fd f;
+
+ f = fdget(fd);
+ if (f.file) {
+ wqueue = ERR_PTR(-EINVAL);
+ if (f.file->f_op == &watch_queue_fops) {
+ wqueue = f.file->private_data;
+ kref_get(&wqueue->usage);
+ }
+ fdput(f);
+ }
+
+ return wqueue;
+}
+EXPORT_SYMBOL(get_watch_queue);
+
+static struct miscdevice watch_queue_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "watch_queue",
+ .fops = &watch_queue_fops,
+ .mode = 0666,
+};
+builtin_misc_device(watch_queue_dev);
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 2474c3f785ca..2f72ea80d4fe 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1420,6 +1420,13 @@
* @ctx is a pointer in which to place the allocated security context.
* @ctxlen points to the place to put the length of @ctx.
*
+ * @post_notification:
+ * Check to see if a watch notification can be posted to a particular
+ * queue.
+ * @q_cred: The credentials of the target watch queue.
+ * @cred: The event-triggerer's credentials
+ * @n: The notification being posted
+ *
* Security hooks for using the eBPF maps and programs functionalities through
* eBPF syscalls.
*
@@ -1698,6 +1705,11 @@ union security_list_options {
int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+ int (*post_notification)(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#ifdef CONFIG_SECURITY_NETWORK
int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -1977,6 +1989,9 @@ struct security_hook_heads {
struct hlist_head inode_notifysecctx;
struct hlist_head inode_setsecctx;
struct hlist_head inode_getsecctx;
+#ifdef CONFIG_WATCH_QUEUE
+ struct hlist_head post_notification;
+#endif
#ifdef CONFIG_SECURITY_NETWORK
struct hlist_head unix_stream_connect;
struct hlist_head unix_may_send;
diff --git a/include/linux/security.h b/include/linux/security.h
index 23c8b602c0ab..1df8d55de8da 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -58,6 +58,7 @@ struct fs_context;
struct fs_parameter;
enum fs_value_type;
struct fsinfo_kparams;
+struct watch_notification;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -396,6 +397,11 @@ void security_inode_invalidate_secctx(struct inode *inode);
int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#else /* CONFIG_SECURITY */
static inline int call_lsm_notifier(enum lsm_event event, void *data)
@@ -1215,6 +1221,14 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32
{
return -EOPNOTSUPP;
}
+#ifdef CONFIG_WATCH_QUEUE
+static inline int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return 0;
+}
+#endif
#endif /* CONFIG_SECURITY */
#ifdef CONFIG_SECURITY_NETWORK
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
new file mode 100644
index 000000000000..18e0834e8aa7
--- /dev/null
+++ b/include/linux/watch_queue.h
@@ -0,0 +1,87 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#ifndef _LINUX_WATCH_QUEUE_H
+#define _LINUX_WATCH_QUEUE_H
+
+#include <uapi/linux/watch_queue.h>
+#include <linux/kref.h>
+
+#ifdef CONFIG_WATCH_QUEUE
+
+struct watch_queue;
+
+/*
+ * Representation of a watch on an object.
+ */
+struct watch {
+ union {
+ struct rcu_head rcu;
+ u32 info_id; /* ID to be OR'd in to info field */
+ };
+ struct watch_queue __rcu *queue; /* Queue to post events to */
+ struct hlist_node queue_node; /* Link in queue->watches */
+ struct watch_list __rcu *watch_list;
+ struct hlist_node list_node; /* Link in watch_list->watchers */
+ void *private; /* Private data for the watched object */
+ u64 id; /* Internal identifier */
+ struct kref usage; /* Object usage count */
+};
+
+/*
+ * List of watches on an object.
+ */
+struct watch_list {
+ struct rcu_head rcu;
+ struct hlist_head watchers;
+ void (*release_watch)(struct watch *);
+ spinlock_t lock;
+};
+
+extern void __post_watch_notification(struct watch_list *,
+ struct watch_notification *,
+ const struct cred *,
+ u64);
+extern struct watch_queue *get_watch_queue(int);
+extern void put_watch_queue(struct watch_queue *);
+extern void put_watch_list(struct watch_list *);
+extern void init_watch(struct watch *, struct watch_queue *);
+extern int add_watch_to_object(struct watch *, struct watch_list *);
+extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+
+static inline void init_watch_list(struct watch_list *wlist)
+{
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+}
+
+static inline void post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ if (unlikely(wlist))
+ __post_watch_notification(wlist, n, cred, id);
+}
+
+static inline void remove_watch_list(struct watch_list *wlist)
+{
+ if (wlist) {
+ remove_watch_from_object(wlist, NULL, 0, true);
+ kfree_rcu(wlist, rcu);
+ }
+}
+
+#endif
+
+#endif /* _LINUX_WATCH_QUEUE_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
new file mode 100644
index 000000000000..4a7e0f735f4f
--- /dev/null
+++ b/include/uapi/linux/watch_queue.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_WATCH_QUEUE_H
+#define _UAPI_LINUX_WATCH_QUEUE_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define IOC_WATCH_QUEUE_SET_SIZE _IO('s', 0x01) /* Set the size in pages */
+#define IOC_WATCH_QUEUE_SET_FILTER _IO('s', 0x02) /* Set the filter */
+
+enum watch_notification_type {
+ WATCH_TYPE_META = 0, /* Special record */
+ WATCH_TYPE_MOUNT_NOTIFY = 1, /* Mount notification record */
+ WATCH_TYPE_SB_NOTIFY = 2, /* Superblock notification */
+ WATCH_TYPE_KEY_NOTIFY = 3, /* Key/keyring change notification */
+ WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
+#define WATCH_TYPE___NR 5
+};
+
+enum watch_meta_notification_subtype {
+ WATCH_META_SKIP_NOTIFICATION = 0, /* Just skip this record */
+ WATCH_META_REMOVAL_NOTIFICATION = 1, /* Watched object was removed */
+};
+
+/*
+ * Notification record
+ */
+struct watch_notification {
+ __u32 type:24; /* enum watch_notification_type */
+ __u32 subtype:8; /* Type-specific subtype (filterable) */
+ __u32 info;
+#define WATCH_INFO_OVERRUN 0x00000001 /* Event(s) lost due to overrun */
+#define WATCH_INFO_ENOMEM 0x00000002 /* Event(s) lost due to ENOMEM */
+#define WATCH_INFO_RECURSIVE 0x00000004 /* Change was recursive */
+#define WATCH_INFO_LENGTH 0x000001f8 /* Length of record / sizeof(watch_notification) */
+#define WATCH_INFO_IN_SUBTREE 0x00000200 /* Change was not at watched root */
+#define WATCH_INFO_TYPE_FLAGS 0x00ff0000 /* Type-specific flags */
+#define WATCH_INFO_FLAG_0 0x00010000
+#define WATCH_INFO_FLAG_1 0x00020000
+#define WATCH_INFO_FLAG_2 0x00040000
+#define WATCH_INFO_FLAG_3 0x00080000
+#define WATCH_INFO_FLAG_4 0x00100000
+#define WATCH_INFO_FLAG_5 0x00200000
+#define WATCH_INFO_FLAG_6 0x00400000
+#define WATCH_INFO_FLAG_7 0x00800000
+#define WATCH_INFO_ID 0xff000000 /* ID of watchpoint */
+};
+
+#define WATCH_LENGTH_SHIFT 3
+
+struct watch_queue_buffer {
+ union {
+ /* The first few entries are special, containing the
+ * ring management variables.
+ */
+ struct {
+ struct watch_notification watch; /* WATCH_TYPE_META */
+ __u32 head; /* Ring head index */
+ __u32 tail; /* Ring tail index */
+ __u32 mask; /* Ring index mask */
+ } meta;
+ struct watch_notification slots[0];
+ };
+};
+
+/*
+ * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER).
+ */
+struct watch_notification_type_filter {
+ __u32 type; /* Type to apply filter to */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+ __u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */
+};
+
+struct watch_notification_filter {
+ __u32 nr_filters; /* Number of filters */
+ __u32 __reserved; /* Must be 0 */
+ struct watch_notification_type_filter filters[];
+};
+
+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/mm/interval_tree.c b/mm/interval_tree.c
index 27ddfd29112a..9a53ddf4bd62 100644
--- a/mm/interval_tree.c
+++ b/mm/interval_tree.c
@@ -25,6 +25,8 @@ INTERVAL_TREE_DEFINE(struct vm_area_struct, shared.rb,
unsigned long, shared.rb_subtree_last,
vma_start_pgoff, vma_last_pgoff,, vma_interval_tree)
+EXPORT_SYMBOL_GPL(vma_interval_tree_insert);
+
/* Insert node immediately after prev in the interval tree */
void vma_interval_tree_insert_after(struct vm_area_struct *node,
struct vm_area_struct *prev,
diff --git a/mm/memory.c b/mm/memory.c
index 96f1d473c89a..9f2fa2138287 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3360,6 +3360,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,
return 0;
}
+EXPORT_SYMBOL_GPL(alloc_set_pte);
/**
diff --git a/security/security.c b/security/security.c
index 3af886e8fced..af758dc71e24 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1929,6 +1929,15 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
}
EXPORT_SYMBOL(security_inode_getsecctx);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return call_int_hook(post_notification, 0, q_cred, cred, n);
+}
+#endif
+
#ifdef CONFIG_SECURITY_NETWORK
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk)
^ permalink raw reply related
* [RFC][PATCH 0/8] Mount, FS, Block and Keyrings notifications [ver #2]
From: David Howells @ 2019-06-04 16:34 UTC (permalink / raw)
To: viro
Cc: Casey Schaufler, dhowells, raven, linux-fsdevel, linux-api,
linux-block, keyrings, linux-security-module, linux-kernel
Hi Al,
Here's a set of patches to add a general variable-length notification queue
concept and to add sources of events for:
(1) Mount topology events, such as mounting, unmounting, mount expiry,
mount reconfiguration.
(2) Superblock events, such as R/W<->R/O changes, quota overrun and I/O
errors (not complete yet).
(3) Block layer events, such as I/O errors.
(4) Key/keyring events, such as creating, linking and removal of keys.
One of the reasons for this is so that we can remove the issue of processes
having to repeatedly and regularly scan /proc/mounts, which has proven to
be a system performance problem. To further aid this, the fsinfo() syscall
on which this patch series depends, provides a way to access superblock and
mount information in binary form without the need to parse /proc/mounts.
LSM support is included:
(1) The creds of the process that did the fput() that reduced the refcount
to zero are cached in the file struct.
(2) __fput() overrides the current creds with the creds from (1) whilst
doing the cleanup, thereby making sure that the creds seen by the
destruction notification generated by mntput() appears to come from
the last fputter.
(3) security_post_notification() is called for each queue that we might
want to post a notification into, thereby allowing the LSM to prevent
covert communications.
(?) Do I need to add security_set_watch(), say, to rule on whether a watch
may be set in the first place? I might need to add a variant per
watch-type.
(?) Do I really need to keep track of the process creds in which an
implicit object destruction happened? For example, imagine you create
an fd with fsopen()/fsmount(). It is marked to dissolve the mount it
refers to on close unless move_mount() clears that flag. Now, imagine
someone looking at that fd through procfs at the same time as you exit
due to an error. The LSM sees the destruction notification come from
the looker if they happen to do their fput() after yours.
Design decisions:
(1) A misc chardev is used to create and open a ring buffer:
fd = open("/dev/watch_queue", O_RDWR);
which is then configured and mmap'd into userspace:
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
The fd cannot be read or written (though there is a facility to use
write to inject records for debugging) and userspace just pulls data
directly out of the buffer.
(2) The ring index pointers are stored inside the ring and are thus
accessible to userspace. Userspace should only update the tail
pointer and never the head pointer or risk breaking the buffer. The
kernel checks that the pointers appear valid before trying to use
them. A 'skip' record is maintained around the pointers.
(3) poll() can be used to wait for data to appear in the buffer.
(4) Records in the buffer are binary, typed and have a length so that they
can be of varying size.
This means that multiple heterogeneous sources can share a common
buffer. Tags may be specified when a watchpoint is created to help
distinguish the sources.
(5) The queue is reusable as there are 16 million types available, of
which I've used 4, so there is scope for others to be used.
(6) Records are filterable as types have up to 256 subtypes that can be
individually filtered. Other filtration is also available.
(7) Each time the buffer is opened, a new buffer is created - this means
that there's no interference between watchers.
(8) When recording a notification, the kernel will not sleep, but will
rather mark a queue as overrun if there's insufficient space, thereby
avoiding userspace causing the kernel to hang.
(9) The 'watchpoint' should be specific where possible, meaning that you
specify the object that you want to watch.
(10) The buffer is created and then watchpoints are attached to it, using
one of:
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
mount_notify(AT_FDCWD, "/", 0, fd, 0x02);
sb_notify(AT_FDCWD, "/mnt", 0, fd, 0x03);
where in all three cases, fd indicates the queue and the number after
is a tag between 0 and 255.
(11) The watch must be removed if either the watch buffer is destroyed or
the watched object is destroyed.
Things I want to avoid:
(1) Introducing features that make the core VFS dependent on the network
stack or networking namespaces (ie. usage of netlink).
(2) Dumping all this stuff into dmesg and having a daemon that sits there
parsing the output and distributing it as this then puts the
responsibility for security into userspace and makes handling
namespaces tricky. Further, dmesg might not exist or might be
inaccessible inside a container.
(3) Letting users see events they shouldn't be able to see.
Further things that could be considered:
(1) Adding a keyctl call to allow a watch on a keyring to be extended to
"children" of that keyring, such that the watch is removed from the
child if it is unlinked from the keyring.
(2) Adding global superblock event queue.
(3) Propagating watches to child superblock over automounts.
The patches can be found here also:
http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=notifications
Changes:
v2: I've fixed various issues raised by Jann Horn and GregKH and moved to
krefs for refcounting. I've added some security features to try and
give Casey Schaufler the LSM control he wants.
David
---
David Howells (8):
security: Override creds in __fput() with last fputter's creds
General notification queue with user mmap()'able ring buffer
keys: Add a notification facility
vfs: Add a mount-notification facility
vfs: Add superblock notifications
fsinfo: Export superblock notification counter
block: Add block layer notifications
Add sample notification program
Documentation/security/keys/core.rst | 58 ++
Documentation/watch_queue.rst | 328 ++++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 3
arch/x86/entry/syscalls/syscall_64.tbl | 3
block/Kconfig | 9
block/Makefile | 1
block/blk-core.c | 29 +
block/blk-notify.c | 83 +++
drivers/misc/Kconfig | 13
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 895 ++++++++++++++++++++++++++++++++
fs/Kconfig | 21 +
fs/Makefile | 1
fs/file_table.c | 12
fs/fsinfo.c | 12
fs/mount.h | 33 +
fs/mount_notify.c | 186 +++++++
fs/namespace.c | 9
fs/super.c | 117 ++++
include/linux/blkdev.h | 10
include/linux/dcache.h | 1
include/linux/fs.h | 79 +++
include/linux/key.h | 4
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/syscalls.h | 5
include/linux/watch_queue.h | 87 +++
include/uapi/linux/fsinfo.h | 10
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 185 +++++++
kernel/sys_ni.c | 7
mm/interval_tree.c | 2
mm/memory.c | 1
samples/Kconfig | 6
samples/Makefile | 1
samples/vfs/test-fsinfo.c | 13
samples/watch_queue/Makefile | 9
samples/watch_queue/watch_test.c | 284 ++++++++++
security/keys/Kconfig | 10
security/keys/compat.c | 2
security/keys/gc.c | 5
security/keys/internal.h | 30 +
security/keys/key.c | 37 +
security/keys/keyctl.c | 89 +++
security/keys/keyring.c | 17 -
security/keys/request_key.c | 4
security/security.c | 9
47 files changed, 2713 insertions(+), 38 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 block/blk-notify.c
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 fs/mount_notify.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
^ permalink raw reply
* Re: [RFC PATCH 0/9] security: x86/sgx: SGX vs. LSM
From: Sean Christopherson @ 2019-06-04 16:30 UTC (permalink / raw)
To: Stephen Smalley
Cc: Xing, Cedric, Jarkko Sakkinen, Andy Lutomirski, James Morris,
Serge E . Hallyn, LSM List, Paul Moore, Eric Paris,
selinux@vger.kernel.org, Jethro Beekman, Hansen, Dave,
Thomas Gleixner, Linus Torvalds, LKML, X86 ML,
linux-sgx@vger.kernel.org, Andrew Morton, nhorman@redhat.com,
npmccallum@redhat.com, Ayoun, Serge, Katz-zamir, Shay,
Huang, Haitao, Andy Shevchenko, Svahn, Kai, Borislav Petkov,
Josh Triplett, Huang, Kai, David Rientjes, Roberts, William C,
Tricca, Philip B
In-Reply-To: <10a49f97-b3be-ed09-2821-68157f01aebe@tycho.nsa.gov>
On Tue, Jun 04, 2019 at 11:33:44AM -0400, Stephen Smalley wrote:
> The RFC series seemed to dispense with the use of the sigstruct file and
> just used the source file throughout IIUC. That allowed for reuse of
> FILE__* permissions without ambiguity rather than introducing separate
> ENCLAVE__* permissions or using /dev/sgx/enclave inode as the target of all
> checks.
Drat, I meant to explicitly call that out in the cover letter. Yes, the
concept of using sigstruct as a proxy was dropped for this RFC. The
primary motivation was to avoid having to take a hold a reference to the
sigstruct file for the lifetime of the enclave, and in general so that
userspace isn't forced to put sigstruct into a file.
> Regardless, IIUC, your approach requires that we always check FILE__EXECMOD,
> and FILE__EXECUTE up front during security_enclave_load() irrespective of
> prot so that we can save the result in the f_security for later use by the
> mprotect hook.
Correct, this approach requires up front checks.
> This may generate many spurious audit messages for cases
> where PROT_EXEC will never be requested, and users will be prone to just
> always allowing it since they cannot tell when it was actually needed.
Userspace will be able to understand when PROT_EXEC is actually needed
as mprotect() will (eventually) fail. Of course that assumes userspace
is being intelligent and isn't blindly declaring permissions they don't
need, e.g. declaring RWX on all pages even though the enclave never
actually maps a RWX or RW->RX page.
One thought for handling this in a more user friendly fashion would be
to immediately return -EACCES instead of modifying @allowed_prot. An
enclave that truly needs the permission would fail immediately.
An enclave loader that wants/needs to speculatively declare PROT_EXEC,
e.g. because the exact requirements of the enclave are unknown, could
handle -EACCESS gracefully by retrying the SGX ioctl() with different
@allowed_prot, e.g.:
region.flags = SGX_ALLOW_READ | SGX_ALLOW_WRITE | SGX_ALLOW_EXEC;
ret = ioctl(fd, SGX_IOC_ENCLAVE_ADD_REGION, ®ion);
if (ret && errno == EACCES && !(prot & PROT_EXEC)) {
region.flags &= ~SGX_ALLOW_EXEC;
ret = ioctl(fd, SGX_IOC_ENCLAVE_ADD_REGION, ®ion);
}
This type of enclave loader would still generate spurious audit messages,
but the spurious messages would be limited to enclave loaders that are
deliberately probing the allowed permissions.
> >The noexec case should be addressed in IOC_ADD_PAGES by testing
> >@source_vma->vm_flags & VM_MAYEXEC.
> >
> >>
> >>>* In hook security_file_free(), if @file is an enclave, free storage
> >>> allocated for WRITTEN flags.
>
^ permalink raw reply
* Re: [RFC PATCH 7/9] x86/sgx: Enforce noexec filesystem restriction for enclaves
From: Jarkko Sakkinen @ 2019-06-04 16:25 UTC (permalink / raw)
To: Sean Christopherson
Cc: Andy Lutomirski, Cedric Xing, Stephen Smalley, James Morris,
Serge E . Hallyn, LSM List, Paul Moore, Eric Paris, selinux,
Jethro Beekman, Dave Hansen, Thomas Gleixner, Linus Torvalds,
LKML, X86 ML, linux-sgx, Andrew Morton, nhorman, npmccallum,
Serge Ayoun, Shay Katz-zamir, Haitao Huang, Andy Shevchenko,
Kai Svahn, Borislav Petkov, Josh Triplett, Kai Huang,
David Rientjes, William Roberts, Philip Tricca
In-Reply-To: <20190531233159.30992-8-sean.j.christopherson@intel.com>
On Fri, May 31, 2019 at 04:31:57PM -0700, Sean Christopherson wrote:
> Do not allow an enclave page to be mapped with PROT_EXEC if the source
> page is backed by a file on a noexec file system.
>
> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
Why don't you just check in sgx_encl_add_page() that whether the path
comes from noexec and deny if SECINFO contains X?
/Jarkko
^ permalink raw reply
* Re: [RFC PATCH 6/9] x86/sgx: Require userspace to provide allowed prots to ADD_PAGES
From: Jarkko Sakkinen @ 2019-06-04 16:23 UTC (permalink / raw)
To: Sean Christopherson
Cc: Andy Lutomirski, Cedric Xing, Stephen Smalley, James Morris,
Serge E . Hallyn, LSM List, Paul Moore, Eric Paris, selinux,
Jethro Beekman, Dave Hansen, Thomas Gleixner, Linus Torvalds,
LKML, X86 ML, linux-sgx, Andrew Morton, nhorman, npmccallum,
Serge Ayoun, Shay Katz-zamir, Haitao Huang, Andy Shevchenko,
Kai Svahn, Borislav Petkov, Josh Triplett, Kai Huang,
David Rientjes, William Roberts, Philip Tricca
In-Reply-To: <20190531233159.30992-7-sean.j.christopherson@intel.com>
On Fri, May 31, 2019 at 04:31:56PM -0700, Sean Christopherson wrote:
> ...to support (the equivalent) of existing Linux Security Module
> functionality.
Long and short descriptions should be separate. Also this does not
make any sense. LSM is a framework with a set of hook to make access
decisions and there various implementations of it.
How this replicates LSMs and why that even would be a goal?
My guess is that you are trying to do something else. I'm just saying
that the idea to do equivalent of LSMs to another subsystems would be
insane if it was done.
> always be MAP_SHARED. Lastly, all real world enclaves will need read,
> write and execute permissions to EPC pages. As a result, SGX does not
> play nice with existing LSM behavior as it is impossible to apply
> policies to enclaves with any reasonable granularity, e.g. an LSM can
> deny access to EPC altogether, but can't deny potentially dangerous
> behavior such as mapping pages RW->RW or RWX.
The mapping must be shared given that it is iomem but why enclave pages
would need RWX for all pages? The information that is missing from this
paragraph is the explanation why an LSM could not deny dangerous
behavior in PTE level.
> To give LSMs enough information to implement their policies without
> having to resort to ugly things, e.g. holding a reference to the vm_file
> of each enclave page, require userspace to explicitly state the allowed
> protections for each page (region), i.e. take ALLOW_{READ,WRITE,EXEC}
> in the ADD_PAGES ioctl.
I would keep descriptions such as "ugly things" away from commit
messages as it is easy way to be not clear and explicit what you are
trying to say.
> The ALLOW_* flags will be passed to LSMs so that they can make informed
> decisions when the enclave is being built, i.e. when the source vm_file
> is available. For example, SELinux's EXECMOD permission can be
> required if an enclave is requesting both ALLOW_WRITE and ALLOW_EXEC.
There should be some explanation what ALLOW_* flag are. It is now like
as it was in common knowledge. SECINFO already has protection flags to
name an example and without any explanation all of this is just very
confusing.
This should address SECINFO and ALLOW_* relationship and differences.
> Update the mmap()/mprotect() hooks to enforce the ALLOW_* protections,
> a la the standard VM_MAY{READ,WRITE,EXEC} flags.
>
> The ALLOW_EXEC flag also has a second (important) use in that it can
> be used to prevent loading an enclave from a noexec file system, on
> SGX2 hardware (regardless of kernel support for SGX2), userspace could
> EADD from a noexec path using read-only permissions and later mprotect()
> and ENCLU[EMODPE] the page to gain execute permissions. By requiring
> ALLOW_EXEC up front, SGX will be able to enforce noexec paths when
> building the enclave.
>
> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
> ---
> arch/x86/include/uapi/asm/sgx.h | 9 ++++++++-
> arch/x86/kernel/cpu/sgx/driver/ioctl.c | 23 +++++++++++++++++------
> arch/x86/kernel/cpu/sgx/encl.c | 2 +-
> arch/x86/kernel/cpu/sgx/encl.h | 1 +
> 4 files changed, 27 insertions(+), 8 deletions(-)
>
> diff --git a/arch/x86/include/uapi/asm/sgx.h b/arch/x86/include/uapi/asm/sgx.h
> index 4a12d6abbcb7..4489e92fa0dc 100644
> --- a/arch/x86/include/uapi/asm/sgx.h
> +++ b/arch/x86/include/uapi/asm/sgx.h
> @@ -31,6 +31,11 @@ struct sgx_enclave_create {
> __u64 src;
> };
>
> +/* Supported flags for struct sgx_enclave_add_pages. */
> +#define SGX_ALLOW_READ VM_READ
> +#define SGX_ALLOW_WRITE VM_WRITE
> +#define SGX_ALLOW_EXEC VM_EXEC
Why these flags are even defined if they are the same as VM_* flags?
> +
> /**
> * struct sgx_enclave_add_pages - parameter structure for the
> * %SGX_IOC_ENCLAVE_ADD_PAGES ioctl
> @@ -39,6 +44,7 @@ struct sgx_enclave_create {
> * @secinfo: address for the SECINFO data (common to all pages)
> * @nr_pages: number of pages (must be virtually contiguous)
> * @mrmask: bitmask for the measured 256 byte chunks (common to all pages)
> + * @flags: flags, e.g. SGX_ALLOW_{READ,WRITE,EXEC} (common to all pages)
> */
> struct sgx_enclave_add_pages {
> __u64 addr;
> @@ -46,7 +52,8 @@ struct sgx_enclave_add_pages {
> __u64 secinfo;
> __u32 nr_pages;
> __u16 mrmask;
> -} __attribute__((__packed__));
> + __u16 flags;
> +};
>
> /**
> * struct sgx_enclave_init - parameter structure for the
> diff --git a/arch/x86/kernel/cpu/sgx/driver/ioctl.c b/arch/x86/kernel/cpu/sgx/driver/ioctl.c
> index 6acfcbdeca9a..c30acd3fbbdd 100644
> --- a/arch/x86/kernel/cpu/sgx/driver/ioctl.c
> +++ b/arch/x86/kernel/cpu/sgx/driver/ioctl.c
> @@ -235,7 +235,8 @@ static int sgx_validate_secs(const struct sgx_secs *secs,
> }
>
> static struct sgx_encl_page *sgx_encl_page_alloc(struct sgx_encl *encl,
> - unsigned long addr)
> + unsigned long addr,
> + unsigned long allowed_prot)
> {
> struct sgx_encl_page *encl_page;
> int ret;
> @@ -247,6 +248,7 @@ static struct sgx_encl_page *sgx_encl_page_alloc(struct sgx_encl *encl,
> return ERR_PTR(-ENOMEM);
> encl_page->desc = addr;
> encl_page->encl = encl;
> + encl_page->allowed_prot = allowed_prot;
> ret = radix_tree_insert(&encl->page_tree, PFN_DOWN(encl_page->desc),
> encl_page);
> if (ret) {
> @@ -530,7 +532,7 @@ static int sgx_encl_queue_page(struct sgx_encl *encl,
>
> static int __sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> void *data, struct sgx_secinfo *secinfo,
> - unsigned int mrmask)
> + unsigned int mrmask, unsigned long allowed_prot)
> {
> u64 page_type = secinfo->flags & SGX_SECINFO_PAGE_TYPE_MASK;
> struct sgx_encl_page *encl_page;
> @@ -556,7 +558,7 @@ static int __sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> goto out;
> }
>
> - encl_page = sgx_encl_page_alloc(encl, addr);
> + encl_page = sgx_encl_page_alloc(encl, addr, allowed_prot);
> if (IS_ERR(encl_page)) {
> ret = PTR_ERR(encl_page);
> goto out;
> @@ -576,12 +578,20 @@ static int __sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
>
> static int sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> unsigned long src, struct sgx_secinfo *secinfo,
> - unsigned int mrmask)
> + unsigned int mrmask, unsigned int flags)
> {
> + unsigned long prot = secinfo->flags & (VM_READ | VM_WRITE | VM_EXEC);
Even if the secinfo flags have the exactly the same values you should
not do this as they are kind of from different type. This is confusing
to read.
> + unsigned long allowed_prot = flags & (VM_READ | VM_WRITE | VM_EXEC);
Why you take the trouble defining those macros and do not then use them
even yourself?
> struct page *data_page;
> void *data;
> int ret;
>
> + BUILD_BUG_ON(SGX_SECINFO_R != VM_READ || SGX_SECINFO_W != VM_WRITE ||
> + SGX_SECINFO_X != VM_EXEC);
Why this check?
> +
> + if (prot & ~allowed_prot)
> + return -EACCES;
> +
> data_page = alloc_page(GFP_HIGHUSER);
> if (!data_page)
> return -ENOMEM;
> @@ -593,7 +603,8 @@ static int sgx_encl_add_page(struct sgx_encl *encl, unsigned long addr,
> goto out;
> }
>
> - ret = __sgx_encl_add_page(encl, addr, data, secinfo, mrmask);
> + ret = __sgx_encl_add_page(encl, addr, data, secinfo, mrmask,
> + allowed_prot);
> out:
> kunmap(data_page);
> __free_page(data_page);
> @@ -645,7 +656,7 @@ static long sgx_ioc_enclave_add_pages(struct file *filep, unsigned int cmd,
>
> ret = sgx_encl_add_page(encl, addp->addr + i*PAGE_SIZE,
> addp->src + i*PAGE_SIZE,
> - &secinfo, addp->mrmask);
> + &secinfo, addp->mrmask, addp->flags);
> }
> return ret;
> }
> diff --git a/arch/x86/kernel/cpu/sgx/encl.c b/arch/x86/kernel/cpu/sgx/encl.c
> index 955d4f430adc..e5847571a265 100644
> --- a/arch/x86/kernel/cpu/sgx/encl.c
> +++ b/arch/x86/kernel/cpu/sgx/encl.c
> @@ -249,7 +249,7 @@ int sgx_map_allowed(struct sgx_encl *encl, unsigned long start,
>
> for (addr = start; addr < end; addr += PAGE_SIZE) {
> page = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT);
> - if (!page)
> + if (!page || (prot & ~page->allowed_prot))
> return -EACCES;
> }
However this goes it would be good idea to have only ony patch in the
patch set that fully defines this function. Impossible to review
properly with this split.
>
>
> diff --git a/arch/x86/kernel/cpu/sgx/encl.h b/arch/x86/kernel/cpu/sgx/encl.h
> index 6e310e3b3fff..7cca076a4987 100644
> --- a/arch/x86/kernel/cpu/sgx/encl.h
> +++ b/arch/x86/kernel/cpu/sgx/encl.h
> @@ -41,6 +41,7 @@ enum sgx_encl_page_desc {
>
> struct sgx_encl_page {
> unsigned long desc;
> + unsigned long allowed_prot;
> struct sgx_epc_page *epc_page;
> struct sgx_va_page *va_page;
> struct sgx_encl *encl;
> --
> 2.21.0
>
This patch left me very confused. I don't get it.
/Jarkko
^ permalink raw reply
* Re: [PATCH 00/58] LSM: Module stacking for AppArmor
From: Casey Schaufler @ 2019-06-04 16:14 UTC (permalink / raw)
To: Stephen Smalley, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: keescook, john.johansen, penguin-kernel, paul, casey
In-Reply-To: <f71388e9-a4c5-8935-137b-8eb50be7f833@tycho.nsa.gov>
On 6/4/2019 5:29 AM, Stephen Smalley wrote:
> On 6/2/19 12:50 PM, Casey Schaufler wrote:
>> This patchset provides the changes required for
>> the AppArmor security module to stack safely with any other.
>
> Please explain the motivation
I'll add some explanation for the next revision.
It won't be anything that I haven't posted many times
before, but you're right that it belongs in the log.
> - why do we want to allow AppArmor to stack with other modules,
First, is there a reason not to? Sure, you can confuse
administrators by implementing complex security policies,
but there are lots of ways to do that already.
AppArmor provides a different security model than SELinux,
TOMOYO or Smack. Smack is better at system component
separation, while AppArmor is better at application isolation.
It's a win to use each to its strength rather than trying to
stretch either to the edge of what it can do.
> who would use it,
Can't name names, but there have been multiple requests.
> how would it be used,
As mentioned above, Smack for system separation, AppArmor for
application isolation.
> what does it provide that isn't already possible in the absence of it.
It's not necessary that something be impossible to do any
other way. The question should be whether this provides for
a better way to achieve the goals, and this does that.
If I tried the come up with something that's impossible I
would expect the usual "you can do that with SELinux policy"
argument. We know we can do things. We want to have the tools
to do them better.
> Also, Ubuntu fully upstreamed all of their changes to AppArmor, would this still suffice to enable stacking of AppArmor or do they rely on hooks that are not handled here?
Some amount of merging will likely be required. But that's
always going to be true with parallel development tracks.
That's why we have git!
> Please explain the cost of the change - what do we pay in terms of memory, runtime, or other overheads in order to support this change?
Do you have particular benchmarks you want to see?
When I've supplied numbers in the past they have not
been remarked on.
>
>>
>> A new process attribute identifies which security module
>> information should be reported by SO_PEERSEC and the
>> /proc/.../attr/current interface. This is provided by
>> /proc/.../attr/display. Writing the name of the security
>> module desired to this interface will set which LSM hooks
>> will be called for this information. The first security
>> module providing the hooks will be used by default.
>
> Doesn't this effectively undo making the hooks read-only after init, at least for the subset involved? What are the security implications thereof?
Any mechanism, be it a separate set of hooks, a name used to
do list look ups, or an sophisticated hash scheme will have that
impact for the processes that use it. This scheme has the best
performance profile of the mechanisms I experimented with and
avoids all sorts of special cases.
>
>> The use of integer based security tokens (secids) is
>> generally (but not completely) replaced by a structure
>> lsm_export. The lsm_export structure can contain information
>> for each of the security modules that export information
>> outside the LSM layer.
>>
>> The LSM interfaces that provide "secctx" text strings
>> have been changed to use a structure "lsm_context"
>> instead of a pointer/length pair. In some cases the
>> interfaces used a "char *" pointer and in others a
>> "void *". This was necessary to ensure that the correct
>> release mechanism for the text is used. It also makes
>> many of the interfaces cleaner.
>>
>> https://github.com/cschaufler/lsm-stacking.git#stack-5.2-v1-apparmor
>>
>> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
>> ---
>> drivers/android/binder.c | 25 ++-
>> fs/kernfs/dir.c | 6 +-
>> fs/kernfs/inode.c | 31 ++-
>> fs/kernfs/kernfs-internal.h | 3 +-
>> fs/nfs/inode.c | 13 +-
>> fs/nfs/internal.h | 8 +-
>> fs/nfs/nfs4proc.c | 17 +-
>> fs/nfs/nfs4xdr.c | 16 +-
>> fs/nfsd/nfs4proc.c | 8 +-
>> fs/nfsd/nfs4xdr.c | 14 +-
>> fs/nfsd/vfs.c | 7 +-
>> fs/proc/base.c | 1 +
>> include/linux/cred.h | 3 +-
>> include/linux/lsm_hooks.h | 91 +++++----
>> include/linux/nfs4.h | 8 +-
>> include/linux/security.h | 133 +++++++++----
>> include/net/af_unix.h | 2 +-
>> include/net/netlabel.h | 10 +-
>> include/net/scm.h | 14 +-
>> kernel/audit.c | 43 ++--
>> kernel/audit.h | 9 +-
>> kernel/auditfilter.c | 6 +-
>> kernel/auditsc.c | 77 ++++----
>> kernel/cred.c | 15 +-
>> net/ipv4/cipso_ipv4.c | 13 +-
>> net/ipv4/ip_sockglue.c | 12 +-
>> net/netfilter/nf_conntrack_netlink.c | 29 ++-
>> net/netfilter/nf_conntrack_standalone.c | 16 +-
>> net/netfilter/nfnetlink_queue.c | 38 ++--
>> net/netfilter/nft_meta.c | 13 +-
>> net/netfilter/xt_SECMARK.c | 14 +-
>> net/netlabel/netlabel_kapi.c | 5 +-
>> net/netlabel/netlabel_unlabeled.c | 101 +++++-----
>> net/netlabel/netlabel_unlabeled.h | 2 +-
>> net/netlabel/netlabel_user.c | 13 +-
>> net/netlabel/netlabel_user.h | 2 +-
>> net/unix/af_unix.c | 6 +-
>> security/apparmor/audit.c | 4 +-
>> security/apparmor/include/audit.h | 2 +-
>> security/apparmor/include/net.h | 6 +-
>> security/apparmor/include/secid.h | 9 +-
>> security/apparmor/lsm.c | 64 +++---
>> security/apparmor/secid.c | 42 ++--
>> security/integrity/ima/ima.h | 14 +-
>> security/integrity/ima/ima_api.c | 9 +-
>> security/integrity/ima/ima_appraise.c | 6 +-
>> security/integrity/ima/ima_main.c | 34 ++--
>> security/integrity/ima/ima_policy.c | 19 +-
>> security/security.c | 338 +++++++++++++++++++++++++++-----
>> security/selinux/hooks.c | 259 ++++++++++++------------
>> security/selinux/include/audit.h | 5 +-
>> security/selinux/include/objsec.h | 42 +++-
>> security/selinux/netlabel.c | 25 +--
>> security/selinux/ss/services.c | 18 +-
>> security/smack/smack.h | 18 ++
>> security/smack/smack_lsm.c | 238 +++++++++++-----------
>> security/smack/smack_netfilter.c | 8 +-
>> security/smack/smackfs.c | 12 +-
>> 58 files changed, 1217 insertions(+), 779 deletions(-)
>>
>
^ permalink raw reply
* Re: [RFC PATCH 0/9] security: x86/sgx: SGX vs. LSM
From: Stephen Smalley @ 2019-06-04 15:33 UTC (permalink / raw)
To: Xing, Cedric, Christopherson, Sean J
Cc: Jarkko Sakkinen, Andy Lutomirski, James Morris, Serge E . Hallyn,
LSM List, Paul Moore, Eric Paris, selinux@vger.kernel.org,
Jethro Beekman, Hansen, Dave, Thomas Gleixner, Linus Torvalds,
LKML, X86 ML, linux-sgx@vger.kernel.org, Andrew Morton,
nhorman@redhat.com, npmccallum@redhat.com, Ayoun, Serge,
Katz-zamir, Shay, Huang, Haitao, Andy Shevchenko, Svahn, Kai,
Borislav Petkov, Josh Triplett, Huang, Kai, David Rientjes,
Roberts, William C, Tricca, Philip B
In-Reply-To: <960B34DE67B9E140824F1DCDEC400C0F654ED042@ORSMSX116.amr.corp.intel.com>
On 6/3/19 2:30 PM, Xing, Cedric wrote:
>> From: Christopherson, Sean J
>> Sent: Monday, June 03, 2019 10:16 AM
>>
>> On Sun, Jun 02, 2019 at 12:29:35AM -0700, Xing, Cedric wrote:
>>> Hi Sean,
>>>
>>> Generally I agree with your direction but think ALLOW_* flags are
>>> completely internal to LSM because they can be both produced and
>>> consumed inside an LSM module. So spilling them into SGX driver and
>>> also user mode code makes the solution ugly and in some cases
>>> impractical because not every enclave host process has a priori
>>> knowledge on whether or not an enclave page would be EMODPE'd at
>> runtime.
>>
>> In this case, the host process should tag *all* pages it *might* convert
>> to executable as ALLOW_EXEC. LSMs can (and should/will) be written in
>> such a way that denying ALLOW_EXEC is fatal to the enclave if and only
>> if the enclave actually attempts mprotect(PROT_EXEC).
>
> What if those pages contain self-modifying code but the host doesn't know ahead of time? Would it require ALLOW_WRITE|ALLOW_EXEC at EADD? Then would it prevent those pages to start with PROT_EXEC?
>
> Anyway, my point is that it is unnecessary even if it works.
>
>>
>> Take the SELinux path for example. The only scenario in which
>> PROT_WRITE is cleared from @allowed_prot is if the page *starts* with
>> PROT_EXEC.
>> If PROT_EXEC is denied on a page that starts RW, e.g. an EAUG'd page,
>> then PROT_EXEC will be cleared from @allowed_prot.
>>
>> As Stephen pointed out, auditing the denials on @allowed_prot means the
>> log will contain false positives of a sort. But this is more of a noise
>> issue than true false positives. E.g. there are three possible outcomes
>> for the enclave.
>>
>> - The enclave does not do EMODPE[PROT_EXEC] in any scenario, ever.
>> Requesting ALLOW_EXEC is either a straightforward a userspace bug or
>> a poorly written generic enclave loader.
>>
>> - The enclave conditionally performs EMODPE[PROT_EXEC]. In this case
>> the denial is a true false positive.
>>
>> - The enclave does EMODPE[PROT_EXEC] and its host userspace then fails
>> on mprotect(PROT_EXEC), i.e. the LSM denial is working as intended.
>> The audit log will be noisy, but viewed as a whole the denials
>> aren't
>> false positives.
>
> What I was talking about was EMODPE[PROT_WRITE] on an RX page.
>
>>
>> The potential for noisy audit logs and/or false positives is unfortunate,
>> but it's (by far) the lesser of many evils.
>>
>>> Theoretically speaking, what you really need is a per page flag (let's
>>> name it WRITTEN?) indicating whether a page has ever been written to
>>> (or more precisely, granted PROT_WRITE), which will be used to decide
>>> whether to grant PROT_EXEC when requested in future. Given the fact
>>> that all mprotect() goes through LSM and mmap() is limited to
>>> PROT_NONE, it's easy for LSM to capture that flag by itself instead of
>> asking user mode code to provide it.
>>>
>>> That said, here is the summary of what I think is a better approach.
>>> * In hook security_file_alloc(), if @file is an enclave, allocate some
>> data
>>> structure to store for every page, the WRITTEN flag as described
>> above.
>>> WRITTEN is cleared initially for all pages.
>>
>> This would effectively require *every* LSM to duplicate the SGX driver's
>> functionality, e.g. track per-page metadata, implement locking to
>> prevent races between multiple mm structs, etc...
>
> Architecturally we shouldn't dictate how LSM makes decisions. ALLOW_* are no difference than PROCESS__* or FILE__* flags, which are just artifacts to assist particular LSMs in decision making. They are never considered part of the LSM interface, even if other LSMs than SELinux may adopt the same/similar approach.
>
> If code duplication is what you are worrying about, you can put them in a library, or implement/export them in some new file (maybe security/enclave.c?) as utility functions. But spilling them into user mode is what I think is unacceptable.
>
>>
>>> Open: Given a file of type struct file *, how to tell if it is an
>> enclave (i.e. /dev/sgx/enclave)?
>>> * In hook security_mmap_file(), if @file is an enclave, make sure
>> @prot can
>>> only be PROT_NONE. This is to force all protection changes to go
>> through
>>> security_file_mprotect().
>>> * In the newly introduced hook security_enclave_load(), set WRITTEN
>> for pages
>>> that are requested PROT_WRITE.
>>
>> How would an LSM associate a page with a specific enclave? vma->vm_file
>> will point always point at /dev/sgx/enclave. vma->vm_mm is useless
>> because we're allowing multiple processes to map a single enclave, not
>> to mention that by mm would require holding a reference to the mm.
>
> Each open("/dev/sgx/enclave") syscall creates a *new* instance of struct file to uniquely identify one enclave instance. What I mean is @vma->vm_file, not @vma->vm_file->f_path or @vma->vm_file->f_inode.
>
>>
>>> * In hook security_file_mprotect(), if @vma->vm_file is an enclave,
>> look up
>>> and use WRITTEN flags for all pages within @vma, along with other
>> global
>>> flags (e.g. PROCESS__EXECMEM/FILE__EXECMOD in the case of SELinux)
>> to decide
>>> on allowing/rejecting @prot.
>>
>> vma->vm_file will always be /dev/sgx/enclave at this point, which means
>> LSMs don't have the necessary anchor back to the source file, e.g. to
>> enforce FILE__EXECUTE. The noexec file system case is also unaddressed.
>
> vma->vm_file identifies an enclave instance uniquely. FILE__EXECUTE is checked by security_enclave_load() using @source_vma->vm_file. Once a page has been EADD'ed, whether to allow RW->RX depends on .sigstruct file (more precisely, the file backing SIGSTRUCT), whose FILE__* attributes could be cached in vma->vm_file->f_security by security_enclave_init().
The RFC series seemed to dispense with the use of the sigstruct file and
just used the source file throughout IIUC. That allowed for reuse of
FILE__* permissions without ambiguity rather than introducing separate
ENCLAVE__* permissions or using /dev/sgx/enclave inode as the target of
all checks.
Regardless, IIUC, your approach requires that we always check
FILE__EXECMOD, and FILE__EXECUTE up front during security_enclave_load()
irrespective of prot so that we can save the result in the f_security
for later use by the mprotect hook. This may generate many spurious
audit messages for cases where PROT_EXEC will never be requested, and
users will be prone to just always allowing it since they cannot tell
when it was actually needed.
>
> The noexec case should be addressed in IOC_ADD_PAGES by testing @source_vma->vm_flags & VM_MAYEXEC.
>
>>
>>> * In hook security_file_free(), if @file is an enclave, free storage
>>> allocated for WRITTEN flags.
^ permalink raw reply
* Re: [RFC PATCH 5/9] x86/sgx: Restrict mapping without an enclave page to PROT_NONE
From: Jarkko Sakkinen @ 2019-06-04 15:32 UTC (permalink / raw)
To: Sean Christopherson
Cc: Andy Lutomirski, Cedric Xing, Stephen Smalley, James Morris,
Serge E . Hallyn, LSM List, Paul Moore, Eric Paris, selinux,
Jethro Beekman, Dave Hansen, Thomas Gleixner, Linus Torvalds,
LKML, X86 ML, linux-sgx, Andrew Morton, nhorman, npmccallum,
Serge Ayoun, Shay Katz-zamir, Haitao Huang, Andy Shevchenko,
Kai Svahn, Borislav Petkov, Josh Triplett, Kai Huang,
David Rientjes, William Roberts, Philip Tricca
In-Reply-To: <20190531233159.30992-6-sean.j.christopherson@intel.com>
On Fri, May 31, 2019 at 04:31:55PM -0700, Sean Christopherson wrote:
> To support LSM integration, SGX will require userspace to explicitly
> specify the allowed protections for each page. The allowed protections
> will be supplied to and modified by LSMs (based on their policies).
How the allowed protections are modified by LSMs? AFAIK they don't touch
the PROT_* flags but I could be wrong too.
> To prevent userspace from circumventing the allowed protections, do not
> allow PROT_{READ,WRITE,EXEC} mappings to an enclave without an
> associated enclave page (which will track the allowed protections).
>
> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
> ---
> arch/x86/kernel/cpu/sgx/driver/main.c | 5 +++++
> arch/x86/kernel/cpu/sgx/encl.c | 30 +++++++++++++++++++++++++++
> arch/x86/kernel/cpu/sgx/encl.h | 3 +++
> 3 files changed, 38 insertions(+)
>
> diff --git a/arch/x86/kernel/cpu/sgx/driver/main.c b/arch/x86/kernel/cpu/sgx/driver/main.c
> index 129d356aff30..65a87c2fdf02 100644
> --- a/arch/x86/kernel/cpu/sgx/driver/main.c
> +++ b/arch/x86/kernel/cpu/sgx/driver/main.c
> @@ -63,6 +63,11 @@ static long sgx_compat_ioctl(struct file *filep, unsigned int cmd,
> static int sgx_mmap(struct file *file, struct vm_area_struct *vma)
> {
> struct sgx_encl *encl = file->private_data;
> + int ret;
> +
> + ret = sgx_map_allowed(encl, vma->vm_start, vma->vm_end, vma->vm_flags);
> + if (ret)
> + return ret;
>
> vma->vm_ops = &sgx_vm_ops;
> vma->vm_flags |= VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP | VM_IO;
> diff --git a/arch/x86/kernel/cpu/sgx/encl.c b/arch/x86/kernel/cpu/sgx/encl.c
> index f23ea0fbaa47..955d4f430adc 100644
> --- a/arch/x86/kernel/cpu/sgx/encl.c
> +++ b/arch/x86/kernel/cpu/sgx/encl.c
> @@ -235,6 +235,35 @@ static void sgx_vma_close(struct vm_area_struct *vma)
> kref_put(&encl->refcount, sgx_encl_release);
> }
>
> +int sgx_map_allowed(struct sgx_encl *encl, unsigned long start,
> + unsigned long end, unsigned long prot)
Documentation missing.
> +{
> + struct sgx_encl_page *page;
> + unsigned long addr;
> +
> + prot &= (VM_READ | VM_WRITE | VM_EXEC);
> + if (!prot || !encl)
> + return 0;
> +
> + mutex_lock(&encl->lock);
> +
> + for (addr = start; addr < end; addr += PAGE_SIZE) {
> + page = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT);
> + if (!page)
> + return -EACCES;
> + }
> +
> + mutex_unlock(&encl->lock);
> +
> + return 0;
> +}
> +
> +static int sgx_vma_mprotect(struct vm_area_struct *vma, unsigned long start,
> + unsigned long end, unsigned long prot)
> +{
> + return sgx_map_allowed(vma->vm_private_data, start, end, prot);
> +}
> +
> static unsigned int sgx_vma_fault(struct vm_fault *vmf)
> {
> unsigned long addr = (unsigned long)vmf->address;
> @@ -372,6 +401,7 @@ static int sgx_vma_access(struct vm_area_struct *vma, unsigned long addr,
> const struct vm_operations_struct sgx_vm_ops = {
> .close = sgx_vma_close,
> .open = sgx_vma_open,
> + .mprotect = sgx_vma_mprotect,
> .fault = sgx_vma_fault,
> .access = sgx_vma_access,
> };
> diff --git a/arch/x86/kernel/cpu/sgx/encl.h b/arch/x86/kernel/cpu/sgx/encl.h
> index c557f0374d74..6e310e3b3fff 100644
> --- a/arch/x86/kernel/cpu/sgx/encl.h
> +++ b/arch/x86/kernel/cpu/sgx/encl.h
> @@ -106,6 +106,9 @@ static inline unsigned long sgx_pcmd_offset(pgoff_t page_index)
> sizeof(struct sgx_pcmd);
> }
>
> +int sgx_map_allowed(struct sgx_encl *encl, unsigned long start,
> + unsigned long end, unsigned long prot);
> +
> enum sgx_encl_mm_iter {
> SGX_ENCL_MM_ITER_DONE = 0,
> SGX_ENCL_MM_ITER_NEXT = 1,
> --
> 2.21.0
This is missing explanation why it is OK to have a mismatch between
the SECINFO flags and VM_* flags. Maybe that could be explained in
sgx_map_allowed() documentation.
/Jarkko
^ permalink raw reply
* Re: [PATCH v5 2/3] mm: init: report memory auto-initialization features at boot time
From: Alexander Potapenko @ 2019-06-04 15:06 UTC (permalink / raw)
To: Kaiwan N Billimoria
Cc: Kees Cook, Andrew Morton, Christoph Lameter, Dmitry Vyukov,
James Morris, Jann Horn, Kostya Serebryany, Laura Abbott,
Mark Rutland, Masahiro Yamada, Matthew Wilcox, Nick Desaulniers,
Randy Dunlap, Sandeep Patil, Serge E. Hallyn, Souptick Joarder,
Marco Elver, Kernel Hardening, Linux Memory Management List,
linux-security-module
In-Reply-To: <CAPDLWs-JqUx+_sDtsER=keDu9o2NKYQ3mvZVXLY8deXOMZoH=g@mail.gmail.com>
On Tue, Jun 4, 2019 at 8:01 AM Kaiwan N Billimoria
<kaiwan@kaiwantech.com> wrote:
>
> On Tue, Jun 4, 2019 at 8:44 AM Kees Cook <keescook@chromium.org> wrote:
> >
> > On Mon, Jun 03, 2019 at 11:24:49AM +0200, Alexander Potapenko wrote:
> > > On Sat, Jun 1, 2019 at 3:18 AM Andrew Morton <akpm@linux-foundation.org> wrote:
> > > >
> > > > On Wed, 29 May 2019 14:38:11 +0200 Alexander Potapenko <glider@google.com> wrote:
> > > >
> > > > > Print the currently enabled stack and heap initialization modes.
> > > > >
> > > > > The possible options for stack are:
> > > > > - "all" for CONFIG_INIT_STACK_ALL;
> > > > > - "byref_all" for CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL;
> > > > > - "byref" for CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF;
> > > > > - "__user" for CONFIG_GCC_PLUGIN_STRUCTLEAK_USER;
> > > > > - "off" otherwise.
> > > > >
> > > > > Depending on the values of init_on_alloc and init_on_free boottime
> > > > > options we also report "heap alloc" and "heap free" as "on"/"off".
> > > >
> > > > Why?
> > > >
> > > > Please fully describe the benefit to users so that others can judge the
> > > > desirability of the patch. And so they can review it effectively, etc.
> > > I'm going to update the description with the following passage:
> > >
> > > Print the currently enabled stack and heap initialization modes.
> > >
> > > Stack initialization is enabled by a config flag, while heap
> > > initialization is configured at boot time with defaults being set
> > > in the config. It's more convenient for the user to have all information
> > > about these hardening measures in one place.
> > >
> > > Does this make sense?
> > > > Always!
> > > >
> > > > > In the init_on_free mode initializing pages at boot time may take some
> > > > > time, so print a notice about that as well.
> > > >
> > > > How much time?
> > > I've seen pauses up to 1 second, not actually sure they're worth a
> > > separate line in the log.
> > > Kees, how long were the delays in your case?
> >
> > I didn't measure it, but I think it was something like 0.5 second per GB.
> > I noticed because normally boot flashes by. With init_on_free it pauses
> > for no apparent reason, which is why I suggested the note. (I mean *I*
> > knew why it was pausing, but it might surprise someone who sets
> > init_on_free=1 without really thinking about what's about to happen at
> > boot.)
>
> (Pardon the gmail client)
> How about:
> - if (want_init_on_free())
> - pr_info("Clearing system memory may take some time...\n");
> + if (want_init_on_free())
> + pr_info("meminit: clearing system memory may take some
> time...\n");
Yes, adding a prefix may give the users better understanding of who's
clearing the memory.
We should stick to the same prefix as before though, i.e. "mem auto-init"
>
> or even
>
> + if (want_init_on_free())
> + pr_info("meminit (init_on_free == 1): clearing system
> memory may take some time...\n");
>
> or some combo thereof?
>
> --
> Kaiwan
> >
> > --
> > Kees Cook
> >
--
Alexander Potapenko
Software Engineer
Google Germany GmbH
Erika-Mann-Straße, 33
80636 München
Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
^ permalink raw reply
* Re: [RFC PATCH 4/9] mm: Introduce vm_ops->mprotect()
From: Andy Lutomirski @ 2019-06-04 14:51 UTC (permalink / raw)
To: Sean Christopherson
Cc: Jarkko Sakkinen, Andy Lutomirski, Cedric Xing, Stephen Smalley,
James Morris, Serge E . Hallyn, LSM List, Paul Moore, Eric Paris,
selinux, Jethro Beekman, Dave Hansen, Thomas Gleixner,
Linus Torvalds, LKML, X86 ML, linux-sgx, Andrew Morton, nhorman,
npmccallum, Serge Ayoun, Shay Katz-zamir, Haitao Huang,
Andy Shevchenko, Kai Svahn, Borislav Petkov, Josh Triplett,
Kai Huang, David Rientjes, William Roberts, Philip Tricca
In-Reply-To: <20190531233159.30992-5-sean.j.christopherson@intel.com>
On Fri, May 31, 2019 at 4:32 PM Sean Christopherson
<sean.j.christopherson@intel.com> wrote:
>
> SGX will use the mprotect() hook to prevent userspace from circumventing
> various security checks, i.e. Linux Security Modules.
>
> Enclaves are built by copying data from normal memory into the Enclave
> Page Cache (EPC). Due to the nature of SGX, the EPC is represented by a
> single file that must be MAP_SHARED, i.e. mprotect() only ever sees a
> single MAP_SHARED vm_file. Furthermore, all enclaves will need read,
> write and execute pages in the EPC.
>
> As a result, LSM policies cannot be meaningfully applied, e.g. an LSM
> can deny access to the EPC as a whole, but can't deny PROT_EXEC on page
> that originated in a non-EXECUTE file (which is long gone by the time
> mprotect() is called).
>
> By hooking mprotect(), SGX can make explicit LSM upcalls while an
> enclave is being built, i.e. when the kernel has a handle to origin of
> each enclave page, and enforce the result of the LSM policy whenever
> userspace maps the enclave page in the future.
>
> Alternatively, SGX could play games with MAY_{READ,WRITE,EXEC}, but
> that approach is quite ugly, e.g. would require userspace to call an
> SGX ioctl() prior to using mprotect() to extend a page's protections.
>
> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
> ---
> include/linux/mm.h | 2 ++
> mm/mprotect.c | 15 +++++++++++----
> 2 files changed, 13 insertions(+), 4 deletions(-)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 0e8834ac32b7..50a42364a885 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -458,6 +458,8 @@ struct vm_operations_struct {
> void (*close)(struct vm_area_struct * area);
> int (*split)(struct vm_area_struct * area, unsigned long addr);
> int (*mremap)(struct vm_area_struct * area);
> + int (*mprotect)(struct vm_area_struct * area, unsigned long start,
> + unsigned long end, unsigned long prot);
> vm_fault_t (*fault)(struct vm_fault *vmf);
> vm_fault_t (*huge_fault)(struct vm_fault *vmf,
> enum page_entry_size pe_size);
> diff --git a/mm/mprotect.c b/mm/mprotect.c
> index bf38dfbbb4b4..e466ca5e4fe0 100644
> --- a/mm/mprotect.c
> +++ b/mm/mprotect.c
> @@ -547,13 +547,20 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
> goto out;
> }
>
> - error = security_file_mprotect(vma, reqprot, prot);
> - if (error)
> - goto out;
> -
> tmp = vma->vm_end;
> if (tmp > end)
> tmp = end;
> +
> + if (vma->vm_ops && vma->vm_ops->mprotect) {
> + error = vma->vm_ops->mprotect(vma, nstart, tmp, prot);
> + if (error)
> + goto out;
> + }
> +
> + error = security_file_mprotect(vma, reqprot, prot);
> + if (error)
> + goto out;
> +
I think that, if you're going to do it like this, you need to call it
mprotect_and_check_security or something. Or you could just add
.may_mprotect, which is allowed to fail but, on success, falls through
to call security_file_mprotect and mprotect_fixup().
--Andy
^ permalink raw reply
* Re: [RFC][PATCH 0/7] Mount, FS, Block and Keyrings notifications
From: David Howells @ 2019-06-04 12:33 UTC (permalink / raw)
To: Amir Goldstein
Cc: dhowells, Jan Kara, Al Viro, Ian Kent, linux-fsdevel, linux-api,
linux-block, keyrings, LSM List, linux-kernel
In-Reply-To: <CAOQ4uxjLzURf8c1UH_xCJKkuD2es8i-=P-ZNM=t3aFcZLMwXEg@mail.gmail.com>
Amir Goldstein <amir73il@gmail.com> wrote:
> Well I am sure that ring buffer for fanotify events would be useful, so
> seeing that David is proposing a generic notification mechanism, I wanted
> to know how that mechanism could best share infrastructure with fsnotify.
>
> But apart from that I foresee the questions from users about why the
> mount notification API and filesystem events API do not have better
> integration.
>
> The way I see it, the notification queue can serve several classes
> of notifications and fsnotify could be one of those classes
> (at least FAN_CLASS_NOTIF fits nicely to the model).
It could be done; the main thing that concerns me is that the buffer is of
limited capacity.
However, I could take this:
struct fanotify_event_metadata {
__u32 event_len;
__u8 vers;
__u8 reserved;
__u16 metadata_len;
__aligned_u64 mask;
__s32 fd;
__s32 pid;
};
and map it to:
struct fanotify_notification {
struct watch_notification watch; /* WATCH_TYPE_FANOTIFY */
__aligned_u64 mask;
__u16 metadata_len;
__u8 vers;
__u8 reserved;
__u32 reserved2;
__s32 fd;
__s32 pid;
};
and some of the watch::info bit could be used:
n->watch.info & WATCH_INFO_OVERRUN watch queue overran
n->watch.info & WATCH_INFO_LENGTH event_len
n->watch.info & WATCH_INFO_RECURSIVE FAN_EVENT_ON_CHILD
n->watch.info & WATCH_INFO_FLAG_0 FAN_*_PERM
n->watch.info & WATCH_INFO_FLAG_1 FAN_Q_OVERFLOW
n->watch.info & WATCH_INFO_FLAG_2 FAN_ON_DIR
n->subtype ffs(n->mask)
Ideally, I'd dispense with metadata_len, vers, reserved* and set the version
when setting the watch.
fanotify_watch(int watchfd, unsigned int flags, u64 *mask,
int dirfd, const char *pathname, unsigned int at_flags);
We might also want to extend the watch_filter to allow you to, say, filter on
the first __u64 after the watch member so that you could filter on specific
events:
struct watch_notification_type_filter {
__u32 type;
__u32 info_filter;
__u32 info_mask;
__u32 subtype_filter[8];
__u64 payload_mask[1];
__u64 payload_set[1];
};
So, in this case, it would require:
n->mask & wf->payload_mask[0] == wf->payload_set[0]
to be true to record the message.
David
^ permalink raw reply
* Re: [PATCH 00/58] LSM: Module stacking for AppArmor
From: Stephen Smalley @ 2019-06-04 12:29 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: keescook, john.johansen, penguin-kernel, paul
In-Reply-To: <20190602165101.25079-1-casey@schaufler-ca.com>
On 6/2/19 12:50 PM, Casey Schaufler wrote:
> This patchset provides the changes required for
> the AppArmor security module to stack safely with any other.
Please explain the motivation - why do we want to allow AppArmor to
stack with other modules, who would use it, how would it be used, what
does it provide that isn't already possible in the absence of it. Also,
Ubuntu fully upstreamed all of their changes to AppArmor, would this
still suffice to enable stacking of AppArmor or do they rely on hooks
that are not handled here?
Please explain the cost of the change - what do we pay in terms of
memory, runtime, or other overheads in order to support this change?
>
> A new process attribute identifies which security module
> information should be reported by SO_PEERSEC and the
> /proc/.../attr/current interface. This is provided by
> /proc/.../attr/display. Writing the name of the security
> module desired to this interface will set which LSM hooks
> will be called for this information. The first security
> module providing the hooks will be used by default.
Doesn't this effectively undo making the hooks read-only after init, at
least for the subset involved? What are the security implications thereof?
> The use of integer based security tokens (secids) is
> generally (but not completely) replaced by a structure
> lsm_export. The lsm_export structure can contain information
> for each of the security modules that export information
> outside the LSM layer.
>
> The LSM interfaces that provide "secctx" text strings
> have been changed to use a structure "lsm_context"
> instead of a pointer/length pair. In some cases the
> interfaces used a "char *" pointer and in others a
> "void *". This was necessary to ensure that the correct
> release mechanism for the text is used. It also makes
> many of the interfaces cleaner.
>
> https://github.com/cschaufler/lsm-stacking.git#stack-5.2-v1-apparmor
>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> ---
> drivers/android/binder.c | 25 ++-
> fs/kernfs/dir.c | 6 +-
> fs/kernfs/inode.c | 31 ++-
> fs/kernfs/kernfs-internal.h | 3 +-
> fs/nfs/inode.c | 13 +-
> fs/nfs/internal.h | 8 +-
> fs/nfs/nfs4proc.c | 17 +-
> fs/nfs/nfs4xdr.c | 16 +-
> fs/nfsd/nfs4proc.c | 8 +-
> fs/nfsd/nfs4xdr.c | 14 +-
> fs/nfsd/vfs.c | 7 +-
> fs/proc/base.c | 1 +
> include/linux/cred.h | 3 +-
> include/linux/lsm_hooks.h | 91 +++++----
> include/linux/nfs4.h | 8 +-
> include/linux/security.h | 133 +++++++++----
> include/net/af_unix.h | 2 +-
> include/net/netlabel.h | 10 +-
> include/net/scm.h | 14 +-
> kernel/audit.c | 43 ++--
> kernel/audit.h | 9 +-
> kernel/auditfilter.c | 6 +-
> kernel/auditsc.c | 77 ++++----
> kernel/cred.c | 15 +-
> net/ipv4/cipso_ipv4.c | 13 +-
> net/ipv4/ip_sockglue.c | 12 +-
> net/netfilter/nf_conntrack_netlink.c | 29 ++-
> net/netfilter/nf_conntrack_standalone.c | 16 +-
> net/netfilter/nfnetlink_queue.c | 38 ++--
> net/netfilter/nft_meta.c | 13 +-
> net/netfilter/xt_SECMARK.c | 14 +-
> net/netlabel/netlabel_kapi.c | 5 +-
> net/netlabel/netlabel_unlabeled.c | 101 +++++-----
> net/netlabel/netlabel_unlabeled.h | 2 +-
> net/netlabel/netlabel_user.c | 13 +-
> net/netlabel/netlabel_user.h | 2 +-
> net/unix/af_unix.c | 6 +-
> security/apparmor/audit.c | 4 +-
> security/apparmor/include/audit.h | 2 +-
> security/apparmor/include/net.h | 6 +-
> security/apparmor/include/secid.h | 9 +-
> security/apparmor/lsm.c | 64 +++---
> security/apparmor/secid.c | 42 ++--
> security/integrity/ima/ima.h | 14 +-
> security/integrity/ima/ima_api.c | 9 +-
> security/integrity/ima/ima_appraise.c | 6 +-
> security/integrity/ima/ima_main.c | 34 ++--
> security/integrity/ima/ima_policy.c | 19 +-
> security/security.c | 338 +++++++++++++++++++++++++++-----
> security/selinux/hooks.c | 259 ++++++++++++------------
> security/selinux/include/audit.h | 5 +-
> security/selinux/include/objsec.h | 42 +++-
> security/selinux/netlabel.c | 25 +--
> security/selinux/ss/services.c | 18 +-
> security/smack/smack.h | 18 ++
> security/smack/smack_lsm.c | 238 +++++++++++-----------
> security/smack/smack_netfilter.c | 8 +-
> security/smack/smackfs.c | 12 +-
> 58 files changed, 1217 insertions(+), 779 deletions(-)
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox