Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util/sockopt: add fallback for SELinux denial on SO_PEERPIDFD #343

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

bluca
Copy link
Contributor

@bluca bluca commented Feb 23, 2024

If SELinux is in enforcing and we get EACCES, return an error to skip using pidfds.
This can happen with an old policy and a new kernel that uses the new pidfs filesystem to back pidfds, instead of anonymous inodes, as the existing policy denies access.

If SELinux is in enforcing and we get EACCES, return an error
to skip using pidfds.
This can happen with an old policy and a new kernel that uses the
new pidfs filesystem to back pidfds, instead of anonymous inodes,
as the existing policy denies access.

Signed-off-by: Luca Boccassi <[email protected]>
Co-authored-by: Christian Brauner <[email protected]>
Copy link

@brauner brauner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@dvdhrm
Copy link
Member

dvdhrm commented Feb 24, 2024

Is this fixed in the selinux rules?

@bluca
Copy link
Contributor Author

bluca commented Feb 24, 2024

No, there are many policies around, I don't think any is fixed yet

@dvdhrm
Copy link
Member

dvdhrm commented Feb 24, 2024

Can you elaborate why this needs to be fixed in the code-base, rather than in the policy? I mean, this will not hit distributions any earlier than a fix of the policies. But a policy-fix will actually enable the feature, while this workaround will just disable it until another component (i.e. the policy) has been updated.

Also, it is very common for package updates to conflict with existing policies. It is common procedure to update the policies before pushing package updates to release branches. Is this different here?

Lastly, what do you mean with there are many policies around? The reference policy carries the required bits for dbus-broker. I am not aware of any deployment that does not base its policies on it. If there are any, please let us know so we can track them when updating the dbus policies.

Just for the record, the dbus (and dbus-broker) related poclies can be found here: https://github.com/SELinuxProject/refpolicy/tree/main/policy/modules/services

@bluca
Copy link
Contributor Author

bluca commented Feb 24, 2024

Redhat have their own policy that is disjoint from the upstream one. In MSFT we have our own policy as well, that sometimes intersecates but not always. That's at least three. I think Android has its own too - not that it would matter for this, but still that's four that I know of. and I don't really follow selinux that closely.

In general, with new syscalls or adjacent, there's always going to be LSMs that reject them. It's not just selinux, there's also seccomp that usually is deny-by-default. This new kernel filesystem triggers unexpected denials that you wouldn't know unless you are a kernel developers - an 'open' on a getsockopt() call is not that obvious. So given there's a clear fallback, it's better to use it instead of trying to foresee what the kernel might or might not return, as these things change all the time, and having your system suddenly fail to boot because there was a new kernel version is not a nice experience for a user, and having to suddenly go in fire fighting mode is not a nice experience for developers.

dbus-daemon doesn't have this issue, it falls back if the getsockopt is rejected without any problem.

brauner added a commit to brauner/linux that referenced this pull request Feb 24, 2024
Moving pidfds from the anonymous inode infrastructure to a separate tiny
in-kernel filesystem similar to sockfs, pipefs, and anon_inodefs causes
Selinux denials and thus various userspace components that make heavy
use of pidfds to fail if we would be using dentry_open() directly.
pidfds used anon_inode_getfile() which never was subject to any LSM
hooks. But dentry_open() is and that would cause regressions:

Feb 23 12:09:58 fed1 audit[353]: AVC avc:  denied  { read write open } for  pid=353 comm="systemd-userdbd" path="pidfd:[709]" dev="pidfs" ino=709 scontext=system_u:system_r:systemd_userdbd_t:>

The failures that are seen are selinux denials. But the core failure is
dbus-broker. That cascades into other services failing that depend on
dbus-broker. For example, when dbus-broker fails to start polkit and all
the others won't be able to work because they depend on dbus-broker.

The reason for dbus-broker failing is because it doesn't handle failures
for SO_PEERPIDFD correctly. Last kernel release (either v6.7 or v6.6,
I'm not completely sure right now) we introduced SO_PEERPIDFD (and
SCM_PIDFD). SO_PEERPIDFD allows dbus-broker and polkit and others to
receive a pidfd for the peer of an AF_UNIX socket. This is the first
time in the history of Linux that we can safely authenticate clients in
a race-free manner.

dbus-broker immediately made use of this but messed up the error
checking. It only allowed EINVAL as a valid failure for SO_PEERPIDFD.
That's obviously problematic not just because of LSM denials but because
of seccomp denials that would prevent SO_PEERPIDFD from working; or any
other new error code from there.

So this is catching a flawed implementation in dbus-broker as well. It
has to fallback to the old pid-based authentication when SO_PEERPIDFD
doesn't work no matter the reasons otherwise it'll always risk such
failures. So overall that LSM denial should not have caused dbus-broker
to fail. It can never assume that a feature released one kernel ago like
SO_PEERPIDFD can be assumed to be available.

So, the next fix separate from the selinux policy update is to fix
dbus-broker at [3]. That should make it into Fedora as well. In addition
the selinux reference policy should also be updated. See [4] for that.
If Selinux is in enforcing mode in userspace and it encounters anything
that it doesn't know about it will deny it by default. And the policy is
entirely in userspace including declaring new types for stuff like nsfs
or pidfs to allow it. There's just nothing to do in the kernel so we
can't use dentry_open(). So instead we use alloc_file(). Once selinux is
ready we can switch to dentry_open() or we introduce separate LSM hook
for pidfds.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=2265630 [1]
Link: fedora-selinux/selinux-policy#2050 [2]
Link: bus1/dbus-broker#343 [3]
Link: SELinuxProject/refpolicy#762 [4]
Reported-by: Nathan Chancellor <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/r/20240218-neufahrzeuge-brauhaus-fb0eb6459771@brauner
Signed-off-by: Christian Brauner <[email protected]>
brauner added a commit to brauner/linux that referenced this pull request Feb 25, 2024
Moving pidfds from the anonymous inode infrastructure to a separate tiny
in-kernel filesystem similar to sockfs, pipefs, and anon_inodefs causes
Selinux denials and thus various userspace components that make heavy
use of pidfds to fail if we would be using dentry_open() directly.
pidfds used anon_inode_getfile() which never was subject to any LSM
hooks. But dentry_open() is and that would cause regressions:

Feb 23 12:09:58 fed1 audit[353]: AVC avc:  denied  { read write open } for  pid=353 comm="systemd-userdbd" path="pidfd:[709]" dev="pidfs" ino=709 scontext=system_u:system_r:systemd_userdbd_t:>

The failures that are seen are selinux denials. But the core failure is
dbus-broker. That cascades into other services failing that depend on
dbus-broker. For example, when dbus-broker fails to start polkit and all
the others won't be able to work because they depend on dbus-broker.

The reason for dbus-broker failing is because it doesn't handle failures
for SO_PEERPIDFD correctly. Last kernel release (either v6.7 or v6.6,
I'm not completely sure right now) we introduced SO_PEERPIDFD (and
SCM_PIDFD). SO_PEERPIDFD allows dbus-broker and polkit and others to
receive a pidfd for the peer of an AF_UNIX socket. This is the first
time in the history of Linux that we can safely authenticate clients in
a race-free manner.

dbus-broker immediately made use of this but messed up the error
checking. It only allowed EINVAL as a valid failure for SO_PEERPIDFD.
That's obviously problematic not just because of LSM denials but because
of seccomp denials that would prevent SO_PEERPIDFD from working; or any
other new error code from there.

So this is catching a flawed implementation in dbus-broker as well. It
has to fallback to the old pid-based authentication when SO_PEERPIDFD
doesn't work no matter the reasons otherwise it'll always risk such
failures. So overall that LSM denial should not have caused dbus-broker
to fail. It can never assume that a feature released one kernel ago like
SO_PEERPIDFD can be assumed to be available.

So, the next fix separate from the selinux policy update is to fix
dbus-broker at [3]. That should make it into Fedora as well. In addition
the selinux reference policy should also be updated. See [4] for that.
If Selinux is in enforcing mode in userspace and it encounters anything
that it doesn't know about it will deny it by default. And the policy is
entirely in userspace including declaring new types for stuff like nsfs
or pidfs to allow it. There's just nothing to do in the kernel so we
can't use dentry_open(). So instead we use alloc_file(). Once selinux is
ready we can switch to dentry_open() or we introduce separate LSM hook
for pidfds.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=2265630 [1]
Link: fedora-selinux/selinux-policy#2050 [2]
Link: bus1/dbus-broker#343 [3]
Link: SELinuxProject/refpolicy#762 [4]
Reported-by: Nathan Chancellor <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/r/20240218-neufahrzeuge-brauhaus-fb0eb6459771@brauner
Signed-off-by: Christian Brauner <[email protected]>
@dvdhrm
Copy link
Member

dvdhrm commented Feb 26, 2024

Redhat have their own policy that is disjoint from the upstream one. In MSFT we have our own policy as well, that sometimes intersecates but not always. That's at least three. I think Android has its own too - not that it would matter for this, but still that's four that I know of. and I don't really follow selinux that closely.

Excellent! So lets start with selinux-refpolicy and fedora-selinux, and send a request to msft. With the policies fixed, we can switch the pidfd to use dentry_open() and everything will keep working.

In general, with new syscalls or adjacent, there's always going to be LSMs that reject them. It's not just selinux, there's also seccomp that usually is deny-by-default. This new kernel filesystem triggers unexpected denials that you wouldn't know unless you are a kernel developers - an 'open' on a getsockopt() call is not that obvious.

I will cheekily restrict this PR to a workaround for the referenced kernel changes. I am open to discuss support for systems that block specific syscalls via seccomp, yet I would prefer to discuss that separately (yes, a possible fix might involve checking for the same error-code, but their root cause is IMO very much different; but they very likely should not check for selinux enforcing mode).

So given there's a clear fallback, it's better to use it instead of trying to foresee what the kernel might or might not return, as these things change all the time[,...]

Imagine someone deploying a Fedora machine with linux-6.7 and dbus-broker-35. They use this as minimum required versions for their setup and deploy an application that relies on ProcessFD in GetConnectionCredentials(). They are very much allowed to do that, and they can rely on ProcessFD to be available and present. They control the setup, they are very much allowed to set high minimum requirements, and they can rely on released kernel features to not break. They can even put this into a device and sell it.

With the referenced kernel-update their setup will break, regardless whether the suggested PR is applied to dbus-broker or not. dbus-broker cannot report ProcessFD even with your PR applied. Hence, this kernel update is a breaking change. There is nothing dbus-broker can do to avoid this, and thus I very much disagree with your statement that there is a "clear fallback".

I see 2 options to avoid this breakage: 1) update SELinux policies and ensure they are synchronized with kernel updates (which very much relies on lenient update policies of the respective distributions) 2) ensure that no open()-LSM-hook is triggered by SO_PEERPIDFD.

Maybe there is no dbus client that relies on ProcessFD, maybe there is. But we as bus maintainers will protect the APIs we grant to our clients and ensure that they do not break. And that is why we do not have default-fallbacks to disable features at runtime. We believe this hides bugs. And this proposed kernel change could have broken D-Bus clients, if we hadn't refused to disable features at runtime.

[...] and having your system suddenly fail to boot because there was a new kernel version is not a nice experience for a user, and having to suddenly go in fire fighting mode is not a nice experience for developers.

I don't understand. The linux kernel famously has one important rule: "You do not break existing user-space." And history has shown that (almost) anything violating this rule will get rigorously reverted. And this isn't even a niche issue hard to trigger. No. You cannot boot the most recent Fedora version with this proposed kernel update, hence if this finds its way into a released kernel it would be a careless neglect of testing practices.

No-one has to go into "fire fighting mode" for this change other than kernel developers.

dbus-daemon doesn't have this issue, it falls back if the getsockopt is rejected without any problem.

dbus-daemon possibly breaks existing clients silently without telling anyone. Yes, if no such client existed, they would get away with it and thus make life easier for breaking kernel changes (which is a very valid thing to do, we just happen to choose a different route). However, if such a client existed, they would put all the burden of ensuring the kernel does not break existing code on those clients, which might receive much much less exposure and testing and not notice breakage until it is far too late.

@brauner
Copy link

brauner commented Feb 26, 2024 via email

@dvdhrm

This comment was marked as off-topic.

@brauner
Copy link

brauner commented Feb 26, 2024 via email

@dvdhrm
Copy link
Member

dvdhrm commented Feb 27, 2024

Imagine someone deploying a Fedora container and using dbus-broker with a standard seccomp profile forbidding ProcessFD in GetConnectionCredentials() or an LSM restricting access to it in the future.

You mean blocking the required syscalls to implement ProcessFD, right? I am not aware of an LSM that allows blocking based on entries in GetConnectionCredentials(), but only based on transaction metadata.

Not that it changes the setup, just wondering.

Happens today. In your case dbus-broker will refuse to be usable on permission error and cause cascading failures for other services. I would rather use a fall back even on permission error right now. And then, if you're really serious about not falling back on errors other than this thing not being available go all the way and simply remove the non-SO_PEERPIDFD code completely in a few years.

Right. As said before, if such setups exist, tell us about them, explain their policy on rule-updates, and who deploys them, and we can talk about supporting it. So far, no-one has reported breakage in the past, and we have continuously made use of new socketops in a similar fashion. As far as I understand, your report is also theoretical and you are not aware of a system that broke, right?

(Note that I do not think the breakage that would have been caused by the proposed kernel changes applies here, as it would have broken D-Bus clients anyway.)

Additionally, I would strongly prefer to upstream any seccomp-filters to the service files provided by us, rather than applying them downstream. This allows keeping them in-sync with updates and avoiding conflicts. Such attempts are currently ongoing in Fedora, btw (https://fedoraproject.org/wiki/Changes/SystemdSecurityHardening).

Lastly, if downstream intends to apply system-wide seccomp-filters (or other restrictions) which cannot take into account service-individual requirements, then I would very much like to know why they insist on updating the software they run, but refuse to update the filters to the requirements of that software. The industry maintains a huge effort to provide stable releases in RHEL and friends for decades. Lets use it! If you run git-main, then ensure git-main requirements.

Finally, note that we have deviated from that (I grant you) rather philosophical argument many times in the past. But on good grounds based on actual deployments. In this case, in particular, I would want to know what the policy for seccomp-filter updates is, how we can track this, and most importantly how we can annotate this so it does not accidentally break. I want this tested in our CI, before I make any promises.

brauner added a commit to brauner/linux that referenced this pull request Feb 27, 2024
Moving pidfds from the anonymous inode infrastructure to a separate tiny
in-kernel filesystem similar to sockfs, pipefs, and anon_inodefs causes
Selinux denials and thus various userspace components that make heavy
use of pidfds to fail if we would be using dentry_open() directly.
pidfds used anon_inode_getfile() which never was subject to any LSM
hooks. But dentry_open() is and that would cause regressions:

Feb 23 12:09:58 fed1 audit[353]: AVC avc:  denied  { read write open } for  pid=353 comm="systemd-userdbd" path="pidfd:[709]" dev="pidfs" ino=709 scontext=system_u:system_r:systemd_userdbd_t:>

The failures that are seen are selinux denials. But the core failure is
dbus-broker. That cascades into other services failing that depend on
dbus-broker. For example, when dbus-broker fails to start polkit and all
the others won't be able to work because they depend on dbus-broker.

The reason for dbus-broker failing is because it doesn't handle failures
for SO_PEERPIDFD correctly. Last kernel release (either v6.7 or v6.6,
I'm not completely sure right now) we introduced SO_PEERPIDFD (and
SCM_PIDFD). SO_PEERPIDFD allows dbus-broker and polkit and others to
receive a pidfd for the peer of an AF_UNIX socket. This is the first
time in the history of Linux that we can safely authenticate clients in
a race-free manner.

dbus-broker immediately made use of this but messed up the error
checking. It only allowed EINVAL as a valid failure for SO_PEERPIDFD.
That's obviously problematic not just because of LSM denials but because
of seccomp denials that would prevent SO_PEERPIDFD from working; or any
other new error code from there.

So this is catching a flawed implementation in dbus-broker as well. It
has to fallback to the old pid-based authentication when SO_PEERPIDFD
doesn't work no matter the reasons otherwise it'll always risk such
failures. So overall that LSM denial should not have caused dbus-broker
to fail. It can never assume that a feature released one kernel ago like
SO_PEERPIDFD can be assumed to be available.

So, the next fix separate from the selinux policy update is to fix
dbus-broker at [3]. That should make it into Fedora as well. In addition
the selinux reference policy should also be updated. See [4] for that.
If Selinux is in enforcing mode in userspace and it encounters anything
that it doesn't know about it will deny it by default. And the policy is
entirely in userspace including declaring new types for stuff like nsfs
or pidfs to allow it. So for now we continue to raise S_PRIVATE on the
inode if it's a pidfs inode.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=2265630 [1]
Link: fedora-selinux/selinux-policy#2050 [2]
Link: bus1/dbus-broker#343 [3]
Link: SELinuxProject/refpolicy#762 [4]
Reported-by: Nathan Chancellor <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/r/20240218-neufahrzeuge-brauhaus-fb0eb6459771@brauner
Signed-off-by: Christian Brauner <[email protected]>
brauner added a commit to brauner/linux that referenced this pull request Feb 28, 2024
Moving pidfds from the anonymous inode infrastructure to a separate tiny
in-kernel filesystem similar to sockfs, pipefs, and anon_inodefs causes
selinux denials and thus various userspace components that make heavy
use of pidfds to fail as pidfds used anon_inode_getfile() which aren't
subject to any LSM hooks. But dentry_open() is and that would cause
regressions.

The failures that are seen are selinux denials. But the core failure is
dbus-broker. That cascades into other services failing that depend on
dbus-broker. For example, when dbus-broker fails to start polkit and all
the others won't be able to work because they depend on dbus-broker.

The reason for dbus-broker failing is because it doesn't handle failures
for SO_PEERPIDFD correctly. Last kernel release we introduced
SO_PEERPIDFD (and SCM_PIDFD). SO_PEERPIDFD allows dbus-broker and polkit
and others to receive a pidfd for the peer of an AF_UNIX socket. This is
the first time in the history of Linux that we can safely authenticate
clients in a race-free manner.

dbus-broker immediately made use of this but messed up the error
checking. It only allowed EINVAL as a valid failure for SO_PEERPIDFD.
That's obviously problematic not just because of LSM denials but because
of seccomp denials that would prevent SO_PEERPIDFD from working; or any
other new error code from there.

So this is catching a flawed implementation in dbus-broker as well. It
has to fallback to the old pid-based authentication when SO_PEERPIDFD
doesn't work no matter the reasons otherwise it'll always risk such
failures. So overall that LSM denial should not have caused dbus-broker
to fail. It can never assume that a feature released one kernel ago like
SO_PEERPIDFD can be assumed to be available.

So, the next fix separate from the selinux policy update is to try and
fix dbus-broker at [3]. That should make it into Fedora as well. In
addition the selinux reference policy should also be updated. See [4]
for that. If Selinux is in enforcing mode in userspace and it encounters
anything that it doesn't know about it will deny it by default. And the
policy is entirely in userspace including declaring new types for stuff
like nsfs or pidfs to allow it.

For now we continue to raise S_PRIVATE on the inode if it's a pidfs
inode which means things behave exactly like before.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=2265630
Link: fedora-selinux/selinux-policy#2050
Link: bus1/dbus-broker#343 [3]
Link: SELinuxProject/refpolicy#762 [4]
Reported-by: Nathan Chancellor <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/r/20240218-neufahrzeuge-brauhaus-fb0eb6459771@brauner
Signed-off-by: Christian Brauner <[email protected]>
brauner added a commit to brauner/linux that referenced this pull request Mar 1, 2024
Moving pidfds from the anonymous inode infrastructure to a separate tiny
in-kernel filesystem similar to sockfs, pipefs, and anon_inodefs causes
selinux denials and thus various userspace components that make heavy
use of pidfds to fail as pidfds used anon_inode_getfile() which aren't
subject to any LSM hooks. But dentry_open() is and that would cause
regressions.

The failures that are seen are selinux denials. But the core failure is
dbus-broker. That cascades into other services failing that depend on
dbus-broker. For example, when dbus-broker fails to start polkit and all
the others won't be able to work because they depend on dbus-broker.

The reason for dbus-broker failing is because it doesn't handle failures
for SO_PEERPIDFD correctly. Last kernel release we introduced
SO_PEERPIDFD (and SCM_PIDFD). SO_PEERPIDFD allows dbus-broker and polkit
and others to receive a pidfd for the peer of an AF_UNIX socket. This is
the first time in the history of Linux that we can safely authenticate
clients in a race-free manner.

dbus-broker immediately made use of this but messed up the error
checking. It only allowed EINVAL as a valid failure for SO_PEERPIDFD.
That's obviously problematic not just because of LSM denials but because
of seccomp denials that would prevent SO_PEERPIDFD from working; or any
other new error code from there.

So this is catching a flawed implementation in dbus-broker as well. It
has to fallback to the old pid-based authentication when SO_PEERPIDFD
doesn't work no matter the reasons otherwise it'll always risk such
failures. So overall that LSM denial should not have caused dbus-broker
to fail. It can never assume that a feature released one kernel ago like
SO_PEERPIDFD can be assumed to be available.

So, the next fix separate from the selinux policy update is to try and
fix dbus-broker at [3]. That should make it into Fedora as well. In
addition the selinux reference policy should also be updated. See [4]
for that. If Selinux is in enforcing mode in userspace and it encounters
anything that it doesn't know about it will deny it by default. And the
policy is entirely in userspace including declaring new types for stuff
like nsfs or pidfs to allow it.

For now we continue to raise S_PRIVATE on the inode if it's a pidfs
inode which means things behave exactly like before.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=2265630
Link: fedora-selinux/selinux-policy#2050
Link: bus1/dbus-broker#343 [3]
Link: SELinuxProject/refpolicy#762 [4]
Reported-by: Nathan Chancellor <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/r/20240218-neufahrzeuge-brauhaus-fb0eb6459771@brauner
Signed-off-by: Christian Brauner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants