Zack's Kernel News

Zack's Kernel News

Article from Issue 177/2015
Author(s):

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, pZ represents the old value and pZ' 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

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Core Technologies

    Everyone wants to be root, because root can do anything. But in fact, its powers are now split. Learn more in this overview of capability sets.

  • Security Lessons: Capabilities

    Granting root access, even temporarily, is rife with danger. Capabilities could help.

  • Kernel News

    This month in Kernel News: Git Merge "Simplification" Advice; Loading Modules from Containers; Git Tree Synchronicity; and The New "No New Warnings" Warning.

  • Kernel News

    Chronicler Zack Brown reports on the latest news, views, dilemmas, and developments within the Linux kernel community.

  • Kernel: New Maintainer for x86 Branch

    Back at the Kernel Summit in September Andi Kleen announced that he would no longer be maintaining the i386 and x86_64 branches if they were merged in the new x86 branch. A new patch shows that Kleen has kept his promise.

comments powered by Disqus
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.

Learn More

News