runc: Cannot run apt-get update in rootless container with sufficient capabilities

Similar to https://github.com/opencontainers/runc/issues/1860. I can’t seem to get a rootless ubuntu:18.04 container to run apt-get update, even with CAP_SYS_ADMIN.

$ mkdir rootfs
$ docker export $(docker create ubuntu:18.04) | tar -C rootfs -xf -
$ runc --version
runc version 1.0.0-rc91
spec: 1.0.2-dev
$ cat config.json
{
	"ociVersion": "1.0.2-dev",
	"process": {
		"terminal": false,
		"user": {
			"uid": 0,
			"gid": 0
		},
		"args": [
			"apt-get",
			"update"
		],
		"env": [
			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
		],
		"cwd": "/",
		"capabilities": {
			"bounding": [
				"CAP_SYS_ADMIN"
			],
			"effective": [
				"CAP_SYS_ADMIN"
			],
			"inheritable": [
				"CAP_SYS_ADMIN"
			],
			"permitted": [
				"CAP_SYS_ADMIN"
			],
			"ambient": [
				"CAP_SYS_ADMIN"
			]
		},
		"rlimits": [
			{
				"type": "RLIMIT_NOFILE",
				"hard": 1024,
				"soft": 1024
			}
		],
		"noNewPrivileges": true
	},
	"root": {
		"path": "rootfs",
		"readonly": false
	},
	"hostname": "runc",
	"mounts": [
		{
			"destination": "/proc",
			"type": "proc",
			"source": "proc"
		},
		{
			"destination": "/dev",
			"type": "tmpfs",
			"source": "tmpfs",
			"options": [
				"nosuid",
				"strictatime",
				"mode=755",
				"size=65536k"
			]
		},
		{
			"destination": "/dev/pts",
			"type": "devpts",
			"source": "devpts",
			"options": [
				"nosuid",
				"noexec",
				"newinstance",
				"ptmxmode=0666",
				"mode=0620"
			]
		},
		{
			"destination": "/dev/shm",
			"type": "tmpfs",
			"source": "shm",
			"options": [
				"nosuid",
				"noexec",
				"nodev",
				"mode=1777",
				"size=65536k"
			]
		},
		{
			"destination": "/dev/mqueue",
			"type": "mqueue",
			"source": "mqueue",
			"options": [
				"nosuid",
				"noexec",
				"nodev"
			]
		},
		{
			"destination": "/sys",
			"type": "none",
			"source": "/sys",
			"options": [
				"rbind",
				"nosuid",
				"noexec",
				"nodev",
				"ro"
			]
		}
	],
	"linux": {
		"uidMappings": [
			{
				"containerID": 0,
				"hostID": 1000,
				"size": 1
			}
		],
		"gidMappings": [
			{
				"containerID": 0,
				"hostID": 1000,
				"size": 1
			}
		],
		"namespaces": [
			{
				"type": "pid"
			},
			{
				"type": "ipc"
			},
			{
				"type": "uts"
			},
			{
				"type": "mount"
			},
			{
				"type": "user"
			}
		],
		"maskedPaths": [
			"/proc/acpi",
			"/proc/asound",
			"/proc/kcore",
			"/proc/keys",
			"/proc/latency_stats",
			"/proc/timer_list",
			"/proc/timer_stats",
			"/proc/sched_debug",
			"/sys/firmware",
			"/proc/scsi"
		],
		"readonlyPaths": [
			"/proc/bus",
			"/proc/fs",
			"/proc/irq",
			"/proc/sys",
			"/proc/sysrq-trigger"
		]
	}
}

The error I’m getting is:

$ runc run example
E: setgroups 65534 failed - setgroups (1: Operation not permitted)
E: setegid 65534 failed - setegid (22: Invalid argument)
E: seteuid 100 failed - seteuid (22: Invalid argument)
E: setgroups 0 failed - setgroups (1: Operation not permitted)
Reading package lists...
W: chown to _apt:root of directory /var/lib/apt/lists/partial failed - SetupAPTPartialDirectory (22: Invalid argument)
W: chown to _apt:root of directory /var/lib/apt/lists/auxfiles failed - SetupAPTPartialDirectory (22: Invalid argument)
E: setgroups 65534 failed - setgroups (1: Operation not permitted)
E: setegid 65534 failed - setegid (22: Invalid argument)
E: seteuid 100 failed - seteuid (22: Invalid argument)
E: setgroups 0 failed - setgroups (1: Operation not permitted)
E: Method gave invalid 400 URI Failure message: Failed to setgroups - setgroups (1: Operation not permitted)
E: Method http has died unexpectedly!
E: Sub-process http returned an error code (112)

With rootless docker I am able to actually run apt-get update. Any clue what’s going wrong here?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (3 by maintainers)

Most upvoted comments

This is how I got around it (thanks to @rofl0r commenting on apt internals above)

apt-config dump | grep Sandbox::User
cat <<EOF > /etc/apt/apt.conf.d/sandbox-disable
APT::Sandbox::User "root";
EOF

it seems the developers of apt went to great lengths to make it hard to use their software outside the context they envisioned. we can find this source code in apt-pkg/contrib/fileutl.cc (which i found after receiving those error messages after creating a ptrace hook to return fake success from SYS_chown):

   // enabled by default as all fakeroot-lookalikes should fake that accordingly
   if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::IDs", true) == true)
   {
      // Verify that gid, egid, uid, and euid changed
      if (getgid() != pw->pw_gid)
         return _error->Error("Could not switch group");
      if (getegid() != pw->pw_gid)
         return _error->Error("Could not switch effective group");
      if (getuid() != pw->pw_uid)
         return _error->Error("Could not switch user");
      if (geteuid() != pw->pw_uid)
         return _error->Error("Could not switch effective user");

i.e. the software refuses to work if you don’t use exactly the sandboxing mechanism they invented.

the good news is that we can get it to work by simply removing the line with the username _apt from /etc/passwd, and suddenly we can use apt update even with the most simple unshare sandbox as regular user. there seems to be also a config option “APT::Sandbox::Verify” but i don’t know how/where one can set it.

i’m commenting here in the hope other users getting those errors messages will find my solution, rather than having to mess around with capabilities to get it to work and generally wasting a lot of time.

I actually hoped i could convince you to do it, since the request coming from a developer of a well-known container solution would look a lot more legitimate than from some random distro dude.

I mean, I can do it – though there are far less combative ways to ask me to write and send patches to another project.

I don’t really see why there’s such a reluctance in the container scene to cooperate with the few misbehaving program’s authors instead of trying to find ever new low-level solutions for the problem.

It’s not reluctance to co-operate with other projects (speaking for myself, I contribute to many other projects). There are a couple of reasons why having workaround solutions in place is a good thing:

  • Working with upstream for every issue we run into doesn’t make it easier for users who are trying to just get something working in a container – having a best-effort solution (such as through syscall emulation) solves the problem for most people without blocking everything on us getting code in many different upstreams.
  • It’s possible any one upstream will disagree with our view that users should be able to run code inside a rootless container, at which point we would need a workaround solution anyway.
  • People with older images won’t get the benefit of any patches we push upstream because some distributions update on timescales rivaling continental drift.
  • Users may need to run proprietary software which they cannot patch, and having workarounds for those projects is also useful.

None of this means “we shouldn’t fix upstream projects” (far from it), it’s just that having a workaround solution is clearly better than not having one and I don’t see why you feel so strongly that having a workaround solution is somehow negative. The alternative is that users have no way of working around a project that doesn’t work in rootless containers.

(Not to mention that for some more complicated cases – such as faking mknod(2) – tools like SECCOMP_RET_USER_NOTIF are the only way of getting programs to run in containers nicely. So these techniques are useful in general.)

Same happens with the whole list:

"CAP_AUDIT_WRITE",
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_MKNOD",
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SETFCAP",
"CAP_SETGID",
"CAP_SETPCAP",
"CAP_SETUID",
"CAP_SYS_ADMIN",
"CAP_SYS_CHROOT"