GNU C Library version 2.39
The GNU C Library (glibc) released version 2.39 on January 31, including several new features. Notable highlights include new functions for spawning child processes, support for shadow stacks on x86_64, new security features, and the removal of libcrypt. The glibc maintainers had also hoped to include improvements to qsort(), which ended up not making it into this release. Glibc releases are made every six months.
Pidfd changes
The Linux kernel added support for pidfds in the 5.1 development cycle in 2018. More support was added in the kernel in 2019, but still required some user-space support to be generally useful. Process IDs (PIDs) can be reused by different processes, so there can be races between the time a PID is obtained and used; if the PID is reused in that window, the wrong process will be operated on. Other objects on Unix-like systems are traditionally represented using files — pidfds are unique file descriptors that refer to a specific process, even if another process reuses the same PID. However, making this facility generally available to programs requires some user-space support. The new release of glibc includes several additional functions for working with pidfds; LWN covered the creation of two new helper functions when the work was being introduced: pidfd_spawn() and pidfd_spawnp(). These functions are used to create a process and directly return its pidfd, avoiding a potential race condition where a process could exit and be replaced by a different process before its parent can open a pidfd for it. These functions are only available on systems with the clone3() system call.
Those aren't the only new functions for working with pidfds, however. This release also adds posix_spawnattr_setcgroup_np() and posix_spawnattr_getcgroup_np() which can be used to set which control group a spawned process will be part of before the process is started. Setting a process's control group ahead of time avoids a race condition between starting the process and applying resource limits to it. These functions are part of the family of functions that modify the posix_spawnattr_t structure used by the posix_spawn() family of functions to specify implementation-defined attributes of the process to be spawned.
int posix_spawnattr_getcgroup_np(const posix_spawnattr_t *restrict attr,
int *restrict cgroup);
int posix_spawnattr_setcgroup_np(posix_spawnattr_t *attr,
int cgroup);
Finally, programs that use the new pidfd interface, but that still need to determine the child process's PID for whatever reason, can use pidfd_getpid():
pid_t pidfd_getpid(int fd);
Shadow stacks
Another new kernel feature that this release allows programmers to take advantage of is support for x86's control-flow enforcement technology (CET). There are two technologies included in CET: shadow stacks and indirect branch tracking. Both are aimed at reducing or eliminating return-oriented programming (ROP) attacks. Glibc already supported programs compiled with support for indirect branch tracking. This glibc update is focused on adding support for shadow stacks, since setting up a shadow stack also requires the cooperation of the dynamic linker. Shadow stacks work by keeping a second copy of function return addresses in specially protected memory, so that an attack that overwrites the stack cannot take control of the program.
The Linux kernel has allowed programs to opt into using shadow stacks since version 6.6, but there hasn't been a good way for them to take advantage of that capability without making direct system calls. This release of glibc includes an --enable-cet configure option that enables support for shadow stacks. When built with this flag, glibc will ask the kernel to allocate memory for the shadow stack and enable the protection during program startup.
Programs also need to be compiled with shadow-stack support to take advantage of the feature, however. The GNU Compiler Collection (GCC) and Clang both support compiling programs with shadow-stack support via the -fcf-protection flag. Some distributions have already enabled this flag by default (including Ubuntu and Fedora). Glibc's --enable-cet configure option changes the behavior of loading shared objects. When a process starts, glibc checks whether the binary supports CET. If it does, loading a shared object that was not compiled with CET support becomes an error. Glibc also supports --enable-cet=permissive, which silently disables the shadow stack when opening a non-shadow-stack-aware shared object.
PLT rewrite
One last proactive security feature in this release is the addition of support for rewriting the Procedure Linkage Table (PLT) on startup. A dynamically linked program cannot tell at compile time what the location of any shared objects in memory will be at run time. Therefore, when the program makes a call to a function from another shared object, that call goes via the PLT. On the first invocation of a given function in the PLT, the code there jumps to the dynamic linker, which searches to find the right offset for the function and then rewrites the PLT entry to point to the function directly; subsequent calls do not need to involve the dynamic linker.
This release of glibc adds a "tunable" called glibc.cpu.plt_rewrite. When this option is enabled, the dynamic linker rewrites the PLT to use absolute jumps to the correct location on startup, instead of waiting for a particular function to be invoked. Once the PLT is rewritten, it is then remapped as read-only memory. This can slightly increase the startup time of the program, but it removes a potential avenue for attacks that seek to control the program: overwriting the PLT with maliciously chosen jump destinations. Glibc tunables can be set using the GLIBC_TUNABLES environment variable as follows: GLIBC_TUNABLES=glibc.cpu.plt_rewrite=1.
qsort()
One change that did not make it into the release was a proposed improvement to
qsort(). In October, Adhemerval Zanella authored
a patch that swapped qsort()'s
venerable merge sort
implementation for one based on
introsort instead.
He noted that merge sort performs poorly on already-sorted or nearly-sorted
arrays, a problem that does not appear in introsort.
He also noted that the "mergesort implementation has
some issues
", going on to point out that merge sort requires auxiliary
memory, meaning that qsort() can call malloc(). This demand
for additional memory means
that calling qsort() might introduce arbitrary delays while the system
conjures enough memory for merge sort's auxiliary array.
This potential delay also makes the function not async-cancel safe. POSIX requires that programs in certain contexts (such as signal handlers, or after a new thread is created) call only async-cancel-safe functions, or risk undefined behavior. POSIX only requires a handful of functions be async-cancel safe, but glibc maintains a list of which library functions are async-cancel safe in practice, to aid developers wishing to rely on details of glibc's implementation. In an earlier discussion on the mailing list, Zanella noted that he sees making glibc functions async-cancel safe where possible as a quality-of-life improvement for users.
Zanella ended up
reverting the change at the beginning of January, saying that
the removal of merge sort
"had the side-effect of making sorting nonstable. Although neither
POSIX nor C standard specify that qsort should be stable, it seems
that it has become an instance of Hyrum's law where multiple programs
expect it
". Stable sorting algorithms such as merge sort keep two inputs
that compare equal in the same order in the output, while unstable sorting
algorithms don't make that guarantee.
Zanella went on to note that the existing implementation does fall
back to a heap-sort
algorithm if it is unable to allocate the needed additional
memory. Heap sort is not a
stable sort, so there is still a potential bug lurking in programs that expect
qsort() to be stable, although it can't be triggered on the vast
majority of Linux systems. Most Linux systems use memory overcommit: a feature of the kernel
which allows programs to allocate more anonymous memory than the system can
support in total, on the theory that many programs don't use all the anonymous
memory they request. Memory
overcommit is enabled on nearly all Linux systems — the exception
being some embedded systems that specifically turn it off — which may make finding
the error in any programs that do trigger this problem somewhat difficult.
Qualys reported
a security vulnerability in the qsort() implementation in January, but
Florian Weimer had actually already fixed it during some unrelated work the month before. The
vulnerability affects all versions of glibc present in the project's
Git repository, going back to 1989 at least. In the announcement, Qualys merely
claims the vulnerability is present in 1992, but there were no changes to the
relevant code between the initial commits recorded in the Git history and the
1.04 release in 1992. The project's security team were quoted in Qualys's report
as saying that the behavior
required to trigger the vulnerability is "undefined according to POSIX and
ISO C standards
", and that they nonetheless "acknowledge that this is a
quality of implementation issue and we fixed this in a recent refactor of qsort
".
libcrypt
Another thing that this release does not contain is libcrypt, the library that
provided the
crypt() password-hashing function.
The default build configuration of glibc already
omitted libcrypt as of version 2.38, but users could opt-in using the
--enable-crypt configure flag, which is now removed.
Users of libcrypt should consider switching to
libxcrypt, a replacement
offering the same interface under a compatible license, but maintained
separately from glibc.
Libcrypt was the last remaining use of Mozilla's
Network Security Services (NSS)
cryptography library in the C library, allowing the project to drop that
dependency.
The removal of libcrypt is the end of a process that began in 2017, when Zack
Weinberg
suggested that libcrypt might best be moved to "a separate project that
could move faster
" in response to a request to add an OpenBSD-compatible
bcrypt() function.
Conclusion
Version 2.39 does not include any groundbreaking new developments, which is to be expected from a stable project like the GNU C library. This release includes access to new kernel features and several security improvements, alongside the expected maintenance of the locale system and bug fixes. 68 contributors helped produce the new version, with 21 reviewing patches. Version 2.40 is expected in another six months, around the end of July.
