Whitespace in Sudo Commands

While configuring Buildbot to build Debian packages via pbuilder, I needed some way to give the buildworker user permission to run pbuilder build as root. pbuilder requires root in order to unpack (and remove) the build directory, set up a separate network namespace, and use the chroot(2) system call when actually invoking the build.

The most commonly used command in this situation is sudo, but it has some annoying shortcomings that I discovered while getting it to work with pbuilder.

Initial setup

First of all, we need to obtain the command that pbuilder is actually trying to run as root. By default it uses sudo, so all we have to do is attempt a build, and examine the system log with journalctl -p err -e -o cat SYSLOG_IDENTIFIER=sudo:

So far, so good. We can start by placing this command into the sudoers file:

We've already but up a Sudo limitation: it has no support for regular expressions, so simple wildcard matching is the only way to make one rule match multiple commands. However, wildcard matching is extremely dangerous in sudo so I never use it. Instead, I decided to duplicate the above user specification for each package built. It's not ideal, but I only have a few to deal with so it's not the end of the world.

The problem

I found that sudo continued to deny the buildworker user permission to run the command, logging buildworker : command not allowed ;.

After double-checking that I hadn't made any typos, I configured sudo debug logging and made the following discovery:

This log message shows both the command that the user tried to run, and the specification from the sudoers file against which it is being matched. Examining them closely, we can see the difference:

Whitespace issues? That's odd, I would have expected sudo to compare each member of the user-provided argv against the result of splitting the command specification using the same rules as the shell. However, a quick look at match.d shows us that the user-provided command is passed as a plain string:

Ugh. Examining pbuilder's source code shows us that the empty arguments in the command it wants to run as root are unavoidable:

This means that my original command spec would never have worked, because I did not indicate the presence of empty entries in the argument list when I wrote the spec; nor is there any way to specify the presence or absence of empty arguments within an argument in a command specification.

Wrapper script as a solution

I created a wrapper script which buildworker is allowed to run with any arguments:

The script checks the user-provided arguments against a list. Since the script can only be modified by root, it's safe to do whatever checks we want before executing the user-provided command.

This allows for a couple of extra enhancements:

  1. Regular expressions can be used to allow arguments to match files only within the build directory
  2. We can run the user-provided command via cgexec which allows the firewall to recognize packets from the pbuilder chroot.

Finally we have to tell pbuilder to use this script when it wants to run something as root, rather than its default of sudo -E. This is done by running pdebuild with the following arguments:

Other alternatives

I've been meaning to investigate other alternatives to Sudo for a while.


robots.org.uk: WhitespaceInSudoCommands (last edited 2017-03-15 13:55:12 by sam)

© Sam Morris <sam@robots.org.uk>.
Content may be distributed and modified providing this notice is preserved.