Zack's Kernel News
Zack's Kernel News
Chronicler Zack Brown reports on the latest news, views, dilemmas, and developments within the Linux kernel community.
Bogus GCC Warnings Reveal Real Kernel Problem
Linus Torvalds recently took great pains to explain why even though a particular GCC warning resulted in identifying a true problem with the kernel code, the warning itself was nevertheless bogus and insane. In fact, he said, most of the warnings introduced in GCC 5.1 didn't seem so valuable to him.
The one that had caught his attention, though, was a warning against using a switch
statement with a Boolean variable. Presumably, the GCC folks thought that the whole point of switch
was to operate on several possible values using a simple coding structure, and thus avoid long if
/else
/if
chains. They may have thought that for a Boolean variable, a simple if
statement would be the only appropriate way to go.
In the case of this particular bit of kernel code, said Linus, the warning was good because the switch
statement in the kernel used a Boolean variable, whereas the case
statements did not. He felt that this would be a perfectly legitimate thing for GCC to issue warnings about. But, if the switch
and case
statements both used a Boolean variable, he said, it would be a perfectly fine use case, and might even be better than the equivalent if
/else
in some cases:
switch (boolean) { case true: ... case false: }
Kirill A. Shutemov looked at that snippet and asked for some kind of example of when it could be better than a straightforward if
statement. Linus replied:
"With switch()
, you can write things like
switch (a) { case 1: A; if (b) break; B; /* fallthrough */ case 2: C; }
"where you share that C
case for some conditions but not others. You can do the same using goto
, of course, but you can *not* do it with pure nested if ()
statements. So even with just two cases, switch ()
is syntactically more powerful than if ()
because it allows more structured exits. So anybody who says that 'booleans don't make sense for switch()
' is just crazy."
He added:
"… the new warning in gcc-5 seems to be just stupid. In general, warnings that encourage you to write bad code are stupid. The above
switch (boolean) { case true:
"is *good* code, while the gcc documentation suggests that you should cast it to int
in order to avoid the warning, but anybody who actually thinks that
switch ((int)boolean) { case 1:
"is better, clearly has absolutely zero taste and is just objectively wrong. Really. A warning where the very *documentation* tells you to do stupid things is stupid."
New Public Key Crypto API
Tadeusz Struk posted some patches introducing a new public key encryption API. The code would allow drivers to provide their own RSA (Rivest, Shamir, and Adleman) and DSA (Digital Signature Algorithm) implementations. The kernel would also be able to offload encryption calculations to specialized hardware, if any were available. One of the main purposes of Tadeusz's code would be to verify digitally signed kernel modules.
Tadeusz also submitted patches converting relevant portions of the kernel from the old public key API to his new code.
Various folks had technical suggestions and a couple of objections. For instance, Tadeusz's code allowed any third-party RSA and DSA implementations to specify, in the form of a data structure, the exact set of features they implemented. That way, user code could check programatically for the features they needed. But, Herbert Xu argued that this would be a nightmare for users having to deal with multiple implementations, each of which might implement a different set of features. He suggested instead that Tadeusz require all RSA and DSA implementations to provide a standard set of features with predictable fallbacks. Otherwise, every user would have to reimplement the same complex set of tests.
David Howells asked what the implementations should do for cases where a fallback didn't exit. For example, he said, "a H/W contained key is specifically limited to, say, just sign/verify and the[n] not permitted to be used for encrypt/decrypt. How do you provide a fallback given you can't get at the key?" But Herbert replied, "That's a transform with a specific key. I don't see why such a piece of hardware would even need to be exposed through the crypto API which is about generic implementations that can take any arbitrary key."
David asked, "So what if we want to use a key that's stored in a TPM? I presume then we can't use the crypto interface, but must rather use the *key* as the primary interface somehow." Herbert explained, however, that the TPM (Trusted Platform Module) hardware chip would never be the only option, because Tadeusz's code was intended to provide a generic software interface, so even if it could be overridden by a TPM chip, it would never be necessary to do so. As Herbert put it, "anything that cannot be done purely in software does not fall under our purview."
Fixing Capability Inheritance (Sort Of)
Andy Lutomirski posted some patches based on ideas from Christoph Lameter and Serge Hallyn, to make some significant changes to filesystem capabilities. For starters, Andy gave a really cool summary of the current state of capabilities:
"On Linux, there are a number of capabilities defined by the kernel. To perform various privileged tasks, processes can wield capabilities that they hold.
"Each task has four capability masks: effective (pE
), permitted (pP
), inheritable (pI
), and a bounding set (X
). When the kernel checks for a capability, it checks pE
. The other capability masks serve to modify what capabilities can be in pE
.
"Any task can remove capabilities from pE
, pP
, or pI
at any time. If a task has a capability in pP
, it can add that capability to pE
and/or pI
. If a task has CAP_SETPCAP
, then it can add any capability to pI
, and it can remove capabilities from X
.
"Tasks are not the only things that can have capabilities; files can also have capabilities. A file can have no capability information at all. If a file has capability information, then it has a permitted mask (fP
) and an inheritable mask (fI
) as well as a single effective bit (fE
). File capabilities modify the capabilities of tasks that execve(2)
them.
"A task that successfully calls execve
has its capabilities modified for the file ultimately being executed (i.e., the binary itself if that binary is ELF or for the interpreter if the binary is a script.) In the capability evolution rules, for each mask Z, p
Z represents the old value and p
Z'
represents the new value. The rules are:
pP' = (X & fP) | (pI & fI) pI' = pI pE' = (fE ? pP' : 0) X is unchanged
"For setuid
binaries, fP
, fI
, and fE
are modified by a moderately complicated set of rules that emulate POSIX behavior. Similarly, if euid == 0
or ruid == 0
, then fP
, fI
, and fE
are modified differently (primary, fP
and fI
usually end up being the full set). For non-root users executing binaries with neither setuid nor file caps, fI
and fP
are empty and fE
is false.
"As an extra complication, if you execute a process as nonroot and fE
is set, then the 'secure exec' rules are in effect: AT_SECURE
gets set, LD_PRELOAD
doesn't work, etc.
"This is rather messy. We've learned that making any changes is dangerous, though: if a new kernel version allows an unprivileged program to change its security state in a way that persists cross execution of a setuid program or a program with file caps, this persistent state is surprisingly likely to allow setuid or file-capped programs to be exploited for privilege escalation."
Andy went on to describe the specific situation that his code was meant to solve:
"Capability inheritance is basically useless. If you aren't root and you execute an ordinary binary, fI
is zero, so your capabilities have no effect whatsoever on pP'
. This means that you can't usefully execute a helper process or a shell command with elevated capabilities if you aren't root.
"On current kernels, you can sort of work around this by setting fI
to the full set for most or all non-setuid executable files. This causes pP' = pI
for nonroot, and inheritance works. No one does this because it's a PITA and it isn't even supported on most filesystems.
"If you try this, you'll discover that every nonroot program ends up with secure exec rules, breaking many things. This is a problem that has bitten many people who have tried to use capabilities for anything useful."
Andy's code, he explained, added a fifth capability mask to go along with pE
, pP
, pI
, and X
. The new mask, called the ambient mask (pA
), was intended to provide a better form of inheritance than the original inheritance mask (pI
).
First of all, he said, "no bit can ever be set in pA
if it is not set in both pP
and pI
. Dropping a bit from pP
or pI
drops that bit from pA
. This ensures that existing programs that try to drop capabilities still do so."
Andy's code also introduced a new nuance: using setresuid
to switch from a root user ID to a non-root user ID, would clear all bits from pA
. So, any process that didn't like that would have to re-add the desired capabilities to pA
afterward by hand.
The evolution rules for capability inheritance were also changed:
pA' = <file caps or setuid or setgid> ? 0 : pA pP' = (X & fP) | (pI & fI) | pA' pI' = pI pE' = (fE ? pP' : pA') X <is unchanged>
And what was gained by these insane shenanigans? Andy listed off the benefits for real capability inheritance:
"If you are nonroot but you have a capability, you can add it to pA
. If you do so, your children get that capability in pA
, pP
, and pE
. For example, you can set pA = CAP_NET_BIND_SERVICE
, and your children can automatically bind low-numbered ports. Hallelujah!
"Unprivileged users can create user namespaces, map themselves to a nonzero uid, and create both privileged (relative to their namespace) and unprivileged process trees. This is currently more or less impossible. Hallelujah!
"You cannot use pA
to try to subvert a setuid, setgid, or file-capped program: if you execute any such program, pA
gets cleared and the resulting evolution rules are unchanged by this patch.
"Users with nonzero pA
are unlikely to unintentionally leak that capability. If they run programs that try to drop privileges, dropping privileges will still work."
As Andy put it, the behavior of pA
is what pI
should have been from the beginning. One of the difficulties of fixing capability behavior in Linux has been the need to keep existing user code working as expected. Another difficulty has been the fact that capabilities never actually became part of POSIX, so the best description available is just from a proposal.
Regardless, Andy's code addressed the problem in ways that offered real use cases in the immediate present. As Kees Cook said, "This would be quite welcome for things we're doing in Chrome OS. Presently, we're able to use fscaps
to keep non-root caps across exec and haven't encountered issues with AT_SECURE
(yet), but using pA
would be much nicer and exactly matches how we want to use it: a launcher is creating a tree of processes that are non-root but need some capabilities. Right now the tree is very small and we're able to sprinkle our fscaps lightly. :) This would be better."
There was a big discussion surrounding the features that Andy's code would provide, the security issues it might expose, the bad features it might fail to fix, and desired features it might fail to provide.
Part of the problem with capabilities, as also came out in the discussion, is that a full and total fix that makes everything wonderful might simply not be available, at least not in a direct A-to-B kind of way. Andy's approach didn't solve everything, and it had its own collection of flaws, but it created something that various users said could be useful. That may be the best available option, for now.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Linux Servers Targeted by Akira Ransomware
A group of bad actors who have already extorted $42 million have their sights set on the Linux platform.
-
TUXEDO Computers Unveils Linux Laptop Featuring AMD Ryzen CPU
This latest release is the first laptop to include the new CPU from Ryzen and Linux preinstalled.
-
XZ Gets the All-Clear
The back door xz vulnerability has been officially reverted for Fedora 40 and versions 38 and 39 were never affected.
-
Canonical Collaborates with Qualcomm on New Venture
This new joint effort is geared toward bringing Ubuntu and Ubuntu Core to Qualcomm-powered devices.
-
Kodi 21.0 Open-Source Entertainment Hub Released
After a year of development, the award-winning Kodi cross-platform, media center software is now available with many new additions and improvements.
-
Linux Usage Increases in Two Key Areas
If market share is your thing, you'll be happy to know that Linux is on the rise in two areas that, if they keep climbing, could have serious meaning for Linux's future.
-
Vulnerability Discovered in xz Libraries
An urgent alert for Fedora 40 has been posted and users should pay attention.
-
Canonical Bumps LTS Support to 12 years
If you're worried that your Ubuntu LTS release won't be supported long enough to last, Canonical has a surprise for you in the form of 12 years of security coverage.
-
Fedora 40 Beta Released Soon
With the official release of Fedora 40 coming in April, it's almost time to download the beta and see what's new.
-
New Pentesting Distribution to Compete with Kali Linux
SnoopGod is now available for your testing needs