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
-
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
-
Juno Computers Launches Another Linux Laptop
If you're looking for a powerhouse laptop that runs Ubuntu, the Juno Computers Neptune 17 v6 should be on your radar.
-
ZorinOS 17.1 Released, Includes Improved Windows App Support
If you need or desire to run Windows applications on Linux, there's one distribution intent on making that easier for you and its new release further improves that feature.
-
Linux Market Share Surpasses 4% for the First Time
Look out Windows and macOS, Linux is on the rise and has even topped ChromeOS to become the fourth most widely used OS around the globe.
-
KDE’s Plasma 6 Officially Available
KDE’s Plasma 6.0 "Megarelease" has happened, and it's brimming with new features, polish, and performance.
-
Latest Version of Tails Unleashed
Tails 6.0 is based on Debian 12 and includes GNOME 43.
-
KDE Announces New Slimbook V with Plenty of Power and KDE’s Plasma 6
If you're a fan of KDE Plasma, you'll be thrilled to hear they've announced a new Slimbook with an AMD CPU and the latest version of KDE Plasma desktop.
-
Monthly Sponsorship Includes Early Access to elementary OS 8
If you want to get a glimpse of what's in the pipeline for elementary OS 8, just set up a monthly sponsorship to help fund its continued existence.
-
DebConf24 to be Held in South Korea
Busan will be the location of the latest DebConf running July 28 through August 4