boxboat/fixuid

fixuid must be owned by user 'root' and include the setuid bit

koxu1996 opened this issue · 13 comments

I am unable to run fixuid with simple Dockerfile:

FROM alpine:latest

RUN addgroup -g 1000 docker && \
    adduser -u 1000 -G docker -h /home/docker -s /bin/sh -D docker

RUN apk update && \
    apk add ca-certificates wget && \
    update-ca-certificates

RUN USER=docker && \
    GROUP=docker && \
    wget -qO- https://github.com/boxboat/fixuid/releases/download/v0.3/fixuid-0.3-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \
    chown root:root /usr/local/bin/fixuid && \
    chmod 4755 /usr/local/bin/fixuid && \
    mkdir -p /etc/fixuid && \
    printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml

USER docker:docker
ENTRYPOINT ["fixuid"]

Result:

$ docker build -t test-fixuid .
$ docker run -it test-fixuid
fixuid: fixuid should only ever be used on development systems. DO NOT USE IN PRODUCTION
fixuid: fixuid must be owned by user 'root' and include the setuid bit 'chmod u+s /path/to/fixuid'

I checked it with $ docker run -it --entrypoint sh test-fixuid and it looks good:

/ $ cd /usr/local/bin/
/usr/local/bin $ ls -al
total 3616
drwxr-xr-x    1 root     root            12 Mar 17 23:13 .
drwxr-xr-x    1 root     root            22 Jan  9 19:37 ..
-rwsr-xr-x    1 root     root       3699694 Jan 15 16:08 fixuid

Am I doing something wrong?

What is your host operating system? Is the host running selinux by chance? If so can you setenforce=permissiveon the host and try?

I am using Arch Linux, without selinux.

Odd, I copied your Dockerfile to my system Ubuntu 17.10 w/ Kernel 4.13.0-37-generic running Docker 17.10.0-ce and it works fine:

fixuid: fixuid should only ever be used on development systems. DO NOT USE IN PRODUCTION
fixuid: runtime UID '1000' already matches container user 'docker' UID
fixuid: runtime GID '1000' already matches container group 'docker' GID

The error that you're seeing means that the geteuid check is returning a non-zero value

What is your kernel version from uname -r and your Docker version from docker info?

$ uname -r 
4.15.9-1-ARCH
$ docker info
Containers: 26
 Running: 0
 Paused: 0
 Stopped: 26
Images: 110
Server Version: 18.02.0-ce
Storage Driver: btrfs
 Build Version: Btrfs v4.15
 Library Version: 102
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 9b55aab90508bd389d7654c4baf173a981477d55
runc version: 9f9c96235cc97674e935002fc3d78361b696a69e
init version: 949e6fa
Security Options:
 seccomp
  Profile: default
Kernel Version: 4.15.9-1-ARCH
Operating System: Arch Linux
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 3.853GiB
Name: archPC
ID: 3YY6:CPQ7:QVET:BWBB:SF6N:76KA:2SXF:F4KN:HNSM:HJOR:QDPR:UNEM
Docker Root Dir: /run/media/docker
Debug Mode (client): false
Debug Mode (server): false
Username: koxu1996
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

I checked and running getuid() returns 1000 in suided bin. Dockerfile:

FROM alpine:latest

RUN addgroup -g 1000 docker && \
    adduser -u 1000 -G docker -h /home/docker -s /bin/sh -D docker

RUN apk update && \
    apk add build-base

RUN echo $'#include <stdio.h>\n#include <unistd.h>\n\
int main()\n\
{\n\
    printf("UID: %d", getuid());\n\
    return 0;\n\
}\n' > /usr/local/bin/testuid.c

RUN cd /usr/local/bin && \
    gcc testuid.c -o testuid && \
    chown root:root testuid && \
    chmod 4755 testuid

USER docker:docker

ENTRYPOINT ["testuid"]

Result:

$ docker build -t test-uid .
$ docker run -it test-uid .
UID: 1000

BTW I found similar program: https://github.com/schmidigital/permission-fix/blob/master/tools/permission_fix Could you tell me if fixuid is better than it?

The syscall you should check it geteuid for get effective user ID. Can you rerun the testuid binary with geteuid instead of getuid

The permission fix script you linked just uses usermod and groupmod to change user/group IDs and looks like it would need to run as root. fixuid aims to be distribution agnostic, does not require running a container as root, and handles changing file permissions on specified mounts.

Yep, that should be geteuid(), but even changing it result is still the same: UID: 1000. Same situation occurs when I run it on my host.

My best guess is that you are hitting this:

https://www.projectatomic.io/blog/2016/03/no-new-privs-docker/

Moby has a test case that

Does this allow it to work?

docker run -it --security-opt "no-new-privileges=false" test-uid

Since you are not explicitly setting --security-opt "no-new-privileges=true", my best guess is that it is being enabled by your default seccomp profile. I'm not that familiar with seccomp but there is some mention of it implying no-new-privileges=true in this issue: opencontainers/runtime-spec#290 (comment):

It would be nice to set this to be enabled by default, or at least note that passing a Seccomp configuration implies that it is enabled, given that it has a beneficial relationship with applications in a container that also use Seccomp.

Setting flag does not help:

$ docker build --no-cache -t test-uid .
...
$ docker run -it --security-opt "no-new-privileges=false" test-uid
UID: 1000

This is definetly problem with my host, but not docker or fixuid. I will try to figure it out and let you know.

Thanks, if it's an upcoming change to a default or security profile it'd be good to document the workaround

Finally I found reason why it does not work: https://unix.stackexchange.com/questions/150972/why-setuid-does-not-work-on-executable
My docker storage is on external drive, which is automatically mounted with nosuid option:

mount | grep docker | grep nosuid
/dev/sdb1 on /run/media/docker type btrfs (rw,nosuid,nodev,relatime,space_cache,subvolid=5,subvol=/)
/dev/sdb1 on /run/media/docker/plugins type btrfs (rw,nosuid,nodev,relatime,space_cache,subvolid=5,subvol=/plugins)
/dev/sdb1 on /run/media/docker/btrfs type btrfs (rw,nosuid,nodev,relatime,space_cache,subvolid=5,subvol=/btrfs)

I do not know Go language, but it would be nice if you could add additional check which ensure that filesystem is mounted without nosuid.

I will probably just edit the error message to be more verbose. Something like:

fixuid: not running as root, ensure that the following criteria are met:
fixuid: - fixuid binary is owned by root: 'chown root:root /path/to/fixuid'
fixuid: - fixuid binary has the setuid bit: 'chmod u+s /path/to/fixuid'
fixuid: - NoNewPrivileges is disabled in container security profile
fixuid: - volume containing fixuid binary does not have the 'nosuid' mount option

It is okay for me.