Home > Software design >  No such file or directory on any executable file in docker
No such file or directory on any executable file in docker

Time:06-14

I try to run a copy of ls from /bin (and tried on any executable file that isn't in /bin in a container) and get the error.

docker run -v ~/Desktop/test:/test alpine:latest /test/ls
exec /test/ls: no such file or directory

When I run another command I can see that file.

docker run -v ~/Desktop/test:/test alpine:latest ls /test
ls

I try to set chmode x on this file, but don't get any changes. Changing image doesn't have any influation too. I've updated docker and all packaged by apt-get update && apt-get upgrade.

I use Ubuntu 20.04.3 LTS.

Docker version 20.10.17, build 100c701

Inspect

{
    "Id": "abd66c400e6f58313d71c63c92688472874cdd4393d42e22df23a90264083b33",
    "Created": "2022-06-13T19:59:49.630474765Z",
    "Path": "/test/ls",
    "Args": [],
    "State": {
        "Status": "exited",
        "Running": false,
        "Paused": false,
        "Restarting": false,
        "OOMKilled": false,
        "Dead": false,
        "Pid": 0,
        "ExitCode": 1,
        "Error": "",
        "StartedAt": "2022-06-13T19:59:50.017551978Z",
        "FinishedAt": "2022-06-13T19:59:50.016401363Z"
    },
    "Image": "sha256:0ac33e5f5afa79e084075e8698a22d574816eea8d7b7d480586835657c3e1c8b",
    "ResolvConfPath": "/var/lib/docker/containers/abd66c400e6f58313d71c63c92688472874cdd4393d42e22df23a90264083b33/resolv.conf",
    "HostnamePath": "/var/lib/docker/containers/abd66c400e6f58313d71c63c92688472874cdd4393d42e22df23a90264083b33/hostname",
    "HostsPath": "/var/lib/docker/containers/abd66c400e6f58313d71c63c92688472874cdd4393d42e22df23a90264083b33/hosts",
    "LogPath": "/var/lib/docker/containers/abd66c400e6f58313d71c63c92688472874cdd4393d42e22df23a90264083b33/abd66c400e6f58313d71c63c92688472874cdd4393d42e22df23a90264083b33-json.log",
    "Name": "/cranky_pare",
    "RestartCount": 0,
    "Driver": "overlay2",
    "Platform": "linux",
    "MountLabel": "",
    "ProcessLabel": "",
    "AppArmorProfile": "docker-default",
    "ExecIDs": null,
    "HostConfig": {
        "Binds": [
            "/home/egor/Desktop/test:/test"
        ],
        "ContainerIDFile": "",
        "LogConfig": {
            "Type": "json-file",
            "Config": {}
        },
        "NetworkMode": "default",
        "PortBindings": {},
        "RestartPolicy": {
            "Name": "no",
            "MaximumRetryCount": 0
        },
        "AutoRemove": false,
        "VolumeDriver": "",
        "VolumesFrom": null,
        "CapAdd": null,
        "CapDrop": null,
        "CgroupnsMode": "host",
        "Dns": [],
        "DnsOptions": [],
        "DnsSearch": [],
        "ExtraHosts": null,
        "GroupAdd": null,
        "IpcMode": "private",
        "Cgroup": "",
        "Links": null,
        "OomScoreAdj": 0,
        "PidMode": "",
        "Privileged": false,
        "PublishAllPorts": false,
        "ReadonlyRootfs": false,
        "SecurityOpt": null,
        "UTSMode": "",
        "UsernsMode": "",
        "ShmSize": 67108864,
        "Runtime": "runc",
        "ConsoleSize": [
            0,
            0
        ],
        "Isolation": "",
        "CpuShares": 0,
        "Memory": 0,
        "NanoCpus": 0,
        "CgroupParent": "",
        "BlkioWeight": 0,
        "BlkioWeightDevice": [],
        "BlkioDeviceReadBps": null,
        "BlkioDeviceWriteBps": null,
        "BlkioDeviceReadIOps": null,
        "BlkioDeviceWriteIOps": null,
        "CpuPeriod": 0,
        "CpuQuota": 0,
        "CpuRealtimePeriod": 0,
        "CpuRealtimeRuntime": 0,
        "CpusetCpus": "",
        "CpusetMems": "",
        "Devices": [],
        "DeviceCgroupRules": null,
        "DeviceRequests": null,
        "KernelMemory": 0,
        "KernelMemoryTCP": 0,
        "MemoryReservation": 0,
        "MemorySwap": 0,
        "MemorySwappiness": null,
        "OomKillDisable": false,
        "PidsLimit": null,
        "Ulimits": null,
        "CpuCount": 0,
        "CpuPercent": 0,
        "IOMaximumIOps": 0,
        "IOMaximumBandwidth": 0,
        "MaskedPaths": [
            "/proc/asound",
            "/proc/acpi",
            "/proc/kcore",
            "/proc/keys",
            "/proc/latency_stats",
            "/proc/timer_list",
            "/proc/timer_stats",
            "/proc/sched_debug",
            "/proc/scsi",
            "/sys/firmware"
        ],
        "ReadonlyPaths": [
            "/proc/bus",
            "/proc/fs",
            "/proc/irq",
            "/proc/sys",
            "/proc/sysrq-trigger"
        ]
    },
    "GraphDriver": {
        "Data": {
            "LowerDir": "/var/lib/docker/overlay2/115140c86b11eb773f30d2329ec05388224e75dfbcef9977c87bb3f0e32e8769-init/diff:/var/lib/docker/overlay2/22348355d634b9183f798e9622f71809a9a62a7c6accdf723aa72d979e3ba7f4/diff",
            "MergedDir": "/var/lib/docker/overlay2/115140c86b11eb773f30d2329ec05388224e75dfbcef9977c87bb3f0e32e8769/merged",
            "UpperDir": "/var/lib/docker/overlay2/115140c86b11eb773f30d2329ec05388224e75dfbcef9977c87bb3f0e32e8769/diff",
            "WorkDir": "/var/lib/docker/overlay2/115140c86b11eb773f30d2329ec05388224e75dfbcef9977c87bb3f0e32e8769/work"
        },
        "Name": "overlay2"
    },
    "Mounts": [
        {
            "Type": "bind",
            "Source": "/home/egor/Desktop/test",
            "Destination": "/test",
            "Mode": "",
            "RW": true,
            "Propagation": "rprivate"
        }
    ],
    "Config": {
        "Hostname": "abd66c400e6f",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": true,
        "AttachStderr": true,
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "Cmd": [
            "/test/ls"
        ],
        "Image": "alpine:latest",
        "Volumes": null,
        "WorkingDir": "",
        "Entrypoint": null,
        "OnBuild": null,
        "Labels": {}
    },
    "NetworkSettings": {
        "Bridge": "",
        "SandboxID": "fb64f195f203033f02b9c5555e0b1d2cdd2819f3ac12f3046416f8e13378bc3d",
        "HairpinMode": false,
        "LinkLocalIPv6Address": "",
        "LinkLocalIPv6PrefixLen": 0,
        "Ports": {},
        "SandboxKey": "/var/run/docker/netns/fb64f195f203",
        "SecondaryIPAddresses": null,
        "SecondaryIPv6Addresses": null,
        "EndpointID": "",
        "Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "IPAddress": "",
        "IPPrefixLen": 0,
        "IPv6Gateway": "",
        "MacAddress": "",
        "Networks": {
            "bridge": {
                "IPAMConfig": null,
                "Links": null,
                "Aliases": null,
                "NetworkID": "cb261cd6eca2ddb567984e2d7d9e6df14008c774d73f0d76129da6c8edc6955c",
                "EndpointID": "",
                "Gateway": "",
                "IPAddress": "",
                "IPPrefixLen": 0,
                "IPv6Gateway": "",
                "GlobalIPv6Address": "",
                "GlobalIPv6PrefixLen": 0,
                "MacAddress": "",
                "DriverOpts": null
            }
        }
    },
    "CreatedTime": 1655150389630
}

CodePudding user response:

The program binary itself is (in most cases) not self-sufficient; instead, it depends on a number of dynamic libraries (usually at least a C standard library) and a dynamic linker (also known as the interpreter), which knows how to load the binary into the memory and to link it with these libraries.

The information about what linker the system has to use to launch the specific program and which libraries the program requires is stored inside the program binary itself. You may easily extract this information using tools like ldd and readelf. For example, this is how you may find all the stuff /bin/ls needs to run successfully:

$ ldd /bin/ls
  linux-vdso.so.1 (0x00007ffec7b46000)
  libcap.so.2 => /usr/lib/libcap.so.2 (0x00007f805384c000)
  libc.so.6 => /usr/lib/libc.so.6 (0x00007f8053642000)
  /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f80538ae000)

Or, more verbosely:

$ readelf -a /bin/ls
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
...
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
...
Dynamic section at offset 0x22a58 contains 28 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libcap.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

Note: my examples are taken from Arch box, so your results may be different, yet close to this. The main point here is that the binary relies on ld-linux, which is true for both Arch and Ubuntu (see below).

When you ask the kernel to execute ls, it reads the program binary, looks for the linker the program requires (/lib64/ld-linux-x86-64.so.2) in the filesystem and runs it. The linker then loads ls and all its dependencies into the memory; only after this the code of ls itself is executed.

The problem is that different Linux distributions (and different versions of them) have different environments. While it is common for distros to use GNU libc (glibc) as the C standard library and its ld-linux as the linker, there also are distros using other libc variants and other linkers. Alpine is one of such distros: it uses musl libc and its own linker ld-musl:

$ docker run -it alpine
/ # ldd /bin/ls
  /lib/ld-musl-x86_64.so.1 (0x7f40c772d000)
  libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f40c772d000)

This means that Alpine does not have /lib64/ld-linux-x86-64.so.2 file (by default; see Running glibc programs). That is why running ls built for glibc-based system (Ubuntu in your case) inside an Alpine container fails: the kernel reads the binary, learns that it needs /lib64/ld-linux-x86-64.so.2 to load this program, and finds no such file inside the filesystem.

When the kernel is unable to find the interpreter requested by the program, it returns the same error as when you ask it to execute the non-existent file:

man 2 execve
...
ENOENT The file pathname or a script or ELF interpreter does not exist.

This is why you get no such file or directory, which is somewhat misleading, as it does not say which file is not found — the linker, not the program itself.

Given that ls does not have lots of dependencies, chances are good that the trick you try to do will work with containers having glibc-based systems inside:

$ docker run -it -v /bin/ls:/test/ls alpine /test/ls
exec /test/ls: no such file or directory

...

$ docker run -it -v /bin/ls:/test/ls ubuntu /test/ls
bin   dev  home  lib32  libx32  mnt  proc  run   srv  test  usr
boot  etc  lib   lib64  media opt  root  sbin  sys  tmp   var

...

$ docker run -it -v /bin/ls:/test/ls archlinux /test/ls
bin   dev  home  lib64  opt   root  sbin  sys tmp  var
boot  etc  lib   mnt  proc  run   srv   test  usr

However, running host binaries inside the container is not what you are supposed to do and there is no guarantee that things won't go haywire, so make sure you really know what you do when employing such tricks.

  • Related