Issue
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
}
Solution
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.
Answered By - Danila Kiver Answer Checked By - Mary Flores (WPSolving Volunteer)