Snap and executable stacks


#1

Background

Occasionally programs run with the READ_IMPLIES_X personality bit set. When this bit is set, mmap() read access (PROT_READ) implies execute (PROT_EXEC) such that when the program does something like:

mmap(..., PROT_READ | PROT_WRITE, ...)

the kernel mediates the access as

mmap(..., PROT_READ | PROT_WRITE | PROT_EXEC, ...)

For snaps that run under strict confinement, this means that the ‘m’ permission is required for the access. From the apparmor.d(5) man page:

       m - Allow executable mapping
           This mode allows a file to be mapped into memory using mmap(2)'s
           PROT_EXEC flag. This flag marks the pages executable; it is used on
           some architectures to provide non-executable data pages, which can
           complicate exploit attempts. AppArmor uses this mode to limit which
           files a well-behaved program (or all programs on architectures that
           enforce non-executable memory access controls) may use as libraries, to
           limit the effect of invalid -L flags given to ld(1) and LD_PRELOAD,
           LD_LIBRARY_PATH, given to ld.so(8).

Well-behaved programs should normally run with ‘read’, ‘read and write’, or ‘read and exec’, but not ‘read, write and exec’ or ‘write and exec’. The READ_IMPLIES_X personality bit is problematic because it changes the requirements of the security permissions such that some write accesses also need exec and as a result, adding the ‘m’ permission to these writable files could substantially weaken the policy.

The READ_IMPLIES_X personality will be set if the program is run with an executable stack and programs with executable stacks are vulnerable to exploitation via stack memory. Most software these days (by far) does not require an executable stack and an executable stack is only needed in “a few very rare situations where executable stacks are actually desired, the rest are usually the result of lacking flags in assembly code or using nested functions (which are generally avoidable)”.

The linker will set the stack as executable if the executable or any library it links against has an executable stack. You can see which binaries have an executable stack like so:

readelf -lW ./has-execstack | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

(notice how ‘E’ is listed). A binary without this bit set looks like:

$ readelf -lW ./no-execstack | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

Alternatively, you can use the execstack program to query the binary:

$ execstack -q ./*execstack
X ./has-execstack
- ./no-execstack

Affect on snaps

In terms of snaps, READ_IMPLIES_X won’t trigger a security policy violation for files in the snap’s data directories, however, because the program has an executable stack it will (correctly) trigger a security violation for accesses outside of the snap depending on recent kernels. Snappy will not add ‘m’ everywhere for the very few snaps that have an executable stack since that would significantly weaken the security policy for all snaps. Because snaps with executable stacks are not expected to run under strict mode, the automated reviews in the Snap Store will trigger a warning if it detects binaries with executable stacks.

To fix the snap and allow it to pass automated reviews, snap developers should either:

  • fix the binaries to not require an executable stack
  • strip the executable stack bit on the affected binaries
  • remove the affected binary if it isn’t otherwise used

Because code that has an executable stack is vulnerable to stack memory attacks, fixing the code itself will harden the code everywhere it is run, Ie, it will meaningfully improve the code outside of snappy; it just so happens it will also make the ‘m’ denial go away and allow the snap to work in strict mode.

Some snaps may have binaries that have an executable stack but they operate fine without it (eg, the code doesn’t define a proper .note.GNU-stack section, was compiled with (very) old compilers, is using library that has the bit set but not the function that requires it, etc). In these cases the execstack program can be used to strip the executable stack bit:

$ execstack -q ./foo 
X ./foo
$ execstack --clear-execstack ./foo
$ execstack -q ./foo 
- ./foo

If the snap contains unused binaries with executable stacks (eg, via stage-packages packages), then simply removing those binaries from the snap would allow the snap to pass automated review, with the side-benefit of making the snap smaller in size.

For programs that legitimately require an executable stack when they otherwise work fine in strict mode, the review tools in the Snap Store will allow for overriding the warning and allow the snap to pass automated review.

Future

In general, fixing the program to not require an executable stack or clearing the executable stack bit is preferred since the program will not be vulnerable to certain stack attacks. In the future, AppArmor will have language along the lines of ‘allow read_implies_x’ which, when the personality bit is set, will add ‘x’ implicitly to any read rules. This is not a solution because it merely makes it easier to allow weakened, workaround policy.

If many snaps are found to require executable stacks, snapd may gain a new ‘execstack-support’ manually connected interface. This interface will initially add the specific ‘m’ accesses required by all the snaps needing it, but eventually use the ‘read_implies_x’ AppArmor rule. This ‘execstack-support’ interface could then be plugged by snaps that require it with snap declarations to allow using it. In this manner the security policy remains strong for the vast majority of applications that don’t need it and is weakened only for those that require it.

References


Electron snap killed when using app.makeSingleInstance API
"Could not find any font:" error on Solus & Fedora
AppArmor denial for /dev/nvidiactl
canvas.createContext("webgl") returns null in Electron
canvas.createContext("webgl") returns null in Electron
Fonts fail to load when `desktop` plug added
Manual Review Requested - Anubis
canvas.createContext("webgl") returns null in Electron
#2

Some people have found that they prefer to clear the execstack as part of the snapcraft build instead of fixing their code to not end up with an executable stack. While fixing the code is preferred and the most robust way to ensure proper behavior with no execstack, some may want to consider doing something similar to this snap.


#3

AIUI, newer versions of electron-builder will no longer generate files with an executable stack. Furthermore, snapcraft 2.40 clears files with execstack automatically (displaying a warning about what it is doing).