'GLIBC_2.25' not found on Ubuntu (Windows Subsystem for Linux)

Solution 1:

Now this answer is presumably going to come a bit late, but let me try to give you some technical background of what you ran into here and possible ways to fix it.

For starters, what was commented about is true. The glibc is the C runtime which is central to a Ubuntu system. However, other distros choose other runtimes and a program could also opt to statically link a runtime that is more liberally licensed (musl-libc comes to mind). The latter method has its limitations as well, though.

As a rule of thumb you can run the software that was built on your system. This is perfectly fine for a system such as certain BSDs or Gentoo Linux, but it starts to become an issue for pre-packaged distros like Debian and Ubuntu.

System calls

Your kernel provides a number of system calls. A certain subset of these is standardized some will be specific to your system, i.e. GNU/Linux in this case.

Most system calls - such as "open a file" - have a direct counterpart in your C runtime, but the C runtime often will provide additional stuff on top (e.g. facilities for dynamic linking ...).

C runtime

The glibc is a so-called shared object. Other systems call it dynamic library or dynamically linked library. The goal is largely the same, although implementation and functionality differ.

The term shared object refers to the fact that the object code ("library functions") is shared across processes in your system.

Another advantage is that only this one library needs to be updated instead of each and every application that was statically linked with a particular version of the library.

And proprietary software (i.e. that which isn't FLOSS) has the licensing issue because of certain "issues" the glibc license brings for someone unwilling to disclose their proprietary source code. Now this is evidently not the issue with your code, since you could go ahead and build your own.

Missing symbols

The error you encountered:

/lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.25' not found

is one that I often jokingly confront people with who are complaining about "DLL hell" on Windows. Linux isn't better and the conventional linkers give you little control of the symbols you link against.

Alright, let's get our hands dirty a little. Make sure you have binutils installed (apt install binutils) and I'll assume that you have the standard Bash shell installed and that it can be found in /bin/bash.

We're going to start with the following invocation (instead of readelf, objdump would also work, but it has its own command line arguments):

readelf --dyn-syms /bin/bash|grep -P '\WUND\W'

What it does is to list all dynamic symbols from the Bash binary and then limit the output to those items which are undefined (UND). This will give us the list of functions Bash uses from glibc. We can further refine this by removing all entries that refer to the GLIBC_2.2.5 version by appending grep -v 'GLIBC_2\.2\.5':

$ readelf --dyn-syms /bin/bash|grep -P '\WUND\W'|grep -v 'GLIBC_2\.2\.5'
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_toupper_loc@GLIBC_2.3 (3)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tgetflag@NCURSES_TINFO_5.0.19991023 (4)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __snprintf_chk@GLIBC_2.3.4 (5)
    14: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __vfprintf_chk@GLIBC_2.3.4 (5)
    20: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
    22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tgetent@NCURSES_TINFO_5.0.19991023 (4)
    25: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tputs@NCURSES_TINFO_5.0.19991023 (4)
    37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND faccessat@GLIBC_2.4 (6)
    50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tgoto@NCURSES_TINFO_5.0.19991023 (4)
    60: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND eaccess@GLIBC_2.4 (6)
    63: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (6)
    72: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __fdelt_chk@GLIBC_2.15 (7)
    80: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tgetnum@NCURSES_TINFO_5.0.19991023 (4)
    98: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tgetstr@NCURSES_TINFO_5.0.19991023 (4)
   101: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __asprintf_chk@GLIBC_2.8 (8)
   104: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __memmove_chk@GLIBC_2.3.4 (5)
   106: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __memcpy_chk@GLIBC_2.3.4 (5)
   108: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
   114: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (9)
   138: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND regexec@GLIBC_2.3.4 (5)
   145: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __vsnprintf_chk@GLIBC_2.3.4 (5)
   147: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __strncpy_chk@GLIBC_2.3.4 (5)
   153: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __strcpy_chk@GLIBC_2.3.4 (5)
   157: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __printf_chk@GLIBC_2.3.4 (5)
   160: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __longjmp_chk@GLIBC_2.11 (11)
   195: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __fprintf_chk@GLIBC_2.3.4 (5)
   198: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
   215: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_tolower_loc@GLIBC_2.3 (3)
   216: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_b_loc@GLIBC_2.3 (3)
   219: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __sprintf_chk@GLIBC_2.3.4 (5)

The 2.2.5 version was the first to support x86-64 as an architecture, which is why we're not interested in it. The first available version will never pose an issue.

However, if you want this Bash binary on a system with "only" glibc 2.14 it would not work, because __fdelt_chk@GLIBC_2.15 requires at least version 2.15.

Interlude: symbol versioning

The @GLIBC_2.3.4 (etc.) suffix on the symbol names describe version ranges (also see here). This enables an ELF shared object to support multiple versions of a symbol. As an example a function foo() may have taken 2 arguments in version 1.0 of the library but 3 in the 2.x version range. The fact that software dynamically linking to these symbols contains the symbol version, means that it will always be linked to the appropriate version of a function.

However, the downside is what you encountered. Software linked on a system with a newer glibc (or really any other library also using symbol versioning) won't run on your (older) system if one of the symbols used would require a newer library version than is available on your system.

The classical method to "overcome" this issue was always by building on a system that was old enough to provide a low enough common denominator as far as library versions were concerned. This even allowed you to some extent to run a binary built on one distro to run on a range of others. The downside is/was that this means older compiler, older toolchain, older libraries (because there are more libraries than just the C runtime).

Certain alternatives exist (see below), but they all require that you analyze exactly what it is you want. For example statically linking to musl-libc - which comes at more liberal licensing terms - won't work if your program offers a plugin interface and exchanges buffers with the plugins ... dynamically linking to the same C runtime is important in such a case. Besides, musl-libc and glibc can interfere with each other in the same process because of sbrk.

Possible solutions to your conundrum

  • Run your software inside a VM on a more modern distro version which satisfies the library dependencies out of the box. Because the VM lets you run any kernel you desire, this will even work for software requiring very modern system calls.
  • Run your software inside a chroot jail or a container (similar to the VM theme above, but here you could run into issues if the code running inside makes use of newer system calls not available on your host kernel, LXD 4.0 attempts to mitigate this issue by providing stubs for these system calls).
  • Use an AppImage (someone needs to take care that the stub of the AppImage is as backwards compatible as possible, though.
  • Use a snap package.

More involved solutions, or if you are a developer

  • Offer a newer glibc version alongside the old one. Since this is a daemon there's a chance something gets to start it. Whether it is systemd or good old SysV init, you should be able to set LD_LIBRARY_PATH to point it to your updated glibc.
  • If you were the maintainer of said software, this approach would provide a viable alternative. What it does is to instruct the linker to choose an earlier version of the same symbol instead of the newest (picking the newest is the default).
    There is a project on GitHub under the name wheybags/glibc_version_header, which attempts to formalize this and may get you kickstarted.
    • This is a variation on the above approach.
    • This provides some more details on how to investigate the matter yourself.
    • This shows how to investigate the symbol version ranges required by a binary and the answer sheds some further light on the matter.
  • There used to be a project called autopackage which effectively used this very method in a wrapper apgcc that came with it. There are a number of places where you can still download the respective files to glean implementation details.
  • So far the most nuanced explanation is this one. The new memcpy as of glibc 2.14 is a GNU_IFUNC, which means that once at runtime the most efficient version (e.g. optimized for a particular processor model!) will be picked. That's opposed to the classic way of simply providing a direct reference to the function (to be relocated). The main takeaway from this is that you can't just tinker with the symbol name in this particular case, as this form of "delay-loading" mechanism has an ABI different from the one for the older versions of said symbol.
    • This is another explanation by the same user.

If you're actually interested in the internal workings of linkers you can grab a book on the subject matter or refer to Ian Lance Taylors excellent article series of which you can find an overview here.