Qualys Security Advisory - GNU C Library Memory Leak / Buffer Overflow
Posted on 13 December 2017
Qualys Security Advisory Buffer overflow in glibc's ld.so ======================================================================== Contents ======================================================================== Summary Memory Leak Buffer Overflow Exploitation Acknowledgments ======================================================================== Summary ======================================================================== We have discovered a memory leak and a buffer overflow in the dynamic loader (ld.so) of the GNU C Library (glibc): - the memory leak (CVE-2017-1000408) first appeared in glibc 2.1.1 (released on May 24, 1999) and can be reached and amplified through the LD_HWCAP_MASK environment variable; - the buffer overflow (CVE-2017-1000409) first appeared in glibc 2.5 (released on September 29, 2006) and can be triggered through the LD_LIBRARY_PATH environment variable. Further investigation showed that: - the buffer overflow is not exploitable if /proc/sys/fs/protected_hardlinks is enabled (it is not enabled by default on vanilla Linux kernels, but most Linux distributions turn it on by default); - the memory leak and the buffer overflow are not exploitable if the glibc is patched against CVE-2017-1000366, because this patch ignores the LD_HWCAP_MASK and LD_LIBRARY_PATH environment variables when SUID binaries are executed (CVE-2017-1000366 was first patched in glibc 2.26, released on August 2, 2017, but most Linux distributions had already backported this patch on June 19, 2017). We have therefore rated the impact of these vulnerabilities as Low. Nevertheless, we give a brief analysis of the vulnerable function, and present a simple method for exploiting a SUID binary on the command line and obtaining full root privileges (if /proc/sys/fs/protected_hardlinks is not enabled, and CVE-2017-1000366 is not patched). ======================================================================== Memory Leak (CVE-2017-1000408) ======================================================================== ------------------------------------------------------------------------ Analysis ------------------------------------------------------------------------ In _dl_init_paths(), ld.so malloc()ates "rtld_search_dirs.dirs[0]", a cache of information about the system's trusted directories (typically "/lib" and "/usr/lib" on 32-bit or "/lib64" and "/usr/lib64" on 64-bit). To compute the number of system directories, ld.so uses the classic C idiom "sizeof (system_dirs) / sizeof (system_dirs[0])": 691 rtld_search_dirs.dirs[0] = (struct r_search_path_elem *) 692 malloc ((sizeof (system_dirs) / sizeof (system_dirs[0])) 693 * round_size * sizeof (struct r_search_path_elem)); Unfortunately, "system_dirs" is not a classic array: it is not an array of strings (pointers to characters), but rather an array of characters, the concatenation of all system directories, separated by null bytes: 109 static const char system_dirs[] = SYSTEM_DIRS; where "SYSTEM_DIRS" is generated by "gen-trusted-dirs.awk" (typically "/lib/ /usr/lib/" on 32-bit or "/lib64/ /usr/lib64/" on 64-bit). As a result, the number of system directories is overestimated, and too much memory is allocated for "rtld_search_dirs.dirs[0]": if "system_dirs" is "/lib/ /usr/lib/" for example, the number of system directories is 2, but 16 is used instead (the number of characters in "system_dirs") to compute the size of "rtld_search_dirs.dirs[0]". This extra memory is never accessed, never freed, and mostly filled with null bytes, because only the information about "nsystem_dirs_len" system directories (the correct number of system directories) is written to "rtld_search_dirs.dirs[0]", and because the minimal malloc() implementation in ld.so calls mmap(), but never munmap(). Moreover, this memory leak can be amplified through the LD_HWCAP_MASK environment variable, because ld.so uses "ncapstr" (the total number of hardware-capability combinations) to compute the size of "rtld_search_dirs.dirs[0]": 687 round_size = ((2 * sizeof (struct r_search_path_elem) - 1 688 + ncapstr * sizeof (enum r_dir_status)) 689 / sizeof (struct r_search_path_elem)); ------------------------------------------------------------------------ History ------------------------------------------------------------------------ We tracked down this vulnerability to: commit ab7eb292307152e706948a7b19164ff5e6d593d4 Date: Mon May 3 21:59:35 1999 +0000 Update. * elf/Makefile (trusted-dirs.st): Use gen-trusted-dirs.awk. * elf/gen-trusted-dirs.awk: New file. * elf/dl-load.c (systems_dirs): Moved into file scope. Initialize from SYSTEM_DIRS macro. (system_dirs_len): New variable. Contains lengths of system_dirs strings. (fillin_rpath): Rewrite for systems_dirs being a simple string. Improve string comparisons. Change parameter trusted to be a flag. Change all callers. (_dt_init_paths): Improve using new format for system_dirs. which transformed "system_dirs" from an array of strings (pointers to characters) into an array of characters: - static const char *system_dirs[] = - { -#include "trusted-dirs.h" - NULL - }; ... +static const char system_dirs[] = SYSTEM_DIRS; ======================================================================== Buffer Overflow (CVE-2017-1000409) ======================================================================== ------------------------------------------------------------------------ Analysis ------------------------------------------------------------------------ In _dl_init_paths(), ld.so computes "nllp", the number of colon-separated directories in "llp" (the LD_LIBRARY_PATH environment variable), malloc()ates "env_path_list.dirs", an array of "nllp + 1" pointers to "r_search_path_elem" structures (one for each directory in "llp", plus a terminating NULL pointer), and calls fillin_rpath() to fill in "env_path_list.dirs": 777 if (llp != NULL && *llp != '