state of classic confinement

7 minute read Published:

The next step of the first step into snaps

Classic?

Before we can discuss today’s topic I need to untangle a rather confusing concept. Snapd cares about naming a lot. We really try to do our best to name things in a consistent and clear way. We don’t like to create error messages from hell and force the user to google crap to understand what the computer just said.

Well, we didn’t get one thing right though: the term classic, currently, may refer to:

  • classic distribution that uses an existing package system (debs/rpms) in addition to snaps
  • classic snap that one can install on Ubuntu core (which is not a classic distribution as it uses snaps exclusively) to get classic Ubuntu with apt-get and writable-anywhere file system
  • classic confinement that a snap can request to effectively have no confinement at all (like in a classic distribution)

Today we’re talking about that last thing.

About classic confinement

Classic confinement is a very simple idea. Move your application into a snap easier than before by taking one element out of the equation. Disable confinement and file system redirection entirely to get started faster (and by get started, I mean get started with making a snap).

This has the advantage of still having access to snap features such as channels and a way to update the software easily and has some sense of security through the assertion system that at ensures that we know where the software is coming from. This alone makes it better than curl | sh -c that many upstream projects recommend as their official installation instructions. Even if someone goes rogue / malicious and betrays our initial trust, we can smite such offenders (or the offending package revisions) and pull the snap from the store as fast as possible in order to limit damage.

Digression: We (the snapd team) are paranoid about security and even when the system is no longer shielded from the application the store has a strong policy for well-behaved, well-known FOSS projects as the only allowed providers of snaps using classic confinement. In many ways this is similar to what you get with regular Debian packages (most of them are not using confinement and even if they do, they must do so voluntarily) except for the all-stop freeze-the-archive periods and a way to reuse dependencies from the distribution.

Your application can rely on the core snap and on whatever it brings by itself and is free to try to use anything on the classic distribution file system but if it does then all bets are off.

So far so good. I personally think that it is a great way to deliver developer tools like compilers or interpreters or system administration tools. Anytime where you want them to operate on your system as it really exists and not be stuck in some hidden complex machinery that ensures uniformity across diverse systems.

The idea is pretty good but as things stand today it is not that easy to use.

Technical problem

Technically when snap-confine helps to run an application using classic confinement it behaves in a special way. It no longer enables apparmor, apply a seccomp profile, do any tricks with mount name spaces or bind mounts. It is just a pass-through to your application.

Building classic-confinement snaps with snapcraft was (and I think, still is) a bit bumpy. When we initially discussed this problem we realized that without control of the mount name space we must rely on building the software in a way that would force it to use resources from the core snap before it tried to look at the likes of /lib and /usr which may contain entirely incompatible stuff and lead to crashes.

We set a few simple assumptions here:

  • the executable must use the dynamic linker from the core snap
  • the executable must be linked to the libraries from the core snap or from its own snap
  • the executable must be built in a way that lets it find resource files in its own snap.

With those three assumptions you and the bag of predictable low-level libraries in the core snap you can run your software on pretty much any Linux distribution.

All you need to do is to provide a few build-time configuration options, a few switches to the linker and you’re done. This means that you cannot easily reuse pre-build software. Why? Because it would have none of the magic that makes it do the three things I just listed.

People tried and ran into this issue pretty quickly. If you’re familiar with how things work at that level and you feel comfortable with building your software from source (and you should be if you want to build a snap, after all, it is your software, snaps empower deveopers to ship software) you could build the software from source, with just the right set of options in order to avoid it but it doesn’t feel like a very good and easy story.

If classic confinement was not useful as an entry into the world of snaps it makes little sense to spend time and effort on. We started looking for options and after a few discussions ran into this idea.

Plan B

Since launchers that allow you to run any snap application are managed by snapd, we can use them to redirect the dynamic linker on unmodified binaries.

Technically we can make the snap run -> snap-confine -> snap-exec chain do what we want. At the very last stage snap-exec, instead of just running the application, we can run the dynamic linker with command line option to change the search path to just the core snap and the application’s own snap. Then, for as long as the application doesn’t need to run extra executables from the core snap or from its own snap, things should generally work.

There will still be rough edges. Pre-compiled software will still load data from /usr/share, look at configuration files in /etc (possibly this is not a problem but it depends on fine details). The most serious problem is with applications that internally run other programs. If they attempt to run a program from their own snap (which is perfectly reasonable thing for a snap to do) two things will happen:

  • the kernel will resolve the dynamic linker to use
  • the linker will resolve the dynamic libraries to link

Both of those will not be the carefully overridden values we need to provide to make things work. What is even worse is that because we don’t want to pollute the environment with variables such as LD_LIBRARY_PATH or PATH (as that would mostly prevent applications from the classic distribution from working) the child will no longer be able to run anything from the snappy world without having to pass through the generated application launchers.

Plan B+

One way of tackling that problem would be to enumerate all the executables in the core snap and in the snap that is being started and generate internal launchers for all the executables seen there. Then a directory with all such launchers could be placed on PATH so that they would be found when searched for. The launchers would just ensure the linker switch happens when it is required.

Still this is not perfect. What if an application happens to try to run an executable with the full path? Say /snap/core/current/bin/true. Then we are back to square one and all the problems resurface.

Plan B++

One, perhaps somewhat invasive, option would be to bind mount the shadow launchers over all the executables in the core snap and in all the snaps that are using classic confinement. Then there would be no way to run anything without going through a part of snapd that makes the magic happen.

I think this would be a bit messy though. The output of mount might make a sysadmin raise an eye-brow or reconsider to move to the countryside and herd sheep.

What is the price that needs to be paid to maintain the illusion of seamless access to any pre-compiled application binary running on any Linux distribution?

Summary

It is possible to create snaps that use classic confinement and they will work correctly across a wide variety of environments but the process is not as easy as we would like.

If you build your software from source use: --prefix=/snap/$SNAP_NAME/current/usr and re-locate the code so that on install only the usr directory is present in the snap. Using snapcraft, or directly, use the right dynamic linker (-Wl,--dynamic-linker=...). Use the libraries from the core snap using rpath (-Wl,rpath,...) and things will just work.

As soon as we make the changes you can drop the last two elements and perhaps even switch to pre-build software from another source. I will post an update when this happens. All the existing snaps using classic confinement will work as before but new builds can then drop the extra complexity.

comments powered by Disqus